about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/lib.rs6
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/semantics.rs19
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs10
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs42
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs213
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/items_locator.rs59
9 files changed, 221 insertions, 134 deletions
diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs
index 3bc2eee1e7c..dfc91c73433 100644
--- a/src/tools/rust-analyzer/crates/hir/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs
@@ -3105,10 +3105,10 @@ impl From<ModuleDef> for ItemInNs {
 }
 
 impl ItemInNs {
-    pub fn as_module_def(self) -> Option<ModuleDef> {
+    pub fn into_module_def(self) -> ModuleDef {
         match self {
-            ItemInNs::Types(id) | ItemInNs::Values(id) => Some(id),
-            ItemInNs::Macros(_) => None,
+            ItemInNs::Types(id) | ItemInNs::Values(id) => id,
+            ItemInNs::Macros(id) => ModuleDef::Macro(id),
         }
     }
 
diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs
index f030c1862af..c576d07a0e2 100644
--- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs
@@ -39,8 +39,8 @@ use stdx::TupleExt;
 use syntax::{
     algo::skip_trivia_token,
     ast::{self, HasAttrs as _, HasGenericParams},
-    AstNode, AstToken, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange,
-    TextSize,
+    AstNode, AstToken, Direction, SmolStr, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken,
+    TextRange, TextSize,
 };
 use triomphe::Arc;
 
@@ -1540,6 +1540,21 @@ impl<'db> SemanticsImpl<'db> {
         Some(items.iter_items().map(|(item, _)| item.into()))
     }
 
+    pub fn resolve_mod_path_relative(
+        &self,
+        to: Module,
+        segments: impl IntoIterator<Item = SmolStr>,
+    ) -> Option<impl Iterator<Item = ItemInNs>> {
+        let items = to.id.resolver(self.db.upcast()).resolve_module_path_in_items(
+            self.db.upcast(),
+            &ModPath::from_segments(
+                hir_def::path::PathKind::Plain,
+                segments.into_iter().map(|it| Name::new(&it, SyntaxContextId::ROOT)),
+            ),
+        );
+        Some(items.iter_items().map(|(item, _)| item.into()))
+    }
+
     fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option<VariantId> {
         self.analyze(record_lit.syntax())?.resolve_variant(self.db, record_lit)
     }
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs
index 14518c4d2cc..c3600af5a6c 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs
@@ -86,7 +86,7 @@ fn item_for_path_search(db: &dyn HirDatabase, item: ItemInNs) -> Option<ItemInNs
 }
 
 fn item_as_assoc(db: &dyn HirDatabase, item: ItemInNs) -> Option<AssocItem> {
-    item.as_module_def().and_then(|module_def| module_def.as_assoc_item(db))
+    item.into_module_def().as_assoc_item(db)
 }
 
 #[cfg(test)]
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs
index ac88861fe4f..849b8a42c69 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs
@@ -51,7 +51,7 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
     let candidate = import_assets.import_candidate();
     let qualify_candidate = match syntax_under_caret.clone() {
         NodeOrToken::Node(syntax_under_caret) => match candidate {
-            ImportCandidate::Path(candidate) if candidate.qualifier.is_some() => {
+            ImportCandidate::Path(candidate) if !candidate.qualifier.is_empty() => {
                 cov_mark::hit!(qualify_path_qualifier_start);
                 let path = ast::Path::cast(syntax_under_caret)?;
                 let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?);
@@ -219,11 +219,9 @@ fn find_trait_method(
 }
 
 fn item_as_trait(db: &RootDatabase, item: hir::ItemInNs) -> Option<hir::Trait> {
-    let item_module_def = item.as_module_def()?;
-
-    match item_module_def {
+    match item.into_module_def() {
         hir::ModuleDef::Trait(trait_) => Some(trait_),
-        _ => item_module_def.as_assoc_item(db)?.container_trait(db),
+        item_module_def => item_module_def.as_assoc_item(db)?.container_trait(db),
     }
 }
 
@@ -247,7 +245,7 @@ fn label(
     let import_path = &import.import_path;
 
     match candidate {
-        ImportCandidate::Path(candidate) if candidate.qualifier.is_none() => {
+        ImportCandidate::Path(candidate) if candidate.qualifier.is_empty() => {
             format!("Qualify as `{}`", import_path.display(db, edition))
         }
         _ => format!("Qualify with `{}`", import_path.display(db, edition)),
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
index 2dec876215c..31e828eae27 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -78,7 +78,7 @@ pub(crate) fn replace_derive_with_manual_impl(
         NameToImport::exact_case_sensitive(path.segments().last()?.to_string()),
         items_locator::AssocSearchMode::Exclude,
     )
-    .filter_map(|item| match item.as_module_def()? {
+    .filter_map(|item| match item.into_module_def() {
         ModuleDef::Trait(trait_) => Some(trait_),
         _ => None,
     })
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type.rs
index 2d918a5b1c1..658600cd2d0 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type.rs
@@ -198,7 +198,7 @@ fn wrapper_alias(
     );
 
     ctx.sema.resolve_mod_path(ret_type.syntax(), &wrapper_path).and_then(|def| {
-        def.filter_map(|def| match def.as_module_def()? {
+        def.filter_map(|def| match def.into_module_def() {
             hir::ModuleDef::TypeAlias(alias) => {
                 let enum_ty = alias.ty(ctx.db()).as_adt()?.as_enum()?;
                 (&enum_ty == core_wrapper).then_some(alias)
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs
index 4b949e0d657..f31b0d4910a 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs
@@ -1720,3 +1720,45 @@ fn function() {
         "#]],
     );
 }
+
+#[test]
+fn intrinsics() {
+    check(
+        r#"
+    //- /core.rs crate:core
+    pub mod intrinsics {
+        extern "rust-intrinsic" {
+            pub fn transmute<Src, Dst>(src: Src) -> Dst;
+        }
+    }
+    pub mod mem {
+        pub use crate::intrinsics::transmute;
+    }
+    //- /main.rs crate:main deps:core
+    fn function() {
+            transmute$0
+    }
+    "#,
+        expect![[r#"
+                fn transmute(…) (use core::mem::transmute) unsafe fn(Src) -> Dst
+            "#]],
+    );
+    check(
+        r#"
+//- /core.rs crate:core
+pub mod intrinsics {
+    extern "rust-intrinsic" {
+        pub fn transmute<Src, Dst>(src: Src) -> Dst;
+    }
+}
+pub mod mem {
+    pub use crate::intrinsics::transmute;
+}
+//- /main.rs crate:main deps:core
+fn function() {
+        mem::transmute$0
+}
+"#,
+        expect![""],
+    );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs
index 82a182806a4..dab36bf20b9 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs
@@ -2,10 +2,10 @@
 
 use hir::{
     db::HirDatabase, AsAssocItem, AssocItem, AssocItemContainer, Crate, HasCrate, ImportPathConfig,
-    ItemInNs, ModPath, Module, ModuleDef, Name, PathResolution, PrefixKind, ScopeDef, Semantics,
+    ItemInNs, ModPath, Module, ModuleDef, PathResolution, PrefixKind, ScopeDef, Semantics,
     SemanticsScope, Trait, TyFingerprint, Type,
 };
-use itertools::{EitherOrBoth, Itertools};
+use itertools::Itertools;
 use rustc_hash::{FxHashMap, FxHashSet};
 use syntax::{
     ast::{self, make, HasName},
@@ -13,7 +13,6 @@ use syntax::{
 };
 
 use crate::{
-    helpers::item_name,
     items_locator::{self, AssocSearchMode, DEFAULT_QUERY_SEARCH_LIMIT},
     FxIndexSet, RootDatabase,
 };
@@ -52,7 +51,7 @@ pub struct TraitImportCandidate {
 #[derive(Debug)]
 pub struct PathImportCandidate {
     /// Optional qualifier before name.
-    pub qualifier: Option<Vec<SmolStr>>,
+    pub qualifier: Vec<SmolStr>,
     /// The name the item (struct, trait, enum, etc.) should have.
     pub name: NameToImport,
 }
@@ -264,7 +263,6 @@ impl ImportAssets {
             Some(it) => it,
             None => return <FxIndexSet<_>>::default().into_iter(),
         };
-
         let krate = self.module_with_candidate.krate();
         let scope_definitions = self.scope_definitions(sema);
         let mod_path = |item| {
@@ -279,11 +277,14 @@ impl ImportAssets {
         };
 
         match &self.import_candidate {
-            ImportCandidate::Path(path_candidate) => {
-                path_applicable_imports(sema, krate, path_candidate, mod_path, |item_to_import| {
-                    !scope_definitions.contains(&ScopeDef::from(item_to_import))
-                })
-            }
+            ImportCandidate::Path(path_candidate) => path_applicable_imports(
+                sema,
+                &scope,
+                krate,
+                path_candidate,
+                mod_path,
+                |item_to_import| !scope_definitions.contains(&ScopeDef::from(item_to_import)),
+            ),
             ImportCandidate::TraitAssocItem(trait_candidate)
             | ImportCandidate::TraitMethod(trait_candidate) => trait_applicable_items(
                 sema,
@@ -315,6 +316,7 @@ impl ImportAssets {
 
 fn path_applicable_imports(
     sema: &Semantics<'_, RootDatabase>,
+    scope: &SemanticsScope<'_>,
     current_crate: Crate,
     path_candidate: &PathImportCandidate,
     mod_path: impl Fn(ItemInNs) -> Option<ModPath> + Copy,
@@ -322,8 +324,8 @@ fn path_applicable_imports(
 ) -> FxIndexSet<LocatedImport> {
     let _p = tracing::info_span!("ImportAssets::path_applicable_imports").entered();
 
-    match &path_candidate.qualifier {
-        None => {
+    match &*path_candidate.qualifier {
+        [] => {
             items_locator::items_with_name(
                 sema,
                 current_crate,
@@ -348,89 +350,107 @@ fn path_applicable_imports(
             .take(DEFAULT_QUERY_SEARCH_LIMIT.inner())
             .collect()
         }
-        Some(qualifier) => items_locator::items_with_name(
+        [first_qsegment, qualifier_rest @ ..] => items_locator::items_with_name(
             sema,
             current_crate,
-            path_candidate.name.clone(),
-            AssocSearchMode::Include,
+            NameToImport::Exact(first_qsegment.to_string(), true),
+            AssocSearchMode::Exclude,
         )
-        .filter_map(|item| import_for_item(sema.db, mod_path, qualifier, item, scope_filter))
+        .filter_map(|item| {
+            import_for_item(
+                sema,
+                scope,
+                mod_path,
+                &path_candidate.name,
+                item,
+                qualifier_rest,
+                scope_filter,
+            )
+        })
         .take(DEFAULT_QUERY_SEARCH_LIMIT.inner())
         .collect(),
     }
 }
 
 fn import_for_item(
-    db: &RootDatabase,
+    sema: &Semantics<'_, RootDatabase>,
+    scope: &SemanticsScope<'_>,
     mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
+    candidate: &NameToImport,
+    resolved_qualifier: ItemInNs,
     unresolved_qualifier: &[SmolStr],
-    original_item: ItemInNs,
     scope_filter: impl Fn(ItemInNs) -> bool,
 ) -> Option<LocatedImport> {
     let _p = tracing::info_span!("ImportAssets::import_for_item").entered();
-    let [first_segment, ..] = unresolved_qualifier else { return None };
-
-    let item_as_assoc = item_as_assoc(db, original_item);
 
-    let (original_item_candidate, trait_item_to_import) = match item_as_assoc {
-        Some(assoc_item) => match assoc_item.container(db) {
-            AssocItemContainer::Trait(trait_) => {
-                let trait_ = ItemInNs::from(ModuleDef::from(trait_));
-                (trait_, Some(trait_))
-            }
-            AssocItemContainer::Impl(impl_) => {
-                (ItemInNs::from(ModuleDef::from(impl_.self_ty(db).as_adt()?)), None)
+    let qualifier = {
+        let mut adjusted_resolved_qualifier = resolved_qualifier;
+        if !unresolved_qualifier.is_empty() {
+            match resolved_qualifier {
+                ItemInNs::Types(ModuleDef::Module(module)) => {
+                    adjusted_resolved_qualifier = sema
+                        .resolve_mod_path_relative(module, unresolved_qualifier.iter().cloned())?
+                        .next()?;
+                }
+                // can't resolve multiple segments for non-module item path bases
+                _ => return None,
             }
-        },
-        None => (original_item, None),
-    };
-    let import_path_candidate = mod_path(original_item_candidate)?;
-
-    let mut import_path_candidate_segments = import_path_candidate.segments().iter().rev();
-    let predicate = |it: EitherOrBoth<&SmolStr, &Name>| match it {
-        // segments match, check next one
-        EitherOrBoth::Both(a, b) if b.as_str() == &**a => None,
-        // segments mismatch / qualifier is longer than the path, bail out
-        EitherOrBoth::Both(..) | EitherOrBoth::Left(_) => Some(false),
-        // all segments match and we have exhausted the qualifier, proceed
-        EitherOrBoth::Right(_) => Some(true),
-    };
-    if item_as_assoc.is_none() {
-        let item_name = item_name(db, original_item)?;
-        let last_segment = import_path_candidate_segments.next()?;
-        if *last_segment != item_name {
-            return None;
         }
-    }
-    let ends_with = unresolved_qualifier
-        .iter()
-        .rev()
-        .zip_longest(import_path_candidate_segments)
-        .find_map(predicate)
-        .unwrap_or(true);
-    if !ends_with {
-        return None;
-    }
 
-    let segment_import = find_import_for_segment(db, original_item_candidate, first_segment)?;
-
-    Some(match (segment_import == original_item_candidate, trait_item_to_import) {
-        (true, Some(_)) => {
-            // FIXME we should be able to import both the trait and the segment,
-            // but it's unclear what to do with overlapping edits (merge imports?)
-            // especially in case of lazy completion edit resolutions.
-            return None;
+        match adjusted_resolved_qualifier {
+            ItemInNs::Types(def) => def,
+            _ => return None,
         }
-        (false, Some(trait_to_import)) if scope_filter(trait_to_import) => {
-            LocatedImport::new(mod_path(trait_to_import)?, trait_to_import, original_item)
+    };
+    let import_path_candidate = mod_path(resolved_qualifier)?;
+    let ty = match qualifier {
+        ModuleDef::Module(module) => {
+            return items_locator::items_with_name_in_module(
+                sema,
+                module,
+                candidate.clone(),
+                AssocSearchMode::Exclude,
+            )
+            .find(|&it| scope_filter(it))
+            .map(|item| LocatedImport::new(import_path_candidate, resolved_qualifier, item))
         }
-        (true, None) if scope_filter(original_item_candidate) => {
-            LocatedImport::new(import_path_candidate, original_item_candidate, original_item)
+        // FIXME
+        ModuleDef::Trait(_) => return None,
+        // FIXME
+        ModuleDef::TraitAlias(_) => return None,
+        ModuleDef::TypeAlias(alias) => alias.ty(sema.db),
+        ModuleDef::BuiltinType(builtin) => builtin.ty(sema.db),
+        ModuleDef::Adt(adt) => adt.ty(sema.db),
+        _ => return None,
+    };
+    ty.iterate_path_candidates(sema.db, scope, &FxHashSet::default(), None, None, |assoc| {
+        // FIXME: Support extra trait imports
+        if assoc.container_or_implemented_trait(sema.db).is_some() {
+            return None;
         }
-        (false, None) if scope_filter(segment_import) => {
-            LocatedImport::new(mod_path(segment_import)?, segment_import, original_item)
+        let name = assoc.name(sema.db)?;
+        let is_match = match candidate {
+            NameToImport::Prefix(text, true) => name.as_str().starts_with(text),
+            NameToImport::Prefix(text, false) => {
+                name.as_str().chars().zip(text.chars()).all(|(name_char, candidate_char)| {
+                    name_char.eq_ignore_ascii_case(&candidate_char)
+                })
+            }
+            NameToImport::Exact(text, true) => name.as_str() == text,
+            NameToImport::Exact(text, false) => name.as_str().eq_ignore_ascii_case(text),
+            NameToImport::Fuzzy(text, true) => text.chars().all(|c| name.as_str().contains(c)),
+            NameToImport::Fuzzy(text, false) => text
+                .chars()
+                .all(|c| name.as_str().chars().any(|name_char| name_char.eq_ignore_ascii_case(&c))),
+        };
+        if !is_match {
+            return None;
         }
-        _ => return None,
+        Some(LocatedImport::new(
+            import_path_candidate.clone(),
+            resolved_qualifier,
+            assoc_to_item(assoc),
+        ))
     })
 }
 
@@ -453,45 +473,6 @@ fn item_for_path_search_assoc(db: &RootDatabase, assoc_item: AssocItem) -> Optio
     })
 }
 
-fn find_import_for_segment(
-    db: &RootDatabase,
-    original_item: ItemInNs,
-    unresolved_first_segment: &str,
-) -> Option<ItemInNs> {
-    let segment_is_name = item_name(db, original_item)
-        .map(|name| name.eq_ident(unresolved_first_segment))
-        .unwrap_or(false);
-
-    Some(if segment_is_name {
-        original_item
-    } else {
-        let matching_module =
-            module_with_segment_name(db, unresolved_first_segment, original_item)?;
-        ItemInNs::from(ModuleDef::from(matching_module))
-    })
-}
-
-fn module_with_segment_name(
-    db: &RootDatabase,
-    segment_name: &str,
-    candidate: ItemInNs,
-) -> Option<Module> {
-    let mut current_module = match candidate {
-        ItemInNs::Types(module_def_id) => module_def_id.module(db),
-        ItemInNs::Values(module_def_id) => module_def_id.module(db),
-        ItemInNs::Macros(macro_def_id) => ModuleDef::from(macro_def_id).module(db),
-    };
-    while let Some(module) = current_module {
-        if let Some(module_name) = module.name(db) {
-            if module_name.eq_ident(segment_name) {
-                return Some(module);
-            }
-        }
-        current_module = module.parent(db);
-    }
-    None
-}
-
 fn trait_applicable_items(
     sema: &Semantics<'_, RootDatabase>,
     current_crate: Crate,
@@ -703,7 +684,7 @@ impl ImportCandidate {
             return None;
         }
         Some(ImportCandidate::Path(PathImportCandidate {
-            qualifier: None,
+            qualifier: vec![],
             name: NameToImport::exact_case_sensitive(name.to_string()),
         }))
     }
@@ -730,7 +711,7 @@ fn path_import_candidate(
                         .segments()
                         .map(|seg| seg.name_ref().map(|name| SmolStr::new(name.text())))
                         .collect::<Option<Vec<_>>>()?;
-                    ImportCandidate::Path(PathImportCandidate { qualifier: Some(qualifier), name })
+                    ImportCandidate::Path(PathImportCandidate { qualifier, name })
                 } else {
                     return None;
                 }
@@ -754,10 +735,10 @@ fn path_import_candidate(
             }
             Some(_) => return None,
         },
-        None => ImportCandidate::Path(PathImportCandidate { qualifier: None, name }),
+        None => ImportCandidate::Path(PathImportCandidate { qualifier: vec![], name }),
     })
 }
 
 fn item_as_assoc(db: &RootDatabase, item: ItemInNs) -> Option<AssocItem> {
-    item.as_module_def().and_then(|module_def| module_def.as_assoc_item(db))
+    item.into_module_def().as_assoc_item(db)
 }
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/items_locator.rs b/src/tools/rust-analyzer/crates/ide-db/src/items_locator.rs
index 47549a1d008..7f66ea0c103 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/items_locator.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/items_locator.rs
@@ -3,10 +3,14 @@
 //! The main reason for this module to exist is the fact that project's items and dependencies' items
 //! are located in different caches, with different APIs.
 use either::Either;
-use hir::{import_map, Crate, ItemInNs, Semantics};
+use hir::{import_map, Crate, ItemInNs, Module, Semantics};
 use limit::Limit;
 
-use crate::{imports::import_assets::NameToImport, symbol_index, RootDatabase};
+use crate::{
+    imports::import_assets::NameToImport,
+    symbol_index::{self, SymbolsDatabase as _},
+    RootDatabase,
+};
 
 /// A value to use, when uncertain which limit to pick.
 pub static DEFAULT_QUERY_SEARCH_LIMIT: Limit = Limit::new(100);
@@ -20,8 +24,7 @@ pub fn items_with_name<'a>(
     name: NameToImport,
     assoc_item_search: AssocSearchMode,
 ) -> impl Iterator<Item = ItemInNs> + 'a {
-    let krate_name = krate.display_name(sema.db).map(|name| name.to_string());
-    let _p = tracing::info_span!("items_with_name", name = name.text(), assoc_item_search = ?assoc_item_search, crate = ?krate_name)
+    let _p = tracing::info_span!("items_with_name", name = name.text(), assoc_item_search = ?assoc_item_search, crate = ?krate.display_name(sema.db).map(|name| name.to_string()))
         .entered();
 
     let prefix = matches!(name, NameToImport::Prefix(..));
@@ -66,6 +69,54 @@ pub fn items_with_name<'a>(
     find_items(sema, krate, local_query, external_query)
 }
 
+/// Searches for importable items with the given name in the crate and its dependencies.
+pub fn items_with_name_in_module<'a>(
+    sema: &'a Semantics<'_, RootDatabase>,
+    module: Module,
+    name: NameToImport,
+    assoc_item_search: AssocSearchMode,
+) -> impl Iterator<Item = ItemInNs> + 'a {
+    let _p = tracing::info_span!("items_with_name_in", name = name.text(), assoc_item_search = ?assoc_item_search, ?module)
+        .entered();
+
+    let prefix = matches!(name, NameToImport::Prefix(..));
+    let local_query = match name {
+        NameToImport::Prefix(exact_name, case_sensitive)
+        | NameToImport::Exact(exact_name, case_sensitive) => {
+            let mut local_query = symbol_index::Query::new(exact_name.clone());
+            local_query.assoc_search_mode(assoc_item_search);
+            if prefix {
+                local_query.prefix();
+            } else {
+                local_query.exact();
+            }
+            if case_sensitive {
+                local_query.case_sensitive();
+            }
+            local_query
+        }
+        NameToImport::Fuzzy(fuzzy_search_string, case_sensitive) => {
+            let mut local_query = symbol_index::Query::new(fuzzy_search_string.clone());
+            local_query.fuzzy();
+            local_query.assoc_search_mode(assoc_item_search);
+
+            if case_sensitive {
+                local_query.case_sensitive();
+            }
+
+            local_query
+        }
+    };
+    let mut local_results = Vec::new();
+    local_query.search(&[sema.db.module_symbols(module)], |local_candidate| {
+        local_results.push(match local_candidate.def {
+            hir::ModuleDef::Macro(macro_def) => ItemInNs::Macros(macro_def),
+            def => ItemInNs::from(def),
+        })
+    });
+    local_results.into_iter()
+}
+
 fn find_items<'a>(
     sema: &'a Semantics<'_, RootDatabase>,
     krate: Crate,