Skip to content

Commit 54d67b8

Browse files
authored
Merge pull request #21973 from ChayimFriedman2/import-context
fix: Consider the context of the path for `ImportAssets`
2 parents 8da2304 + 02bc765 commit 54d67b8

File tree

3 files changed

+204
-6
lines changed

3 files changed

+204
-6
lines changed

crates/ide-assists/src/handlers/auto_import.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1928,4 +1928,33 @@ fn f() {
19281928
"#;
19291929
check_auto_import_order(before, &["Import `foo::wanted`", "Import `quux::wanted`"]);
19301930
}
1931+
1932+
#[test]
1933+
fn consider_definition_kind() {
1934+
check_assist(
1935+
auto_import,
1936+
r#"
1937+
//- /eyre.rs crate:eyre
1938+
#[macro_export]
1939+
macro_rules! eyre {
1940+
() => {};
1941+
}
1942+
1943+
//- /color-eyre.rs crate:color-eyre deps:eyre
1944+
pub use eyre;
1945+
1946+
//- /main.rs crate:main deps:color-eyre
1947+
fn main() {
1948+
ey$0re!();
1949+
}
1950+
"#,
1951+
r#"
1952+
use color_eyre::eyre::eyre;
1953+
1954+
fn main() {
1955+
eyre!();
1956+
}
1957+
"#,
1958+
);
1959+
}
19311960
}

crates/ide-completion/src/completions/flyimport.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,12 @@ pub(crate) fn import_on_the_fly_path(
135135
Qualified::With { path, .. } => Some(path.clone()),
136136
_ => None,
137137
};
138-
let import_assets = import_assets_for_path(ctx, &potential_import_name, qualifier.clone())?;
138+
let import_assets = import_assets_for_path(
139+
ctx,
140+
Some(&path_ctx.path),
141+
&potential_import_name,
142+
qualifier.clone(),
143+
)?;
139144

140145
import_on_the_fly(
141146
acc,
@@ -160,7 +165,7 @@ pub(crate) fn import_on_the_fly_pat(
160165
}
161166

162167
let potential_import_name = import_name(ctx);
163-
let import_assets = import_assets_for_path(ctx, &potential_import_name, None)?;
168+
let import_assets = import_assets_for_path(ctx, None, &potential_import_name, None)?;
164169

165170
import_on_the_fly_pat_(
166171
acc,
@@ -402,6 +407,7 @@ fn import_name(ctx: &CompletionContext<'_>) -> String {
402407

403408
fn import_assets_for_path<'db>(
404409
ctx: &CompletionContext<'db>,
410+
path: Option<&ast::Path>,
405411
potential_import_name: &str,
406412
qualifier: Option<ast::Path>,
407413
) -> Option<ImportAssets<'db>> {
@@ -411,6 +417,7 @@ fn import_assets_for_path<'db>(
411417
let fuzzy_name_length = potential_import_name.len();
412418
let mut assets_for_path = ImportAssets::for_fuzzy_path(
413419
ctx.module,
420+
path,
414421
qualifier,
415422
potential_import_name.to_owned(),
416423
&ctx.sema,

crates/ide-db/src/imports/import_assets.rs

Lines changed: 166 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ use hir::{
88
SemanticsScope, Trait, Type,
99
};
1010
use itertools::Itertools;
11+
use parser::SyntaxKind;
1112
use rustc_hash::{FxHashMap, FxHashSet};
1213
use smallvec::{SmallVec, smallvec};
14+
use stdx::never;
1315
use syntax::{
1416
AstNode, SyntaxNode,
1517
ast::{self, HasName, make},
@@ -61,6 +63,103 @@ pub struct TraitImportCandidate<'db> {
6163
pub assoc_item_name: NameToImport,
6264
}
6365

66+
#[derive(Debug)]
67+
struct PathDefinitionKinds {
68+
modules: bool,
69+
bang_macros: bool,
70+
// FIXME: Distinguish between attr and derive macros.
71+
attr_macros: bool,
72+
value_namespace: bool,
73+
type_namespace: bool,
74+
/// Unions, record structs and record enum variants. Note that unions and structs
75+
/// can also be enabled by `type_namespace` (either works).
76+
records: bool,
77+
/// Tuple structs and tuple enum variants. Both are also controlled by `value_namespace`
78+
/// (either works). Structs are also covered by `type_namespace`.
79+
tuple_structs: bool,
80+
/// Structs, enum variants and consts.
81+
structs_and_consts: bool,
82+
}
83+
84+
impl PathDefinitionKinds {
85+
const ALL_DISABLED: Self = Self {
86+
modules: false,
87+
bang_macros: false,
88+
attr_macros: false,
89+
value_namespace: false,
90+
type_namespace: false,
91+
records: false,
92+
tuple_structs: false,
93+
structs_and_consts: false,
94+
};
95+
const ALL_ENABLED: Self = Self {
96+
modules: true,
97+
bang_macros: true,
98+
attr_macros: true,
99+
value_namespace: true,
100+
type_namespace: true,
101+
records: true,
102+
tuple_structs: true,
103+
structs_and_consts: true,
104+
};
105+
// While a path pattern only allows unit structs/enum variants, parentheses/braces may be written later.
106+
const PATH_PAT_KINDS: PathDefinitionKinds =
107+
Self { structs_and_consts: true, bang_macros: true, ..Self::ALL_DISABLED };
108+
109+
fn deduce_from_path(path: &ast::Path, exact: bool) -> Self {
110+
let Some(parent) = path.syntax().parent() else {
111+
return Self::ALL_ENABLED;
112+
};
113+
let mut result = match parent.kind() {
114+
// When there are following segments, it can be a type (with a method) or a module.
115+
// Technically, a type can only have up to 2 segments following (an associated type
116+
// then a method), but most paths are shorter than 3 segments anyway, and we'll also
117+
// validate that the following segment resolve.
118+
SyntaxKind::PATH => Self { modules: true, type_namespace: true, ..Self::ALL_DISABLED },
119+
SyntaxKind::MACRO_CALL => Self { bang_macros: true, ..Self::ALL_DISABLED },
120+
SyntaxKind::META => Self { attr_macros: true, ..Self::ALL_DISABLED },
121+
SyntaxKind::USE_TREE => {
122+
if ast::UseTree::cast(parent).unwrap().use_tree_list().is_some() {
123+
Self { modules: true, ..Self::ALL_DISABLED }
124+
} else {
125+
Self::ALL_ENABLED
126+
}
127+
}
128+
SyntaxKind::VISIBILITY => Self { modules: true, ..Self::ALL_DISABLED },
129+
SyntaxKind::ASM_SYM => Self { value_namespace: true, ..Self::ALL_DISABLED },
130+
// `bang_macros = true` because you can still type the `!`.
131+
// `type_namespace = true` because you can type `::method()`.
132+
SyntaxKind::PATH_EXPR => Self {
133+
value_namespace: true,
134+
bang_macros: true,
135+
type_namespace: true,
136+
..Self::ALL_DISABLED
137+
},
138+
SyntaxKind::PATH_PAT => Self::PATH_PAT_KINDS,
139+
SyntaxKind::TUPLE_STRUCT_PAT => {
140+
Self { tuple_structs: true, bang_macros: true, ..Self::ALL_DISABLED }
141+
}
142+
SyntaxKind::RECORD_EXPR | SyntaxKind::RECORD_PAT => {
143+
Self { records: true, bang_macros: true, ..Self::ALL_DISABLED }
144+
}
145+
SyntaxKind::PATH_TYPE => {
146+
Self { type_namespace: true, bang_macros: true, ..Self::ALL_DISABLED }
147+
}
148+
SyntaxKind::ERROR => Self::ALL_ENABLED,
149+
_ => {
150+
never!("this match should cover all possible parents of paths\nparent={parent:#?}");
151+
Self::ALL_ENABLED
152+
}
153+
};
154+
if !exact {
155+
// When the path is not required to be exact, there could be additional segments to be filled.
156+
result.modules = true;
157+
result.type_namespace = true;
158+
}
159+
result
160+
}
161+
}
162+
64163
/// Path import for a given name, qualified or not.
65164
#[derive(Debug)]
66165
pub struct PathImportCandidate {
@@ -70,6 +169,8 @@ pub struct PathImportCandidate {
70169
pub name: NameToImport,
71170
/// Potentially more segments that should resolve in the candidate.
72171
pub after: Vec<Name>,
172+
/// The kind of definitions that we can include.
173+
definition_kinds: PathDefinitionKinds,
73174
}
74175

75176
/// A name that will be used during item lookups.
@@ -168,13 +269,14 @@ impl<'db> ImportAssets<'db> {
168269

169270
pub fn for_fuzzy_path(
170271
module_with_candidate: Module,
272+
path: Option<&ast::Path>,
171273
qualifier: Option<ast::Path>,
172274
fuzzy_name: String,
173275
sema: &Semantics<'db, RootDatabase>,
174276
candidate_node: SyntaxNode,
175277
) -> Option<Self> {
176278
Some(Self {
177-
import_candidate: ImportCandidate::for_fuzzy_path(qualifier, fuzzy_name, sema)?,
279+
import_candidate: ImportCandidate::for_fuzzy_path(path, qualifier, fuzzy_name, sema)?,
178280
module_with_candidate,
179281
candidate_node,
180282
})
@@ -394,6 +496,9 @@ fn path_applicable_imports(
394496
// see also an ignored test under FIXME comment in the qualify_path.rs module
395497
AssocSearchMode::Exclude,
396498
)
499+
.filter(|(item, _)| {
500+
filter_by_definition_kind(db, *item, &path_candidate.definition_kinds)
501+
})
397502
.filter_map(|(item, do_not_complete)| {
398503
if !scope_filter(item) {
399504
return None;
@@ -442,6 +547,46 @@ fn path_applicable_imports(
442547
result
443548
}
444549

550+
fn filter_by_definition_kind(
551+
db: &RootDatabase,
552+
item: ItemInNs,
553+
allowed: &PathDefinitionKinds,
554+
) -> bool {
555+
let item = item.into_module_def();
556+
let struct_per_kind = |struct_kind| {
557+
allowed.structs_and_consts
558+
|| match struct_kind {
559+
hir::StructKind::Record => allowed.records,
560+
hir::StructKind::Tuple => allowed.value_namespace || allowed.tuple_structs,
561+
hir::StructKind::Unit => allowed.value_namespace,
562+
}
563+
};
564+
match item {
565+
ModuleDef::Module(_) => allowed.modules,
566+
ModuleDef::Function(_) => allowed.value_namespace,
567+
ModuleDef::Adt(hir::Adt::Struct(item)) => {
568+
allowed.type_namespace || struct_per_kind(item.kind(db))
569+
}
570+
ModuleDef::Adt(hir::Adt::Enum(_)) => allowed.type_namespace,
571+
ModuleDef::Adt(hir::Adt::Union(_)) => {
572+
allowed.type_namespace || allowed.records || allowed.structs_and_consts
573+
}
574+
ModuleDef::EnumVariant(item) => struct_per_kind(item.kind(db)),
575+
ModuleDef::Const(_) => allowed.value_namespace || allowed.structs_and_consts,
576+
ModuleDef::Static(_) => allowed.value_namespace,
577+
ModuleDef::Trait(_) => allowed.type_namespace,
578+
ModuleDef::TypeAlias(_) => allowed.type_namespace,
579+
ModuleDef::BuiltinType(_) => allowed.type_namespace,
580+
ModuleDef::Macro(item) => {
581+
if item.is_fn_like(db) {
582+
allowed.bang_macros
583+
} else {
584+
allowed.attr_macros
585+
}
586+
}
587+
}
588+
}
589+
445590
fn filter_candidates_by_after_path(
446591
db: &RootDatabase,
447592
scope: &SemanticsScope<'_>,
@@ -835,6 +980,7 @@ impl<'db> ImportCandidate<'db> {
835980
.collect::<Option<_>>()?;
836981
path_import_candidate(
837982
sema,
983+
Some(path),
838984
path.qualifier(),
839985
NameToImport::exact_case_sensitive(path.segment()?.name_ref()?.to_string()),
840986
after,
@@ -853,25 +999,31 @@ impl<'db> ImportCandidate<'db> {
853999
qualifier: vec![],
8541000
name: NameToImport::exact_case_sensitive(name.to_string()),
8551001
after: vec![],
1002+
definition_kinds: PathDefinitionKinds::PATH_PAT_KINDS,
8561003
}))
8571004
}
8581005

8591006
fn for_fuzzy_path(
1007+
path: Option<&ast::Path>,
8601008
qualifier: Option<ast::Path>,
8611009
fuzzy_name: String,
8621010
sema: &Semantics<'db, RootDatabase>,
8631011
) -> Option<Self> {
8641012
// Assume a fuzzy match does not want the segments after. Because... I guess why not?
865-
path_import_candidate(sema, qualifier, NameToImport::fuzzy(fuzzy_name), Vec::new())
1013+
path_import_candidate(sema, path, qualifier, NameToImport::fuzzy(fuzzy_name), Vec::new())
8661014
}
8671015
}
8681016

8691017
fn path_import_candidate<'db>(
8701018
sema: &Semantics<'db, RootDatabase>,
1019+
path: Option<&ast::Path>,
8711020
qualifier: Option<ast::Path>,
8721021
name: NameToImport,
8731022
after: Vec<Name>,
8741023
) -> Option<ImportCandidate<'db>> {
1024+
let definition_kinds = path.map_or(PathDefinitionKinds::ALL_ENABLED, |path| {
1025+
PathDefinitionKinds::deduce_from_path(path, matches!(name, NameToImport::Exact(..)))
1026+
});
8751027
Some(match qualifier {
8761028
Some(qualifier) => match sema.resolve_path(&qualifier) {
8771029
Some(PathResolution::Def(ModuleDef::BuiltinType(_))) | None => {
@@ -880,7 +1032,12 @@ fn path_import_candidate<'db>(
8801032
.segments()
8811033
.map(|seg| seg.name_ref().map(|name| Name::new_root(&name.text())))
8821034
.collect::<Option<Vec<_>>>()?;
883-
ImportCandidate::Path(PathImportCandidate { qualifier, name, after })
1035+
ImportCandidate::Path(PathImportCandidate {
1036+
qualifier,
1037+
name,
1038+
after,
1039+
definition_kinds,
1040+
})
8841041
} else {
8851042
return None;
8861043
}
@@ -904,7 +1061,12 @@ fn path_import_candidate<'db>(
9041061
}
9051062
Some(_) => return None,
9061063
},
907-
None => ImportCandidate::Path(PathImportCandidate { qualifier: vec![], name, after }),
1064+
None => ImportCandidate::Path(PathImportCandidate {
1065+
qualifier: vec![],
1066+
name,
1067+
after,
1068+
definition_kinds,
1069+
}),
9081070
})
9091071
}
9101072

0 commit comments

Comments
 (0)