Repository: intellij-rust/intellij-rust (language: rust)

Issue:
Incorrect highlighting of dead code in macros
## Environment

* **IntelliJ Rust plugin version:** 0.4.183.4980-222-nightly
* **Rust toolchain version:** 1.67.0-nightly (73c9eaf21 2022-11-07) x86_64-unknown-linux-gnu
* **IDE name and version:** CLion 2022.2.4 (CL-222.4345.21)
* **Operating system:** Linux 5.4.0-131-generic
* **Macro expansion:** enabled
* **Additional experimental features:** org.rust.cargo.features.settings.gutter, org.rust.cargo.emulate.terminal, org.rust.macros.proc.function-like, org.rust.macros.proc

## Problem description

Tokens inside of macros are marked as conditionally disabled, even if they are used inside of AST elements which are not conditionally disabled.

The result of that is that code, which uses macros to generate code for every `cfg` option, will misleadingly appear all conditionally disabled, and won't get any syntax highlighting.

```rust
macro_rules! mac {
    ($name: ident) => {
        #[cfg(unix)]
        struct $name;
        #[cfg(not(unix))]
        struct $name;
    };
}

mac!(Foo);
//   ^^^ conditionally disabled code
```



Discussion / hints:
The issue is known since a while, but coincidentally, I'm just now thinking about how to fix it

Fix patch:
diff --git a/src/main/kotlin/org/rust/ide/annotator/AnnotatorBase.kt b/src/main/kotlin/org/rust/ide/annotator/AnnotatorBase.kt
index 52c6b3629e0..5e28b85029f 100644
--- a/src/main/kotlin/org/rust/ide/annotator/AnnotatorBase.kt
+++ b/src/main/kotlin/org/rust/ide/annotator/AnnotatorBase.kt
@@ -17,7 +17,7 @@ import org.rust.openapiext.isUnitTestMode
 abstract class AnnotatorBase : Annotator {
 
     final override fun annotate(element: PsiElement, holder: AnnotationHolder) {
-        if (!isUnitTestMode || javaClass in enabledAnnotators) {
+        if (isEnabled(javaClass)) {
             annotateInternal(element, holder)
         }
     }
@@ -32,5 +32,9 @@ abstract class AnnotatorBase : Annotator {
             enabledAnnotators += annotatorClass
             Disposer.register(parentDisposable) { enabledAnnotators -= annotatorClass }
         }
+
+        fun isEnabled(annotatorClass: Class<out AnnotatorBase>): Boolean {
+            return !isUnitTestMode || annotatorClass in enabledAnnotators
+        }
     }
 }
diff --git a/src/main/kotlin/org/rust/ide/annotator/RsCfgDisabledCodeAnnotator.kt b/src/main/kotlin/org/rust/ide/annotator/RsCfgDisabledCodeAnnotator.kt
index 2b9a874ad80..b8cbc27b106 100644
--- a/src/main/kotlin/org/rust/ide/annotator/RsCfgDisabledCodeAnnotator.kt
+++ b/src/main/kotlin/org/rust/ide/annotator/RsCfgDisabledCodeAnnotator.kt
@@ -16,16 +16,7 @@ class RsCfgDisabledCodeAnnotator : AnnotatorBase() {
     override fun annotateInternal(element: PsiElement, holder: AnnotationHolder) {
         if (holder.isBatchMode) return
 
-        val crate = holder.currentCrate() ?: return
-
-        if (element is RsDocAndAttributeOwner && !element.isEnabledByCfgSelfOrInAttrProcMacroBody(crate)) {
-            holder.createCondDisabledAnnotation()
-        }
-
-        val isAttrDisabled = element is RsAttr
-            && element.isDisabledCfgAttrAttribute(crate)
-            && element.owner?.isEnabledByCfgSelfOrInAttrProcMacroBody(crate) == true
-        if (isAttrDisabled) {
+        if (shouldHighlightAsCfsDisabled(element, holder)) {
             holder.createCondDisabledAnnotation()
         }
     }
@@ -40,7 +31,19 @@ class RsCfgDisabledCodeAnnotator : AnnotatorBase() {
     }
 
     companion object {
-        private val CONDITIONALLY_DISABLED_CODE_SEVERITY = HighlightSeverity(
+        fun shouldHighlightAsCfsDisabled(element: PsiElement, holder: AnnotationHolder) : Boolean {
+            val crate = holder.currentCrate() ?: return false
+
+            if (element is RsDocAndAttributeOwner && !element.isEnabledByCfgSelfOrInAttrProcMacroBody(crate)) {
+                return true
+            }
+
+            return element is RsAttr
+                && element.isDisabledCfgAttrAttribute(crate)
+                && element.owner?.isEnabledByCfgSelfOrInAttrProcMacroBody(crate) == true
+        }
+
+        val CONDITIONALLY_DISABLED_CODE_SEVERITY = HighlightSeverity(
             "CONDITIONALLY_DISABLED_CODE",
             HighlightSeverity.INFORMATION.myVal + 1
         )
diff --git a/src/main/kotlin/org/rust/ide/annotator/RsMacroExpansionHighlightingPass.kt b/src/main/kotlin/org/rust/ide/annotator/RsMacroExpansionHighlightingPass.kt
index caa381a497e..6620b28ebe2 100644
--- a/src/main/kotlin/org/rust/ide/annotator/RsMacroExpansionHighlightingPass.kt
+++ b/src/main/kotlin/org/rust/ide/annotator/RsMacroExpansionHighlightingPass.kt
@@ -8,10 +8,7 @@ package org.rust.ide.annotator
 import com.intellij.codeHighlighting.DirtyScopeTrackingHighlightingPassFactory
 import com.intellij.codeHighlighting.TextEditorHighlightingPass
 import com.intellij.codeHighlighting.TextEditorHighlightingPassRegistrar
-import com.intellij.codeInsight.daemon.impl.AnnotationHolderImpl
-import com.intellij.codeInsight.daemon.impl.FileStatusMap
-import com.intellij.codeInsight.daemon.impl.HighlightInfo
-import com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil
+import com.intellij.codeInsight.daemon.impl.*
 import com.intellij.lang.annotation.Annotation
 import com.intellij.lang.annotation.AnnotationSession
 import com.intellij.lang.annotation.Annotator
@@ -22,15 +19,21 @@ import com.intellij.openapi.progress.ProgressManager
 import com.intellij.openapi.project.Project
 import com.intellij.openapi.util.TextRange
 import com.intellij.openapi.util.registry.Registry
+import com.intellij.psi.PsiElement
 import com.intellij.psi.PsiFile
 import com.intellij.psi.util.PsiTreeUtil
 import org.rust.ide.annotator.format.RsFormatMacroAnnotator
+import org.rust.ide.colors.RsColor
 import org.rust.ide.fixes.RsQuickFixBase
+import org.rust.lang.core.crate.Crate
 import org.rust.lang.core.crate.asNotFake
 import org.rust.lang.core.macros.*
+import org.rust.lang.core.psi.AttrCache
 import org.rust.lang.core.psi.RsFile
 import org.rust.lang.core.psi.RsMacroCall
 import org.rust.lang.core.psi.ext.RsAttrProcMacroOwner
+import org.rust.lang.core.psi.ext.isEnabledByCfg
+import org.rust.openapiext.isUnitTestMode
 import org.rust.stdext.removeLast
 
 class RsMacroExpansionHighlightingPassFactory(
@@ -73,7 +76,6 @@ class RsMacroExpansionHighlightingPass(
             RsEdition2018KeywordsAnnotator(),
             RsAttrHighlightingAnnotator(),
             RsHighlightingMutableAnnotator(),
-            RsCfgDisabledCodeAnnotator(),
             RsFormatMacroAnnotator(),
         )
         val annotatorsForAttrMacros = annotatorsForDeclMacros + listOf(
@@ -114,8 +116,12 @@ class RsMacroExpansionHighlightingPass(
             @Suppress("DEPRECATION")
             val holder = AnnotationHolderImpl(annotationSession, false)
             val annotators = if (macro.isDeeplyAttrMacro) annotatorsForAttrMacros else annotatorsForDeclMacros
+            val cfgDisabledElements = mutableListOf<PsiElement>()
 
             for (element in macro.elementsForHighlighting) {
+                if (RsCfgDisabledCodeAnnotator.shouldHighlightAsCfsDisabled(element, holder)) {
+                    cfgDisabledElements += element
+                }
                 for (ann in annotators) {
                     ProgressManager.checkCanceled()
                     holder.runAnnotatorWithContext(element, ann)
@@ -131,6 +137,10 @@ class RsMacroExpansionHighlightingPass(
             for (ann in holder) {
                 mapAndCollectAnnotation(macro, ann)
             }
+
+            if (crate != null && AnnotatorBase.isEnabled(RsCfgDisabledCodeAnnotator::class.java)) {
+                highlightCfgDisabledRanges(crate, macro, cfgDisabledElements)
+            }
         }
     }
 
@@ -152,6 +162,32 @@ class RsMacroExpansionHighlightingPass(
         }
     }
 
+    private fun highlightCfgDisabledRanges(
+        crate: Crate,
+        macro: MacroCallPreparedForHighlighting,
+        cfgDisabledElements: List<PsiElement>
+    ) {
+        val cache = AttrCache.HashMapCache(crate)
+        val cfgDisabledMappedRanges = cfgDisabledElements.flatMapTo(HashSet()) {
+            mapRangeFromExpansionToCallBody(macro.expansion, macro.macroCall, it.textRange)
+        }.filter { range ->
+            val element = file.findElementAt(range.startOffset) ?: return@filter false
+            val expansionElements = element.findExpansionElements(cache) ?: return@filter false
+            !expansionElements.any { it.isEnabledByCfg(crate) }
+        }
+
+        for (mappedRange in cfgDisabledMappedRanges) {
+            val color = RsColor.CFG_DISABLED_CODE
+            val severity = if (isUnitTestMode) color.testSeverity else RsCfgDisabledCodeAnnotator.CONDITIONALLY_DISABLED_CODE_SEVERITY
+            results += HighlightInfo.newHighlightInfo(HighlightInfoType.INFORMATION)
+                .severity(severity)
+                .textAttributes(color.textAttributesKey)
+                .range(mappedRange)
+                .descriptionAndTooltip("Conditionally disabled code")
+                .createUnconditionally()
+        }
+    }
+
     override fun doApplyInformationToEditor() {
         UpdateHighlightersUtil.setHighlightersToEditor(
             myProject,


Test patch:
diff --git a/src/test/kotlin/org/rust/ide/annotator/RsCfgDisabledCodeAnnotatorTest.kt b/src/test/kotlin/org/rust/ide/annotator/RsCfgDisabledCodeAnnotatorTest.kt
index 368dae16400..a9e0d345309 100644
--- a/src/test/kotlin/org/rust/ide/annotator/RsCfgDisabledCodeAnnotatorTest.kt
+++ b/src/test/kotlin/org/rust/ide/annotator/RsCfgDisabledCodeAnnotatorTest.kt
@@ -100,4 +100,30 @@ class RsCfgDisabledCodeAnnotatorTest : RsAnnotatorTestBase(RsCfgDisabledCodeAnno
             let x = 1;
         }</CFG_DISABLED_CODE>
     """)
+
+    @MockAdditionalCfgOptions("intellij_rust")
+    fun `test disabled code in a macro call`() = checkHighlighting("""
+        macro_rules! foo {
+            ($ e:expr) => {
+                #[cfg(disabled)]
+                fn foo() {$ e;}
+            };
+        }
+        fn main() {
+            foo!(/*CFG_DISABLED_CODE*/2 + 2/*CFG_DISABLED_CODE**/);
+        }
+    """)
+
+    @MockAdditionalCfgOptions("intellij_rust")
+    fun `test disabled code is not highlighted in a macro call if the same code is used as enabled`() = checkHighlighting("""
+        macro_rules! foo {
+            ($ e:expr) => {
+                #[cfg(disabled)]
+                fn foo() {$ e;}
+            };
+        }
+        fn main() {
+            foo!(2 + 2);
+        }
+    """)
 }