Repository: intellij-rust/intellij-rust
Issue: Plugin thinks type is imported when it is not

<!--
Hello and thank you for the issue!
If you would like to report a bug, we have added some points below that you can fill out.
Feel free to remove all the irrelevant text to request a new feature.
-->

## Environment

* **IntelliJ Rust plugin version:** 0.4.182.4996-222
* **Rust toolchain version:** 1.62.0-beta.7 (747075d9c 2022-06-26) x86_64-unknown-linux-gnu
* **IDE name and version:** CLion 2022.2.4 (CL-222.4345.21)
* **Operating system:** Linux 5.15.0-52-generic
* **Macro expansion:** enabled
* **Additional experimental features:** org.rust.macros.proc

## Problem description
The IDE seems to think sometimes that a type is imported (thus showing up white, rather than red), hitting Ctrl+space does not do anything. The type is not imported though (and rustc confirms)

## Steps to reproduce
Honestly, I dont know for sure. It usually works, except for one type in my project. (HttpError, an enum, in my case)

I unfortunately cannot include the codebase publicly, though I can share it privately with this project's maintainers, if need be.
<!--
Please include as much of your codebase as needed to reproduce the error.
If the relevant files are large, please provide a link to a public repository or a [Gist](https://gist.github.com/).
-->

Changed files:
src/test/kotlin/org/rust/lang/core/resolve/RsResolveInsideFunctionsTest.kt, src/main/kotlin/org/rust/lang/core/resolve2/ModCollector.kt, src/main/kotlin/org/rust/lang/core/resolve2/DefCollector.kt, src/main/kotlin/org/rust/lang/core/resolve2/HangingModData.kt, src/main/kotlin/org/rust/lang/core/macros/MacroExpansionSharedCache.kt, src/main/kotlin/org/rust/lang/core/macros/tt/MappedSubtree.kt, src/main/kotlin/org/rust/lang/core/resolve2/CrateDefMap.kt, src/main/kotlin/org/rust/lang/core/macros/RangeMap.kt, src/main/kotlin/org/rust/lang/core/macros/MappedText.kt, src/main/kotlin/org/rust/lang/core/macros/DocsLowering.kt, src/main/kotlin/org/rust/lang/core/macros/proc/ProcMacroExpander.kt, src/main/kotlin/org/rust/lang/core/macros/decl/DeclMacroExpander.kt

diff --git a/src/main/kotlin/org/rust/lang/core/macros/DocsLowering.kt b/src/main/kotlin/org/rust/lang/core/macros/DocsLowering.kt
index 02a9bfc69..da4ac370f 100644
--- a/src/main/kotlin/org/rust/lang/core/macros/DocsLowering.kt
+++ b/src/main/kotlin/org/rust/lang/core/macros/DocsLowering.kt
@@ -28,7 +28,7 @@ fun PsiBuilder.lowerDocCommentsToPsiBuilder(project: Project): Pair<PsiBuilder,
 }
 
 private fun PsiBuilder.defaultRangeMap(): RangeMap= if (originalText.isNotEmpty()) {
-    RangeMap.from(SmartList(MappedTextRange(0, 0, originalText.length)))
+    RangeMap(MappedTextRange(0, 0, originalText.length))
 } else {
     RangeMap.EMPTY
 }
@@ -78,7 +78,7 @@ fun PsiBuilder.lowerDocComments(): Pair<CharSequence, RangeMap>? {
         }
     }
 
-    return sb to RangeMap.from(ranges)
+    return sb to RangeMap(ranges)
 }
 
 private fun PsiBuilder.hasDocComments(): Boolean {
diff --git a/src/main/kotlin/org/rust/lang/core/macros/MacroExpansionSharedCache.kt b/src/main/kotlin/org/rust/lang/core/macros/MacroExpansionSharedCache.kt
index 19da77c39..f70308795 100644
--- a/src/main/kotlin/org/rust/lang/core/macros/MacroExpansionSharedCache.kt
+++ b/src/main/kotlin/org/rust/lang/core/macros/MacroExpansionSharedCache.kt
@@ -350,6 +350,12 @@ class ExpansionResultOk(
         .toIntArray()
 )
 
+fun ExpansionResultOk.shiftDstRangesRight(offset: Int): ExpansionResultOk {
+    val dollarCrateOccurrences = dollarCrateOccurrences.map { it + offset }.toIntArray()
+    val ranges = ranges.ranges.map { it.dstShiftRight(offset) }
+    return ExpansionResultOk(text, RangeMap(ranges), dollarCrateOccurrences)
+}
+
 private object ExpansionResultExternalizer : DataExternalizer<RsResult<ExpansionResultOk, MacroExpansionError>> {
     @Throws(IOException::class)
     override fun save(out: DataOutput, value: RsResult<ExpansionResultOk, MacroExpansionError>) {
diff --git a/src/main/kotlin/org/rust/lang/core/macros/MappedText.kt b/src/main/kotlin/org/rust/lang/core/macros/MappedText.kt
index e49bf59fc..d9952fa9d 100644
--- a/src/main/kotlin/org/rust/lang/core/macros/MappedText.kt
+++ b/src/main/kotlin/org/rust/lang/core/macros/MappedText.kt
@@ -15,7 +15,7 @@ data class MappedText(val text: String, val ranges: RangeMap) {
             return if (text.isNotEmpty()) {
                 MappedText(
                     text,
-                    RangeMap.from(SmartList(MappedTextRange(srcOffset, 0, text.length)))
+                    RangeMap(MappedTextRange(srcOffset, 0, text.length))
                 )
             } else {
                 EMPTY
@@ -44,7 +44,7 @@ class MutableMappedText private constructor(
         }
     }
 
-    fun toMappedText(): MappedText = MappedText(sb.toString(), RangeMap.from(SmartList(ranges)))
+    fun toMappedText(): MappedText = MappedText(sb.toString(), RangeMap(SmartList(ranges)))
 
     override fun toString(): String {
         return sb.toString()
diff --git a/src/main/kotlin/org/rust/lang/core/macros/RangeMap.kt b/src/main/kotlin/org/rust/lang/core/macros/RangeMap.kt
index 8ef6ae9da..f9b25bae6 100644
--- a/src/main/kotlin/org/rust/lang/core/macros/RangeMap.kt
+++ b/src/main/kotlin/org/rust/lang/core/macros/RangeMap.kt
@@ -18,8 +18,11 @@ import kotlin.math.min
 /**
  * Must provide [equals] method because it is used to track changes in the macro expansion mechanism
  */
-@Suppress("DataClassPrivateConstructor")
-data class RangeMap private constructor(val ranges: List<MappedTextRange>) {
+data class RangeMap(val ranges: List<MappedTextRange>) {
+
+    constructor(range: MappedTextRange) : this(listOf(range))
+
+    constructor(ranges: SmartList<MappedTextRange>) : this(ranges.optimizeList())
 
     fun isEmpty(): Boolean = ranges.isEmpty()
 
@@ -72,10 +75,6 @@ data class RangeMap private constructor(val ranges: List<MappedTextRange>) {
             val ranges = (0 until size).map { data.readMappedTextRange() }
             return RangeMap(ranges)
         }
-
-        fun from(ranges: SmartList<MappedTextRange>): RangeMap {
-            return RangeMap(ranges.optimizeList())
-        }
     }
 }
 
diff --git a/src/main/kotlin/org/rust/lang/core/macros/decl/DeclMacroExpander.kt b/src/main/kotlin/org/rust/lang/core/macros/decl/DeclMacroExpander.kt
index 33daaba85..667693964 100644
--- a/src/main/kotlin/org/rust/lang/core/macros/decl/DeclMacroExpander.kt
+++ b/src/main/kotlin/org/rust/lang/core/macros/decl/DeclMacroExpander.kt
@@ -183,7 +183,7 @@ class DeclMacroExpander(val project: Project): MacroExpander<RsDeclMacroData, De
             }
             return Err(DeclMacroExpansionError.DefSyntax)
         }
-        return Ok(sb to RangeMap.from(ranges))
+        return Ok(sb to RangeMap(ranges))
     }
 
     private fun substituteMacro(
diff --git a/src/main/kotlin/org/rust/lang/core/macros/proc/ProcMacroExpander.kt b/src/main/kotlin/org/rust/lang/core/macros/proc/ProcMacroExpander.kt
index 9d96e8056..221f677f0 100644
--- a/src/main/kotlin/org/rust/lang/core/macros/proc/ProcMacroExpander.kt
+++ b/src/main/kotlin/org/rust/lang/core/macros/proc/ProcMacroExpander.kt
@@ -10,7 +10,6 @@ import com.intellij.openapi.util.registry.Registry
 import com.intellij.psi.PsiElement
 import com.intellij.psi.PsiErrorElement
 import com.intellij.psi.impl.source.tree.LeafPsiElement
-import com.intellij.util.SmartList
 import org.rust.cargo.project.settings.toolchain
 import org.rust.cargo.toolchain.RsToolchainBase
 import org.rust.lang.core.crate.Crate
@@ -81,7 +80,7 @@ class ProcMacroExpander private constructor(
                 subtree.copy(delimiter = null),
                 // TODO try shift TokenMap offsets instead
                 macroCallBodyTokenMap.merge(map),
-                RangeMap.from(SmartList(loweredMacroCallBodyRanges.ranges + shiftedRanges))
+                RangeMap(loweredMacroCallBodyRanges.ranges + shiftedRanges)
             )
         } else {
             Triple(null, macroCallBodyTokenMap, loweredMacroCallBodyRanges)
diff --git a/src/main/kotlin/org/rust/lang/core/macros/tt/MappedSubtree.kt b/src/main/kotlin/org/rust/lang/core/macros/tt/MappedSubtree.kt
index 7d16602e1..651fff2b2 100644
--- a/src/main/kotlin/org/rust/lang/core/macros/tt/MappedSubtree.kt
+++ b/src/main/kotlin/org/rust/lang/core/macros/tt/MappedSubtree.kt
@@ -39,7 +39,7 @@ private class SubtreeTextBuilder(
 
     fun toText(): Pair<CharSequence, RangeMap> {
         subtree.appendSubtree()
-        return sb to RangeMap.from(ranges)
+        return sb to RangeMap(ranges)
     }
 
     private fun TokenTree.Subtree.appendSubtree() {
diff --git a/src/main/kotlin/org/rust/lang/core/resolve2/CrateDefMap.kt b/src/main/kotlin/org/rust/lang/core/resolve2/CrateDefMap.kt
index e7a5b34d5..3e68dbbbd 100644
--- a/src/main/kotlin/org/rust/lang/core/resolve2/CrateDefMap.kt
+++ b/src/main/kotlin/org/rust/lang/core/resolve2/CrateDefMap.kt
@@ -282,8 +282,10 @@ class ModData(
     val isEnum: Boolean = false,
     /** Normal cargo crate with physical crate root, etc */
     val isNormalCrate: Boolean = true,
-    /** not-null when `this` corresponds to [RsBlock]. See [getHangingModInfo] */
+    /** not-null when `this` corresponds to [RsBlock], [RsCodeFragment], etc. See [getHangingModInfo] */
     val context: ModData? = null,
+    /** scope is [RsBlock] */
+    val isBlock: Boolean = false,
     /** Only for debug */
     val crateDescription: String,
 ) {
diff --git a/src/main/kotlin/org/rust/lang/core/resolve2/DefCollector.kt b/src/main/kotlin/org/rust/lang/core/resolve2/DefCollector.kt
index f2a3f9047..d0722e3c7 100644
--- a/src/main/kotlin/org/rust/lang/core/resolve2/DefCollector.kt
+++ b/src/main/kotlin/org/rust/lang/core/resolve2/DefCollector.kt
@@ -10,6 +10,7 @@ import com.intellij.openapi.progress.ProgressIndicator
 import com.intellij.openapi.progress.ProgressManager
 import com.intellij.openapi.project.Project
 import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS
+import com.intellij.psi.stubs.StubElement
 import gnu.trove.THashMap
 import org.rust.cargo.project.workspace.CargoWorkspaceData
 import org.rust.lang.core.crate.CratePersistentId
@@ -17,14 +18,16 @@ import org.rust.lang.core.macros.*
 import org.rust.lang.core.macros.decl.MACRO_DOLLAR_CRATE_IDENTIFIER
 import org.rust.lang.core.macros.proc.ProcMacroApplicationService
 import org.rust.lang.core.psi.*
+import org.rust.lang.core.psi.ext.RsItemsOwner
 import org.rust.lang.core.psi.ext.body
+import org.rust.lang.core.psi.ext.stubDescendantOfTypeOrStrict
 import org.rust.lang.core.resolve.Namespace
 import org.rust.lang.core.resolve2.ImportType.GLOB
 import org.rust.lang.core.resolve2.ImportType.NAMED
 import org.rust.lang.core.resolve2.PartialResolvedImport.*
 import org.rust.lang.core.resolve2.util.DollarCrateMap
+import org.rust.lang.core.resolve2.util.buildStub
 import org.rust.lang.core.resolve2.util.createDollarCrateHelper
-import org.rust.lang.core.stubs.RsFileStub
 import org.rust.openapiext.*
 import org.rust.stdext.HashCode
 import org.rust.stdext.getWithRethrow
@@ -295,7 +298,7 @@ class DefCollector(
     private data class ExpansionOutput(
         val call: MacroCallInfo,
         val def: MacroDefInfo,
-        val expandedFile: RsFileStub?,
+        val expandedFile: StubElement<out RsItemsOwner>?,
         val expansion: ExpansionResultOk?,
         val mixHash: HashCode,
     )
@@ -373,12 +376,36 @@ class DefCollector(
                 return null
             }
         }
-        val (expandedFile, expansion) =
+
+        val (expandedFile, expansion) = if (call.containingMod.isBlock) {
+            expandMacroInBlock(callData.data, defData.data, mixHash)
+        } else {
             macroExpanderShared.createExpansionStub(project, macroExpander, defData.data, callData.data, mixHash).ok()
-                ?: (null to null)
+        } ?: (null to null)
         return ExpansionOutput(call, def, expandedFile, expansion, mixHash)
     }
 
+    /**
+     * fn main() {
+     *     // should be expanded in statement context
+     *     gen!();
+     * }
+     */
+    private fun expandMacroInBlock(
+        callData: RsMacroCallData,
+        defData: RsMacroData,
+        mixHash: HashCode
+    ): Pair<StubElement<RsBlock>, ExpansionResultOk>? {
+        val expansion = macroExpanderShared.cachedExpand(macroExpander, defData, callData, mixHash).ok()
+            ?: return null
+        val macroExpansionContext = MacroExpansionContext.STMT
+        val expandedText = macroExpansionContext.prepareExpandedTextForParsing(expansion.text)
+        val expandedFile = RsPsiFactory(project, markGenerated = false).createPsiFile(expandedText) as? RsFile ?: return null
+        val expandedBlock = expandedFile.stubDescendantOfTypeOrStrict<RsBlock>() ?: return null
+        val expandedStub = expandedBlock.buildStub() ?: return null
+        return expandedStub to expansion.shiftDstRangesRight(macroExpansionContext.expansionFileStartOffset)
+    }
+
     private fun recordExpansion(result: ExpansionOutput) {
         val (call, def, expandedFile, expansion, mixHash) = result
 
diff --git a/src/main/kotlin/org/rust/lang/core/resolve2/HangingModData.kt b/src/main/kotlin/org/rust/lang/core/resolve2/HangingModData.kt
index 4916c62f4..36d01fccc 100644
--- a/src/main/kotlin/org/rust/lang/core/resolve2/HangingModData.kt
+++ b/src/main/kotlin/org/rust/lang/core/resolve2/HangingModData.kt
@@ -101,6 +101,7 @@ private fun createHangingModData(scope: RsItemsOwner, contextInfo: RsModInfo): M
         hasMacroUse = false,
         isNormalCrate = false,
         context = contextData,
+        isBlock = scope is RsBlock,
         crateDescription = "$pathSegment in ${contextData.crateDescription}",
     )
 
diff --git a/src/main/kotlin/org/rust/lang/core/resolve2/ModCollector.kt b/src/main/kotlin/org/rust/lang/core/resolve2/ModCollector.kt
index d3cc02a3d..8caf7f216 100644
--- a/src/main/kotlin/org/rust/lang/core/resolve2/ModCollector.kt
+++ b/src/main/kotlin/org/rust/lang/core/resolve2/ModCollector.kt
@@ -78,7 +78,7 @@ fun collectScope(
 }
 
 fun collectExpandedElements(
-    expandedFile: RsFileStub,
+    scope: StubElement<out RsItemsOwner>,
     call: MacroCallInfo,
     context: ModCollectorContext,
     dollarCrateHelper: DollarCrateHelper?
@@ -91,7 +91,7 @@ fun collectExpandedElements(
         dollarCrateHelper,
         includeMacroFile = null
     )
-    collector.collectMod(expandedFile, propagateLegacyMacros = true)
+    collector.collectMod(scope, propagateLegacyMacros = true)
 }
 
 /**
diff --git a/src/test/kotlin/org/rust/lang/core/resolve/RsResolveInsideFunctionsTest.kt b/src/test/kotlin/org/rust/lang/core/resolve/RsResolveInsideFunctionsTest.kt
index bb5f1dc2d..f7197df3d 100644
--- a/src/test/kotlin/org/rust/lang/core/resolve/RsResolveInsideFunctionsTest.kt
+++ b/src/test/kotlin/org/rust/lang/core/resolve/RsResolveInsideFunctionsTest.kt
@@ -346,6 +346,21 @@ class RsResolveInsideFunctionsTest : RsResolveTestBase() {
     """)
     }
 
+    fun `test macro expanded to import inside block`() = checkByCode("""
+        mod inner {
+            pub fn func() {}
+        }
+        macro_rules! gen {
+            () => {
+                { use inner::func; }
+            }
+        }
+        fn main() {
+            gen!();
+            func();
+        } //^ unresolved
+    """)
+
     fun `test macro expanded to $crate import`() = stubOnlyResolve("""
     //- lib.rs
         #[macro_export]
@@ -408,4 +423,24 @@ class RsResolveInsideFunctionsTest : RsResolveTestBase() {
             foo.func();
         }     //^ lib.rs
     """)
+
+    fun `test nested macro expanded to $crate import`() = stubOnlyResolve("""
+    //- lib.rs
+        pub fn foo() {}
+             //X
+        #[macro_export]
+        macro_rules! gen {
+            () => {
+                as_is! {
+                    use $ crate::foo;
+                }
+            }
+        }
+    //- main.rs
+        macro_rules! as_is { ($($ t:tt)*) => {$($ t)*} }
+        fn main() {
+            test_package::gen!();
+            foo();
+        } //^ lib.rs
+    """)
 }