Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-db/src/imports/import_assets.rs')
-rw-r--r--crates/ide-db/src/imports/import_assets.rs213
1 files changed, 97 insertions, 116 deletions
diff --git a/crates/ide-db/src/imports/import_assets.rs b/crates/ide-db/src/imports/import_assets.rs
index 82a182806a..dab36bf20b 100644
--- a/crates/ide-db/src/imports/import_assets.rs
+++ b/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)
}