Repository: rust-analyzer/rust-analyzer
Pull request #8997 (2021-05-25 23:14:59)

internal: stop expanding UseTrees during ItemTree lowering
Closes https://github.com/rust-analyzer/rust-analyzer/issues/8908

Messy diff, but `ItemTree` lowering got simpler, since we now have a strict 1-to-1 mapping between `ast::Item` and `ModItem`.

The most messy part is mapping a single `UseTree` back to its `ast::UseTree` counterpart for diagnostics, but I think the ad-hoc source map built during lowering does the job.


Patch:
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 1ecd2391b0..01b2de515a 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -472,27 +472,13 @@ impl Module {
                     });
                 }
 
-                DefDiagnosticKind::UnresolvedImport { ast, index } => {
-                    let use_item = ast.to_node(db.upcast());
-                    let hygiene = Hygiene::new(db.upcast(), ast.file_id);
-                    let mut cur = 0;
-                    let mut tree = None;
-                    ModPath::expand_use_item(
-                        db.upcast(),
-                        InFile::new(ast.file_id, use_item),
-                        &hygiene,
-                        |_mod_path, use_tree, _is_glob, _alias| {
-                            if cur == *index {
-                                tree = Some(use_tree.clone());
-                            }
-
-                            cur += 1;
-                        },
-                    );
+                DefDiagnosticKind::UnresolvedImport { id, index } => {
+                    let file_id = id.file_id();
+                    let item_tree = id.item_tree(db.upcast());
+                    let import = &item_tree[id.value];
 
-                    if let Some(tree) = tree {
-                        sink.push(UnresolvedImport { file: ast.file_id, node: AstPtr::new(&tree) });
-                    }
+                    let use_tree = import.use_tree_to_ast(db.upcast(), file_id, *index);
+                    sink.push(UnresolvedImport { file: file_id, node: AstPtr::new(&use_tree) });
                 }
 
                 DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } => {
diff --git a/crates/hir_def/src/item_tree.rs b/crates/hir_def/src/item_tree.rs
index 11767d100f..f84c4cf2b6 100644
--- a/crates/hir_def/src/item_tree.rs
+++ b/crates/hir_def/src/item_tree.rs
@@ -523,21 +523,38 @@ impl<N: ItemTreeNode> Index<FileItemTreeId<N>> for ItemTree {
     }
 }
 
-/// A desugared `use` import.
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub struct Import {
-    pub path: Interned<ModPath>,
-    pub alias: Option<ImportAlias>,
     pub visibility: RawVisibilityId,
-    pub is_glob: bool,
-    /// AST ID of the `use` item this import was derived from. Note that many `Import`s can map to
-    /// the same `use` item.
     pub ast_id: FileAstId<ast::Use>,
-    /// Index of this `Import` when the containing `Use` is visited via `ModPath::expand_use_item`.
-    ///
-    /// This can be used to get the `UseTree` this `Import` corresponds to and allows emitting
-    /// precise diagnostics.
-    pub index: usize,
+    pub use_tree: UseTree,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct UseTree {
+    pub index: Idx<ast::UseTree>,
+    kind: UseTreeKind,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum UseTreeKind {
+    /// ```
+    /// use path::to::Item;
+    /// use path::to::Item as Renamed;
+    /// use path::to::Trait as _;
+    /// ```
+    Single { path: Interned<ModPath>, alias: Option<ImportAlias> },
+
+    /// ```
+    /// use *;  // (invalid, but can occur in nested tree)
+    /// use path::*;
+    /// ```
+    Glob { path: Option<Interned<ModPath>> },
+
+    /// ```
+    /// use prefix::{self, Item, ...};
+    /// ```
+    Prefixed { prefix: Option<Interned<ModPath>>, list: Box<[UseTree]> },
 }
 
 #[derive(Debug, Clone, Eq, PartialEq)]
@@ -711,6 +728,97 @@ pub struct MacroDef {
     pub ast_id: FileAstId<ast::MacroDef>,
 }
 
+impl Import {
+    /// Maps a `UseTree` contained in this import back to its AST node.
+    pub fn use_tree_to_ast(
+        &self,
+        db: &dyn DefDatabase,
+        file_id: HirFileId,
+        index: Idx<ast::UseTree>,
+    ) -> ast::UseTree {
+        // Re-lower the AST item and get the source map.
+        // Note: The AST unwraps are fine, since if they fail we should have never obtained `index`.
+        let ast = InFile::new(file_id, self.ast_id).to_node(db.upcast());
+        let ast_use_tree = ast.use_tree().expect("missing `use_tree`");
+        let hygiene = Hygiene::new(db.upcast(), file_id);
+        let (_, source_map) =
+            lower::lower_use_tree(db, &hygiene, ast_use_tree).expect("failed to lower use tree");
+        source_map[index].clone()
+    }
+}
+
+impl UseTree {
+    /// Expands the `UseTree` into individually imported `ModPath`s.
+    pub fn expand(
+        &self,
+        mut cb: impl FnMut(Idx<ast::UseTree>, ModPath, /* is_glob */ bool, Option<ImportAlias>),
+    ) {
+        self.expand_impl(None, &mut cb)
+    }
+
+    fn expand_impl(
+        &self,
+        prefix: Option<ModPath>,
+        cb: &mut dyn FnMut(
+            Idx<ast::UseTree>,
+            ModPath,
+            /* is_glob */ bool,
+            Option<ImportAlias>,
+        ),
+    ) {
+        fn concat_mod_paths(prefix: Option<ModPath>, path: &ModPath) -> Option<ModPath> {
+            match (prefix, &path.kind) {
+                (None, _) => Some(path.clone()),
+                (Some(mut prefix), PathKind::Plain) => {
+                    for segment in path.segments() {
+                        prefix.push_segment(segment.clone());
+                    }
+                    Some(prefix)
+                }
+                (Some(prefix), PathKind::Super(0)) => {
+                    // `some::path::self` == `some::path`
+                    if path.segments().is_empty() {
+                        Some(prefix)
+                    } else {
+                        None
+                    }
+                }
+                (Some(_), _) => None,
+            }
+        }
+
+        match &self.kind {
+            UseTreeKind::Single { path, alias } => {
+                if let Some(path) = concat_mod_paths(prefix, path) {
+                    cb(self.index, path, false, alias.clone());
+                }
+            }
+            UseTreeKind::Glob { path: Some(path) } => {
+                if let Some(path) = concat_mod_paths(prefix, path) {
+                    cb(self.index, path, true, None);
+                }
+            }
+            UseTreeKind::Glob { path: None } => {
+                if let Some(prefix) = prefix {
+                    cb(self.index, prefix, true, None);
+                }
+            }
+            UseTreeKind::Prefixed { prefix: additional_prefix, list } => {
+                let prefix = match additional_prefix {
+                    Some(path) => match concat_mod_paths(prefix, path) {
+                        Some(path) => Some(path),
+                        None => return,
+                    },
+                    None => prefix,
+                };
+                for tree in &**list {
+                    tree.expand_impl(prefix.clone(), cb);
+                }
+            }
+        }
+    }
+}
+
 macro_rules! impl_froms {
     ($e:ident { $($v:ident ($t:ty)),* $(,)? }) => {
         $(
diff --git a/crates/hir_def/src/item_tree/lower.rs b/crates/hir_def/src/item_tree/lower.rs
index b4389371fb..40f3428b7b 100644
--- a/crates/hir_def/src/item_tree/lower.rs
+++ b/crates/hir_def/src/item_tree/lower.rs
@@ -3,7 +3,6 @@
 use std::{collections::hash_map::Entry, mem, sync::Arc};
 
 use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, name::known, HirFileId};
-use smallvec::SmallVec;
 use syntax::{
     ast::{self, ModuleItemOwner},
     SyntaxNode, WalkEvent,
@@ -20,22 +19,10 @@ fn id<N: ItemTreeNode>(index: Idx<N>) -> FileItemTreeId<N> {
     FileItemTreeId { index, _p: PhantomData }
 }
 
-struct ModItems(SmallVec<[ModItem; 1]>);
-
-impl<T> From<T> for ModItems
-where
-    T: Into<ModItem>,
-{
-    fn from(t: T) -> Self {
-        ModItems(SmallVec::from_buf([t.into(); 1]))
-    }
-}
-
 pub(super) struct Ctx<'a> {
     db: &'a dyn DefDatabase,
     tree: ItemTree,
     hygiene: Hygiene,
-    file: HirFileId,
     source_ast_id_map: Arc<AstIdMap>,
     body_ctx: crate::body::LowerCtx<'a>,
     forced_visibility: Option<RawVisibilityId>,
@@ -47,7 +34,6 @@ impl<'a> Ctx<'a> {
             db,
             tree: ItemTree::default(),
             hygiene,
-            file,
             source_ast_id_map: db.ast_id_map(file),
             body_ctx: crate::body::LowerCtx::new(db, file),
             forced_visibility: None,
@@ -55,11 +41,8 @@ impl<'a> Ctx<'a> {
     }
 
     pub(super) fn lower_module_items(mut self, item_owner: &dyn ModuleItemOwner) -> ItemTree {
-        self.tree.top_level = item_owner
-            .items()
-            .flat_map(|item| self.lower_mod_item(&item, false))
-            .flat_map(|items| items.0)
-            .collect();
+        self.tree.top_level =
+            item_owner.items().flat_map(|item| self.lower_mod_item(&item, false)).collect();
         self.tree
     }
 
@@ -71,7 +54,6 @@ impl<'a> Ctx<'a> {
                 _ => None,
             })
             .flat_map(|item| self.lower_mod_item(&item, false))
-            .flat_map(|items| items.0)
             .collect();
 
         // Non-items need to have their inner items collected.
@@ -98,7 +80,7 @@ impl<'a> Ctx<'a> {
         self.tree.data_mut()
     }
 
-    fn lower_mod_item(&mut self, item: &ast::Item, inner: bool) -> Option<ModItems> {
+    fn lower_mod_item(&mut self, item: &ast::Item, inner: bool) -> Option<ModItem> {
         // Collect inner items for 1-to-1-lowered items.
         match item {
             ast::Item::Struct(_)
@@ -129,34 +111,28 @@ impl<'a> Ctx<'a> {
         };
 
         let attrs = RawAttrs::new(self.db, item, &self.hygiene);
-        let items = match item {
-            ast::Item::Struct(ast) => self.lower_struct(ast).map(Into::into),
-            ast::Item::Union(ast) => self.lower_union(ast).map(Into::into),
-            ast::Item::Enum(ast) => self.lower_enum(ast).map(Into::into),
-            ast::Item::Fn(ast) => self.lower_function(ast).map(Into::into),
-            ast::Item::TypeAlias(ast) => self.lower_type_alias(ast).map(Into::into),
-            ast::Item::Static(ast) => self.lower_static(ast).map(Into::into),
-            ast::Item::Const(ast) => Some(self.lower_const(ast).into()),
-            ast::Item::Module(ast) => self.lower_module(ast).map(Into::into),
-            ast::Item::Trait(ast) => self.lower_trait(ast).map(Into::into),
-            ast::Item::Impl(ast) => self.lower_impl(ast).map(Into::into),
-            ast::Item::Use(ast) => Some(ModItems(
-                self.lower_use(ast).into_iter().map(Into::into).collect::<SmallVec<_>>(),
-            )),
-            ast::Item::ExternCrate(ast) => self.lower_extern_crate(ast).map(Into::into),
-            ast::Item::MacroCall(ast) => self.lower_macro_call(ast).map(Into::into),
-            ast::Item::MacroRules(ast) => self.lower_macro_rules(ast).map(Into::into),
-            ast::Item::MacroDef(ast) => self.lower_macro_def(ast).map(Into::into),
-            ast::Item::ExternBlock(ast) => Some(self.lower_extern_block(ast).into()),
+        let item: ModItem = match item {
+            ast::Item::Struct(ast) => self.lower_struct(ast)?.into(),
+            ast::Item::Union(ast) => self.lower_union(ast)?.into(),
+            ast::Item::Enum(ast) => self.lower_enum(ast)?.into(),
+            ast::Item::Fn(ast) => self.lower_function(ast)?.into(),
+            ast::Item::TypeAlias(ast) => self.lower_type_alias(ast)?.into(),
+            ast::Item::Static(ast) => self.lower_static(ast)?.into(),
+            ast::Item::Const(ast) => self.lower_const(ast).into(),
+            ast::Item::Module(ast) => self.lower_module(ast)?.into(),
+            ast::Item::Trait(ast) => self.lower_trait(ast)?.into(),
+            ast::Item::Impl(ast) => self.lower_impl(ast)?.into(),
+            ast::Item::Use(ast) => self.lower_use(ast)?.into(),
+            ast::Item::ExternCrate(ast) => self.lower_extern_crate(ast)?.into(),
+            ast::Item::MacroCall(ast) => self.lower_macro_call(ast)?.into(),
+            ast::Item::MacroRules(ast) => self.lower_macro_rules(ast)?.into(),
+            ast::Item::MacroDef(ast) => self.lower_macro_def(ast)?.into(),
+            ast::Item::ExternBlock(ast) => self.lower_extern_block(ast).into(),
         };
 
-        if !attrs.is_empty() {
-            for item in items.iter().flat_map(|items| &items.0) {
-                self.add_attrs((*item).into(), attrs.clone());
-            }
-        }
+        self.add_attrs(item.into(), attrs.clone());
 
-        items
+        Some(item)
     }
 
     fn add_attrs(&mut self, item: AttrOwner, attrs: RawAttrs) {
@@ -190,12 +166,10 @@ impl<'a> Ctx<'a> {
                             },
                             ast::Item(item) => {
                                 // FIXME: This triggers for macro calls in expression/pattern/type position
-                                let mod_items = self.lower_mod_item(&item, true);
+                                let mod_item = self.lower_mod_item(&item, true);
                                 let current_block = block_stack.last();
-                                if let (Some(mod_items), Some(block)) = (mod_items, current_block) {
-                                    if !mod_items.0.is_empty() {
-                                        self.data().inner_items.entry(*block).or_default().extend(mod_items.0.iter().copied());
-                                    }
+                                if let (Some(mod_item), Some(block)) = (mod_item, current_block) {
+                                        self.data().inner_items.entry(*block).or_default().push(mod_item);
                                 }
                             },
                             _ => {}
@@ -480,10 +454,7 @@ impl<'a> Ctx<'a> {
                 items: module
                     .item_list()
                     .map(|list| {
-                        list.items()
-                            .flat_map(|item| self.lower_mod_item(&item, false))
-                            .flat_map(|items| items.0)
-                            .collect()
+                        list.items().flat_map(|item| self.lower_mod_item(&item, false)).collect()
                     })
                     .unwrap_or_else(|| {
                         cov_mark::hit!(name_res_works_for_broken_modules);
@@ -561,30 +532,13 @@ impl<'a> Ctx<'a> {
         Some(id(self.data().impls.alloc(res)))
     }
 
-    fn lower_use(&mut self, use_item: &ast::Use) -> Vec<FileItemTreeId<Import>> {
+    fn lower_use(&mut self, use_item: &ast::Use) -> Option<FileItemTreeId<Import>> {
         let visibility = self.lower_visibility(use_item);
         let ast_id = self.source_ast_id_map.ast_id(use_item);
+        let (use_tree, _) = lower_use_tree(self.db, &self.hygiene, use_item.use_tree()?)?;
 
-        // Every use item can expand to many `Import`s.
-        let mut imports = Vec::new();
-        let tree = self.tree.data_mut();
-        ModPath::expand_use_item(
-            self.db,
-            InFile::new(self.file, use_item.clone()),
-            &self.hygiene,
-            |path, _use_tree, is_glob, alias| {
-                imports.push(id(tree.imports.alloc(Import {
-                    path: Interned::new(path),
-                    alias,
-                    visibility,
-                    is_glob,
-                    ast_id,
-                    index: imports.len(),
-                })));
-            },
-        );
-
-        imports
+        let res = Import { visibility, ast_id, use_tree };
+        Some(id(self.data().imports.alloc(res)))
     }
 
     fn lower_extern_crate(
@@ -884,3 +838,81 @@ fn lower_abi(abi: ast::Abi) -> Interned<str> {
         }
     }
 }
+
+struct UseTreeLowering<'a> {
+    db: &'a dyn DefDatabase,
+    hygiene: &'a Hygiene,
+    mapping: Arena<ast::UseTree>,
+}
+
+impl UseTreeLowering<'_> {
+    fn lower_use_tree(&mut self, tree: ast::UseTree) -> Option<UseTree> {
+        if let Some(use_tree_list) = tree.use_tree_list() {
+            let prefix = match tree.path() {
+                // E.g. use something::{{{inner}}};
+                None => None,
+                // E.g. `use something::{inner}` (prefix is `None`, path is `something`)
+                // or `use something::{path::{inner::{innerer}}}` (prefix is `something::path`, path is `inner`)
+                Some(path) => {
+                    match ModPath::from_src(self.db, path, &self.hygiene) {
+                        Some(it) => Some(it),
+                        None => return None, // FIXME: report errors somewhere
+                    }
+                }
+            };
+
+            let list =
+                use_tree_list.use_trees().filter_map(|tree| self.lower_use_tree(tree)).collect();
+
+            Some(
+                self.use_tree(
+                    UseTreeKind::Prefixed { prefix: prefix.map(Interned::new), list },
+                    tree,
+                ),
+            )
+        } else {
+            let is_glob = tree.star_token().is_some();
+            let path = match tree.path() {
+                Some(path) => Some(ModPath::from_src(self.db, path, &self.hygiene)?),
+                None => None,
+            };
+            let alias = tree.rename().map(|a| {
+                a.name().map(|it| it.as_name()).map_or(ImportAlias::Underscore, ImportAlias::Alias)
+            });
+            if alias.is_some() && is_glob {
+                return None;
+            }
+
+            match (path, alias, is_glob) {
+                (path, None, true) => {
+                    if path.is_none() {
+                        cov_mark::hit!(glob_enum_group);
+                    }
+                    Some(self.use_tree(UseTreeKind::Glob { path: path.map(Interned::new) }, tree))
+                }
+                // Globs can't be renamed
+                (_, Some(_), true) | (None, None, false) => None,
+                // `bla::{ as Name}` is invalid
+                (None, Some(_), false) => None,
+                (Some(path), alias, false) => Some(
+                    self.use_tree(UseTreeKind::Single { path: Interned::new(path), alias }, tree),
+                ),
+            }
+        }
+    }
+
+    fn use_tree(&mut self, kind: UseTreeKind, ast: ast::UseTree) -> UseTree {
+        let index = self.mapping.alloc(ast);
+        UseTree { index, kind }
+    }
+}
+
+pub(super) fn lower_use_tree(
+    db: &dyn DefDatabase,
+    hygiene: &Hygiene,
+    tree: ast::UseTree,
+) -> Option<(UseTree, Arena<ast::UseTree>)> {
+    let mut lowering = UseTreeLowering { db, hygiene, mapping: Arena::new() };
+    let tree = lowering.lower_use_tree(tree)?;
+    Some((tree, lowering.mapping))
+}
diff --git a/crates/hir_def/src/item_tree/pretty.rs b/crates/hir_def/src/item_tree/pretty.rs
index 9394a5de66..53631ab192 100644
--- a/crates/hir_def/src/item_tree/pretty.rs
+++ b/crates/hir_def/src/item_tree/pretty.rs
@@ -163,21 +163,46 @@ impl<'a> Printer<'a> {
         }
     }
 
+    fn print_use_tree(&mut self, use_tree: &UseTree) {
+        match &use_tree.kind {
+            UseTreeKind::Single { path, alias } => {
+                w!(self, "{}", path);
+                if let Some(alias) = alias {
+                    w!(self, " as {}", alias);
+                }
+            }
+            UseTreeKind::Glob { path } => {
+                if let Some(path) = path {
+                    w!(self, "{}::", path);
+                }
+                w!(self, "*");
+            }
+            UseTreeKind::Prefixed { prefix, list } => {
+                if let Some(prefix) = prefix {
+                    w!(self, "{}::", prefix);
+                }
+                w!(self, "{{");
+                for (i, tree) in list.iter().enumerate() {
+                    if i != 0 {
+                        w!(self, ", ");
+                    }
+                    self.print_use_tree(tree);
+                }
+                w!(self, "}}");
+            }
+        }
+    }
+
     fn print_mod_item(&mut self, item: ModItem) {
         self.print_attrs_of(item);
 
         match item {
             ModItem::Import(it) => {
-                let Import { visibility, path, is_glob, alias, ast_id: _, index } = &self.tree[it];
+                let Import { visibility, use_tree, ast_id: _ } = &self.tree[it];
                 self.print_visibility(*visibility);
-                w!(self, "use {}", path);
-                if *is_glob {
-                    w!(self, "::*");
-                }
-                if let Some(alias) = alias {
-                    w!(self, " as {}", alias);
-                }
-                wln!(self, ";  // {}", index);
+                w!(self, "use ");
+                self.print_use_tree(use_tree);
+                wln!(self, ";");
             }
             ModItem::ExternCrate(it) => {
                 let ExternCrate { name, alias, visibility, ast_id: _ } = &self.tree[it];
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index 3ea4729080..4296c63045 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -17,6 +17,7 @@ use hir_expand::{
 };
 use hir_expand::{InFile, MacroCallLoc};
 use itertools::Itertools;
+use la_arena::Idx;
 use rustc_hash::{FxHashMap, FxHashSet};
 use syntax::ast;
 
@@ -143,7 +144,7 @@ impl PartialResolvedImport {
 
 #[derive(Clone, Debug, Eq, PartialEq)]
 enum ImportSource {
-    Import(ItemTreeId<item_tree::Import>),
+    Import { id: ItemTreeId<item_tree::Import>, use_tree: Idx<ast::UseTree> },
     ExternCrate(ItemTreeId<item_tree::ExternCrate>),
 }
 
@@ -165,20 +166,26 @@ impl Import {
         krate: CrateId,
         tree: &ItemTree,
         id: ItemTreeId<item_tree::Import>,
-    ) -> Self {
+    ) -> Vec<Self> {
         let it = &tree[id.value];
         let attrs = &tree.attrs(db, krate, ModItem::from(id.value).into());
         let visibility = &tree[it.visibility];
-        Self {
-            path: it.path.clone(),
-            alias: it.alias.clone(),
-            visibility: visibility.clone(),
-            is_glob: it.is_glob,
-            is_prelude: attrs.by_key("prelude_import").exists(),
-            is_extern_crate: false,
-            is_macro_use: false,
-            source: ImportSource::Import(id),
-        }
+        let is_prelude = attrs.by_key("prelude_import").exists();
+
+        let mut res = Vec::new();
+        it.use_tree.expand(|idx, path, is_glob, alias| {
+            res.push(Self {
+                path: Interned::new(path), // FIXME this makes little sense
+                alias,
+                visibility: visibility.clone(),
+                is_glob,
+                is_prelude,
+                is_extern_crate: false,
+                is_macro_use: false,
+                source: ImportSource::Import { id, use_tree: idx },
+            });
+        });
+        res
     }
 
     fn from_extern_crate(
@@ -1130,11 +1137,8 @@ impl DefCollector<'_> {
         }
 
         for directive in &self.unresolved_imports {
-            if let ImportSource::Import(import) = &directive.import.source {
-                let item_tree = import.item_tree(self.db);
-                let import_data = &item_tree[import.value];
-
-                match (import_data.path.segments().first(), &import_data.path.kind) {
+            if let ImportSource::Import { id: import, use_tree } = &directive.import.source {
+                match (directive.import.path.segments().first(), &directive.import.path.kind) {
                     (Some(krate), PathKind::Plain) | (Some(krate), PathKind::Abs) => {
                         if diagnosed_extern_crates.contains(krate) {
                             continue;
@@ -1145,8 +1149,8 @@ impl DefCollector<'_> {
 
                 self.def_map.diagnostics.push(DefDiagnostic::unresolved_import(
                     directive.module_id,
-                    InFile::new(import.file_id(), import_data.ast_id),
-                    import_data.index,
+                    *import,
+                    *use_tree,
                 ));
             }
         }
@@ -1222,16 +1226,20 @@ impl ModCollector<'_, '_> {
             match item {
                 ModItem::Mod(m) => self.collect_module(&self.item_tree[m], &attrs),
                 ModItem::Import(import_id) => {
-                    self.def_collector.unresolved_imports.push(ImportDirective {
-                        module_id: self.module_id,
-                        import: Import::from_use(
-                            self.def_collector.db,
-                            krate,
-                            &self.item_tree,
-                            ItemTreeId::new(self.file_id, import_id),
-                        ),
-                        status: PartialResolvedImport::Unresolved,
-                    })
+                    let module_id = self.module_id;
+                    let imports = Import::from_use(
+                        self.def_collector.db,
+                        krate,
+                        &self.item_tree,
+                        ItemTreeId::new(self.file_id, import_id),
+                    );
+                    self.def_collector.unresolved_imports.extend(imports.into_iter().map(
+                        |import| ImportDirective {
+                            module_id,
+                            import,
+                            status: PartialResolvedImport::Unresolved,
+                        },
+                    ));
                 }
                 ModItem::ExternCrate(import_id) => {
                     self.def_collector.unresolved_imports.push(ImportDirective {
diff --git a/crates/hir_def/src/nameres/diagnostics.rs b/crates/hir_def/src/nameres/diagnostics.rs
index 8f2f0ff9f5..57c36c3c6c 100644
--- a/crates/hir_def/src/nameres/diagnostics.rs
+++ b/crates/hir_def/src/nameres/diagnostics.rs
@@ -2,9 +2,15 @@
 
 use cfg::{CfgExpr, CfgOptions};
 use hir_expand::MacroCallKind;
+use la_arena::Idx;
 use syntax::ast;
 
-use crate::{nameres::LocalModuleId, path::ModPath, AstId};
+use crate::{
+    item_tree::{self, ItemTreeId},
+    nameres::LocalModuleId,
+    path::ModPath,
+    AstId,
+};
 
 #[derive(Debug, PartialEq, Eq)]
 pub enum DefDiagnosticKind {
@@ -12,7 +18,7 @@ pub enum DefDiagnosticKind {
 
     UnresolvedExternCrate { ast: AstId<ast::ExternCrate> },
 
-    UnresolvedImport { ast: AstId<ast::Use>, index: usize },
+    UnresolvedImport { id: ItemTreeId<item_tree::Import>, index: Idx<ast::UseTree> },
 
     UnconfiguredCode { ast: AstId<ast::Item>, cfg: CfgExpr, opts: CfgOptions },
 
@@ -53,10 +59,10 @@ impl DefDiagnostic {
 
     pub(super) fn unresolved_import(
         container: LocalModuleId,
-        ast: AstId<ast::Use>,
-        index: usize,
+        id: ItemTreeId<item_tree::Import>,
+        index: Idx<ast::UseTree>,
     ) -> Self {
-        Self { in_module: container, kind: DefDiagnosticKind::UnresolvedImport { ast, index } }
+        Self { in_module: container, kind: DefDiagnosticKind::UnresolvedImport { id, index } }
     }
 
     pub(super) fn unconfigured_code(
diff --git a/crates/hir_def/src/path.rs b/crates/hir_def/src/path.rs
index d9ec03d2da..16440041dc 100644
--- a/crates/hir_def/src/path.rs
+++ b/crates/hir_def/src/path.rs
@@ -14,10 +14,7 @@ use hir_expand::{
 };
 use syntax::ast;
 
-use crate::{
-    type_ref::{TypeBound, TypeRef},
-    InFile,
-};
+use crate::type_ref::{TypeBound, TypeRef};
 
 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub struct ModPath {
@@ -56,8 +53,7 @@ impl Display for ImportAlias {
 
 impl ModPath {
     pub fn from_src(db: &dyn DefDatabase, path: ast::Path, hygiene: &Hygiene) -> Option<ModPath> {
-        let ctx = LowerCtx::with_hygiene(db, hygiene);
-        lower::lower_path(path, &ctx).map(|it| (*it.mod_path).clone())
+        lower::convert_path(db, None, path, hygiene)
     }
 
     pub fn from_segments(kind: PathKind, segments: impl IntoIterator<Item = Name>) -> ModPath {
@@ -70,18 +66,6 @@ impl ModPath {
         ModPath { kind, segments: Vec::new() }
     }
 
-    /// Calls `cb` with all paths, represented by this use item.
-    pub fn expand_use_item(
-        db: &dyn DefDatabase,
-        item_src: InFile<ast::Use>,
-        hygiene: &Hygiene,
-        mut cb: impl FnMut(ModPath, &ast::UseTree, /* is_glob */ bool, Option<ImportAlias>),
-    ) {
-        if let Some(tree) = item_src.value.use_tree() {
-            lower::lower_use_tree(db, None, tree, hygiene, &mut cb);
-        }
-    }
-
     pub fn segments(&self) -> &[Name] {
         &self.segments
     }
diff --git a/crates/hir_def/src/path/lower.rs b/crates/hir_def/src/path/lower.rs
index 54ede73939..f6220aa928 100644
--- a/crates/hir_def/src/path/lower.rs
+++ b/crates/hir_def/src/path/lower.rs
@@ -15,7 +15,7 @@ use crate::{
     type_ref::{LifetimeRef, TypeBound, TypeRef},
 };
 
-pub(super) use lower_use::lower_use_tree;
+pub(super) use lower_use::convert_path;
 
 /// Converts an `ast::Path` to `Path`. Works with use trees.
 /// It correctly handles `$crate` based path from macro call.
diff --git a/crates/hir_def/src/path/lower/lower_use.rs b/crates/hir_def/src/path/lower/lower_use.rs
index ee80e3df3c..0ee406f63a 100644
--- a/crates/hir_def/src/path/lower/lower_use.rs
+++ b/crates/hir_def/src/path/lower/lower_use.rs
@@ -4,68 +4,15 @@
 use std::iter;
 
 use either::Either;
-use hir_expand::{hygiene::Hygiene, name::AsName};
-use syntax::ast::{self, NameOwner};
+use hir_expand::hygiene::Hygiene;
+use syntax::{ast, AstNode};
 
 use crate::{
     db::DefDatabase,
-    path::{ImportAlias, ModPath, PathKind},
+    path::{ModPath, PathKind},
 };
 
-pub(crate) fn lower_use_tree(
-    db: &dyn DefDatabase,
-    prefix: Option<ModPath>,
-    tree: ast::UseTree,
-    hygiene: &Hygiene,
-    cb: &mut dyn FnMut(ModPath, &ast::UseTree, bool, Option<ImportAlias>),
-) {
-    if let Some(use_tree_list) = tree.use_tree_list() {
-        let prefix = match tree.path() {
-            // E.g. use something::{{{inner}}};
-            None => prefix,
-            // E.g. `use something::{inner}` (prefix is `None`, path is `something`)
-            // or `use something::{path::{inner::{innerer}}}` (prefix is `something::path`, path is `inner`)
-            Some(path) => match convert_path(db, prefix, path, hygiene) {
-                Some(it) => Some(it),
-                None => return, // FIXME: report errors somewhere
-            },
-        };
-        for child_tree in use_tree_list.use_trees() {
-            lower_use_tree(db, prefix.clone(), child_tree, hygiene, cb);
-        }
-    } else {
-        let alias = tree.rename().map(|a| {
-            a.name().map(|it| it.as_name()).map_or(ImportAlias::Underscore, ImportAlias::Alias)
-        });
-        let is_glob = tree.star_token().is_some();
-        if let Some(ast_path) = tree.path() {
-            // Handle self in a path.
-            // E.g. `use something::{self, <...>}`
-            if ast_path.qualifier().is_none() {
-                if let Some(segment) = ast_path.segment() {
-                    if segment.kind() == Some(ast::PathSegmentKind::SelfKw) {
-                        if let Some(prefix) = prefix {
-                            cb(prefix, &tree, false, alias);
-                            return;
-                        }
-                    }
-                }
-            }
-            if let Some(path) = convert_path(db, prefix, ast_path, hygiene) {
-                cb(path, &tree, is_glob, alias)
-            }
-        // FIXME: report errors somewhere
-        // We get here if we do
-        } else if is_glob {
-            cov_mark::hit!(glob_enum_group);
-            if let Some(prefix) = prefix {
-                cb(prefix, &tree, is_glob, None)
-            }
-        }
-    }
-}
-
-fn convert_path(
+pub(crate) fn convert_path(
     db: &dyn DefDatabase,
     prefix: Option<ModPath>,
     path: ast::Path,
@@ -78,7 +25,7 @@ fn convert_path(
     };
 
     let segment = path.segment()?;
-    let res = match segment.kind()? {
+    let mut mod_path = match segment.kind()? {
         ast::PathSegmentKind::Name(name_ref) => {
             match hygiene.name_ref_to_name(db.upcast(), name_ref) {
                 Either::Left(name) => {
@@ -125,5 +72,18 @@ fn convert_path(
             return None;
         }
     };
-    Some(res)
+
+    // handle local_inner_macros :
+    // Basically, even in rustc it is quite hacky:
+    // https://github.com/rust-lang/rust/blob/614f273e9388ddd7804d5cbc80b8865068a3744e/src/librustc_resolve/macros.rs#L456
+    // We follow what it did anyway :)
+    if mod_path.segments.len() == 1 && mod_path.kind == PathKind::Plain {
+        if let Some(_macro_call) = path.syntax().parent().and_then(ast::MacroCall::cast) {
+            if let Some(crate_id) = hygiene.local_inner_macros(db.upcast(), path) {
+                mod_path.kind = PathKind::DollarCrate(crate_id);
+            }
+        }
+    }
+
+    Some(mod_path)
 }
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index dcac7c76d2..6cf5810fa4 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -311,6 +311,7 @@ mod tests {
     ///  * a diagnostic is produced
     ///  * the first diagnostic fix trigger range touches the input cursor position
     ///  * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied
+    #[track_caller]
     pub(crate) fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) {
         check_nth_fix(0, ra_fixture_before, ra_fixture_after);
     }
@@ -325,6 +326,7 @@ mod tests {
         }
     }
 
+    #[track_caller]
     fn check_nth_fix(nth: usize, ra_fixture_before: &str, ra_fixture_after: &str) {
         let after = trim_indent(ra_fixture_after);
 


Test patch:
diff --git a/crates/hir_def/src/item_tree/tests.rs b/crates/hir_def/src/item_tree/tests.rs
index 6407871b50..20773aa699 100644
--- a/crates/hir_def/src/item_tree/tests.rs
+++ b/crates/hir_def/src/item_tree/tests.rs
@@ -26,6 +26,8 @@ use globs::*;
 
 /// docs on import
 use crate::{A, B};
+
+use a::{c, d::{e}};
         "#,
         expect![[r##"
             #![doc = " file comment"]  // AttrId { is_doc_comment: true, ast_index: 0 }
@@ -36,19 +38,14 @@ use crate::{A, B};
 
             pub(super) extern crate bli;
 
-            pub use crate::path::nested;  // 0
-
-            pub use crate::path::items as renamed;  // 1
+            pub use crate::path::{nested, items as renamed, Trait as _};
 
-            pub use crate::path::Trait as _;  // 2
-
-            pub(self) use globs::*;  // 0
+            pub(self) use globs::*;
 
             #[doc = " docs on import"]  // AttrId { is_doc_comment: true, ast_index: 0 }
-            pub(self) use crate::A;  // 0
+            pub(self) use crate::{A, B};
 
-            #[doc = " docs on import"]  // AttrId { is_doc_comment: true, ast_index: 0 }
-            pub(self) use crate::B;  // 1
+            pub(self) use a::{c, d::{e}};
         "##]],
     );
 }
@@ -218,7 +215,7 @@ mod outline;
             #[doc = " outer"]  // AttrId { is_doc_comment: true, ast_index: 0 }
             #[doc = " inner"]  // AttrId { is_doc_comment: true, ast_index: 1 }
             pub(self) mod inline {
-                pub(self) use super::*;  // 0
+                pub(self) use super::*;
 
                 // flags = 0x2
                 pub(self) fn fn_in_module() -> ();
diff --git a/crates/hir_def/src/test_db.rs b/crates/hir_def/src/test_db.rs
index 6c357c915b..a9c1e13e2f 100644
--- a/crates/hir_def/src/test_db.rs
+++ b/crates/hir_def/src/test_db.rs
@@ -278,9 +278,11 @@ impl TestDB {
                         let node = ast.to_node(self.upcast());
                         (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedExternCrate")
                     }
-                    DefDiagnosticKind::UnresolvedImport { ast, .. } => {
-                        let node = ast.to_node(self.upcast());
-                        (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedImport")
+                    DefDiagnosticKind::UnresolvedImport { id, .. } => {
+                        let item_tree = id.item_tree(self.upcast());
+                        let import = &item_tree[id.value];
+                        let node = InFile::new(id.file_id(), import.ast_id).to_node(self.upcast());
+                        (InFile::new(id.file_id(), node.syntax().clone()), "UnresolvedImport")
                     }
                     DefDiagnosticKind::UnconfiguredCode { ast, .. } => {
                         let node = ast.to_node(self.upcast());