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.rs170
1 files changed, 166 insertions, 4 deletions
diff --git a/crates/ide-db/src/imports/import_assets.rs b/crates/ide-db/src/imports/import_assets.rs
index 1c48527027..2f696d07e2 100644
--- a/crates/ide-db/src/imports/import_assets.rs
+++ b/crates/ide-db/src/imports/import_assets.rs
@@ -8,8 +8,10 @@ use hir::{
SemanticsScope, Trait, Type,
};
use itertools::Itertools;
+use parser::SyntaxKind;
use rustc_hash::{FxHashMap, FxHashSet};
use smallvec::{SmallVec, smallvec};
+use stdx::never;
use syntax::{
AstNode, SyntaxNode,
ast::{self, HasName, make},
@@ -61,6 +63,103 @@ pub struct TraitImportCandidate<'db> {
pub assoc_item_name: NameToImport,
}
+#[derive(Debug)]
+struct PathDefinitionKinds {
+ modules: bool,
+ bang_macros: bool,
+ // FIXME: Distinguish between attr and derive macros.
+ attr_macros: bool,
+ value_namespace: bool,
+ type_namespace: bool,
+ /// Unions, record structs and record enum variants. Note that unions and structs
+ /// can also be enabled by `type_namespace` (either works).
+ records: bool,
+ /// Tuple structs and tuple enum variants. Both are also controlled by `value_namespace`
+ /// (either works). Structs are also covered by `type_namespace`.
+ tuple_structs: bool,
+ /// Structs, enum variants and consts.
+ structs_and_consts: bool,
+}
+
+impl PathDefinitionKinds {
+ const ALL_DISABLED: Self = Self {
+ modules: false,
+ bang_macros: false,
+ attr_macros: false,
+ value_namespace: false,
+ type_namespace: false,
+ records: false,
+ tuple_structs: false,
+ structs_and_consts: false,
+ };
+ const ALL_ENABLED: Self = Self {
+ modules: true,
+ bang_macros: true,
+ attr_macros: true,
+ value_namespace: true,
+ type_namespace: true,
+ records: true,
+ tuple_structs: true,
+ structs_and_consts: true,
+ };
+ // While a path pattern only allows unit structs/enum variants, parentheses/braces may be written later.
+ const PATH_PAT_KINDS: PathDefinitionKinds =
+ Self { structs_and_consts: true, bang_macros: true, ..Self::ALL_DISABLED };
+
+ fn deduce_from_path(path: &ast::Path, exact: bool) -> Self {
+ let Some(parent) = path.syntax().parent() else {
+ return Self::ALL_ENABLED;
+ };
+ let mut result = match parent.kind() {
+ // When there are following segments, it can be a type (with a method) or a module.
+ // Technically, a type can only have up to 2 segments following (an associated type
+ // then a method), but most paths are shorter than 3 segments anyway, and we'll also
+ // validate that the following segment resolve.
+ SyntaxKind::PATH => Self { modules: true, type_namespace: true, ..Self::ALL_DISABLED },
+ SyntaxKind::MACRO_CALL => Self { bang_macros: true, ..Self::ALL_DISABLED },
+ SyntaxKind::META => Self { attr_macros: true, ..Self::ALL_DISABLED },
+ SyntaxKind::USE_TREE => {
+ if ast::UseTree::cast(parent).unwrap().use_tree_list().is_some() {
+ Self { modules: true, ..Self::ALL_DISABLED }
+ } else {
+ Self::ALL_ENABLED
+ }
+ }
+ SyntaxKind::VISIBILITY => Self { modules: true, ..Self::ALL_DISABLED },
+ SyntaxKind::ASM_SYM => Self { value_namespace: true, ..Self::ALL_DISABLED },
+ // `bang_macros = true` because you can still type the `!`.
+ // `type_namespace = true` because you can type `::method()`.
+ SyntaxKind::PATH_EXPR => Self {
+ value_namespace: true,
+ bang_macros: true,
+ type_namespace: true,
+ ..Self::ALL_DISABLED
+ },
+ SyntaxKind::PATH_PAT => Self::PATH_PAT_KINDS,
+ SyntaxKind::TUPLE_STRUCT_PAT => {
+ Self { tuple_structs: true, bang_macros: true, ..Self::ALL_DISABLED }
+ }
+ SyntaxKind::RECORD_EXPR | SyntaxKind::RECORD_PAT => {
+ Self { records: true, bang_macros: true, ..Self::ALL_DISABLED }
+ }
+ SyntaxKind::PATH_TYPE => {
+ Self { type_namespace: true, bang_macros: true, ..Self::ALL_DISABLED }
+ }
+ SyntaxKind::ERROR => Self::ALL_ENABLED,
+ _ => {
+ never!("this match should cover all possible parents of paths\nparent={parent:#?}");
+ Self::ALL_ENABLED
+ }
+ };
+ if !exact {
+ // When the path is not required to be exact, there could be additional segments to be filled.
+ result.modules = true;
+ result.type_namespace = true;
+ }
+ result
+ }
+}
+
/// Path import for a given name, qualified or not.
#[derive(Debug)]
pub struct PathImportCandidate {
@@ -70,6 +169,8 @@ pub struct PathImportCandidate {
pub name: NameToImport,
/// Potentially more segments that should resolve in the candidate.
pub after: Vec<Name>,
+ /// The kind of definitions that we can include.
+ definition_kinds: PathDefinitionKinds,
}
/// A name that will be used during item lookups.
@@ -168,13 +269,14 @@ impl<'db> ImportAssets<'db> {
pub fn for_fuzzy_path(
module_with_candidate: Module,
+ path: Option<&ast::Path>,
qualifier: Option<ast::Path>,
fuzzy_name: String,
sema: &Semantics<'db, RootDatabase>,
candidate_node: SyntaxNode,
) -> Option<Self> {
Some(Self {
- import_candidate: ImportCandidate::for_fuzzy_path(qualifier, fuzzy_name, sema)?,
+ import_candidate: ImportCandidate::for_fuzzy_path(path, qualifier, fuzzy_name, sema)?,
module_with_candidate,
candidate_node,
})
@@ -394,6 +496,9 @@ fn path_applicable_imports(
// see also an ignored test under FIXME comment in the qualify_path.rs module
AssocSearchMode::Exclude,
)
+ .filter(|(item, _)| {
+ filter_by_definition_kind(db, *item, &path_candidate.definition_kinds)
+ })
.filter_map(|(item, do_not_complete)| {
if !scope_filter(item) {
return None;
@@ -442,6 +547,46 @@ fn path_applicable_imports(
result
}
+fn filter_by_definition_kind(
+ db: &RootDatabase,
+ item: ItemInNs,
+ allowed: &PathDefinitionKinds,
+) -> bool {
+ let item = item.into_module_def();
+ let struct_per_kind = |struct_kind| {
+ allowed.structs_and_consts
+ || match struct_kind {
+ hir::StructKind::Record => allowed.records,
+ hir::StructKind::Tuple => allowed.value_namespace || allowed.tuple_structs,
+ hir::StructKind::Unit => allowed.value_namespace,
+ }
+ };
+ match item {
+ ModuleDef::Module(_) => allowed.modules,
+ ModuleDef::Function(_) => allowed.value_namespace,
+ ModuleDef::Adt(hir::Adt::Struct(item)) => {
+ allowed.type_namespace || struct_per_kind(item.kind(db))
+ }
+ ModuleDef::Adt(hir::Adt::Enum(_)) => allowed.type_namespace,
+ ModuleDef::Adt(hir::Adt::Union(_)) => {
+ allowed.type_namespace || allowed.records || allowed.structs_and_consts
+ }
+ ModuleDef::EnumVariant(item) => struct_per_kind(item.kind(db)),
+ ModuleDef::Const(_) => allowed.value_namespace || allowed.structs_and_consts,
+ ModuleDef::Static(_) => allowed.value_namespace,
+ ModuleDef::Trait(_) => allowed.type_namespace,
+ ModuleDef::TypeAlias(_) => allowed.type_namespace,
+ ModuleDef::BuiltinType(_) => allowed.type_namespace,
+ ModuleDef::Macro(item) => {
+ if item.is_fn_like(db) {
+ allowed.bang_macros
+ } else {
+ allowed.attr_macros
+ }
+ }
+ }
+}
+
fn filter_candidates_by_after_path(
db: &RootDatabase,
scope: &SemanticsScope<'_>,
@@ -835,6 +980,7 @@ impl<'db> ImportCandidate<'db> {
.collect::<Option<_>>()?;
path_import_candidate(
sema,
+ Some(path),
path.qualifier(),
NameToImport::exact_case_sensitive(path.segment()?.name_ref()?.to_string()),
after,
@@ -853,25 +999,31 @@ impl<'db> ImportCandidate<'db> {
qualifier: vec![],
name: NameToImport::exact_case_sensitive(name.to_string()),
after: vec![],
+ definition_kinds: PathDefinitionKinds::PATH_PAT_KINDS,
}))
}
fn for_fuzzy_path(
+ path: Option<&ast::Path>,
qualifier: Option<ast::Path>,
fuzzy_name: String,
sema: &Semantics<'db, RootDatabase>,
) -> Option<Self> {
// Assume a fuzzy match does not want the segments after. Because... I guess why not?
- path_import_candidate(sema, qualifier, NameToImport::fuzzy(fuzzy_name), Vec::new())
+ path_import_candidate(sema, path, qualifier, NameToImport::fuzzy(fuzzy_name), Vec::new())
}
}
fn path_import_candidate<'db>(
sema: &Semantics<'db, RootDatabase>,
+ path: Option<&ast::Path>,
qualifier: Option<ast::Path>,
name: NameToImport,
after: Vec<Name>,
) -> Option<ImportCandidate<'db>> {
+ let definition_kinds = path.map_or(PathDefinitionKinds::ALL_ENABLED, |path| {
+ PathDefinitionKinds::deduce_from_path(path, matches!(name, NameToImport::Exact(..)))
+ });
Some(match qualifier {
Some(qualifier) => match sema.resolve_path(&qualifier) {
Some(PathResolution::Def(ModuleDef::BuiltinType(_))) | None => {
@@ -880,7 +1032,12 @@ fn path_import_candidate<'db>(
.segments()
.map(|seg| seg.name_ref().map(|name| Name::new_root(&name.text())))
.collect::<Option<Vec<_>>>()?;
- ImportCandidate::Path(PathImportCandidate { qualifier, name, after })
+ ImportCandidate::Path(PathImportCandidate {
+ qualifier,
+ name,
+ after,
+ definition_kinds,
+ })
} else {
return None;
}
@@ -904,7 +1061,12 @@ fn path_import_candidate<'db>(
}
Some(_) => return None,
},
- None => ImportCandidate::Path(PathImportCandidate { qualifier: vec![], name, after }),
+ None => ImportCandidate::Path(PathImportCandidate {
+ qualifier: vec![],
+ name,
+ after,
+ definition_kinds,
+ }),
})
}