mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-12 05:08:52 +00:00
fix: Fix path qualified auto-importing completions not working with re-exports
Prior to this commit we used to generate import paths, then zipped them with the existing qualifier to check if they agree on the path to import. This is brittle when re-exports come into play causing items to have multiple applicable paths that refer to them. This commit instead rewrites this logic by generating the import path for the qualifier, verifying that the rest of the qualifier resolves and then doing a final lookup on that resolution result for the final segment instead.
This commit is contained in:
parent
65c8d1242b
commit
ada5f2059c
9 changed files with 224 additions and 137 deletions
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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![""],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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_))
|
||||
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,
|
||||
}
|
||||
AssocItemContainer::Impl(impl_) => {
|
||||
(ItemInNs::from(ModuleDef::from(impl_.self_ty(db).as_adt()?)), None)
|
||||
}
|
||||
},
|
||||
None => (original_item, None),
|
||||
}
|
||||
|
||||
match adjusted_resolved_qualifier {
|
||||
ItemInNs::Types(def) => def,
|
||||
_ => return 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;
|
||||
}
|
||||
(false, Some(trait_to_import)) if scope_filter(trait_to_import) => {
|
||||
LocatedImport::new(mod_path(trait_to_import)?, trait_to_import, original_item)
|
||||
}
|
||||
(true, None) if scope_filter(original_item_candidate) => {
|
||||
LocatedImport::new(import_path_candidate, original_item_candidate, original_item)
|
||||
}
|
||||
(false, None) if scope_filter(segment_import) => {
|
||||
LocatedImport::new(mod_path(segment_import)?, segment_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))
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue