Skip to content

Commit 309421c

Browse files
Draft the qualifier import resolution
1 parent c395c33 commit 309421c

File tree

4 files changed

+224
-43
lines changed

4 files changed

+224
-43
lines changed

crates/ide_assists/src/handlers/qualify_path.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use std::iter;
22

33
use hir::AsAssocItem;
4-
use ide_db::helpers::{import_assets::ImportCandidate, mod_path_to_ast};
4+
use ide_db::helpers::{
5+
import_assets::{ImportCandidate, Qualifier},
6+
mod_path_to_ast,
7+
};
58
use ide_db::RootDatabase;
69
use syntax::{
710
ast,
@@ -45,7 +48,7 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
4548

4649
let qualify_candidate = match candidate {
4750
ImportCandidate::Path(candidate) => {
48-
if candidate.unresolved_qualifier.is_some() {
51+
if !matches!(candidate.qualifier, Qualifier::Absent) {
4952
cov_mark::hit!(qualify_path_qualifier_start);
5053
let path = ast::Path::cast(syntax_under_caret)?;
5154
let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?);
@@ -192,7 +195,7 @@ fn group_label(candidate: &ImportCandidate) -> GroupLabel {
192195
fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String {
193196
match candidate {
194197
ImportCandidate::Path(candidate) => {
195-
if candidate.unresolved_qualifier.is_some() {
198+
if !matches!(candidate.qualifier, Qualifier::Absent) {
196199
format!("Qualify with `{}`", &import)
197200
} else {
198201
format!("Qualify as `{}`", &import)

crates/ide_completion/src/completions/flyimport.rs

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -775,18 +775,92 @@ fn main() {
775775
}
776776

777777
#[test]
778-
fn unresolved_qualifiers() {
778+
fn unresolved_qualifier() {
779+
check_edit(
780+
"Item",
781+
r#"
782+
mod foo {
783+
pub mod bar {
784+
pub mod baz {
785+
pub struct Item;
786+
}
787+
}
788+
}
789+
790+
fn main() {
791+
bar::baz::Ite$0
792+
}
793+
"#,
794+
r#"
795+
use foo::bar;
796+
797+
mod foo {
798+
pub mod bar {
799+
pub mod baz {
800+
pub struct Item;
801+
}
802+
}
803+
}
804+
805+
fn main() {
806+
bar::baz::Item
807+
}
808+
"#,
809+
);
810+
}
811+
812+
#[test]
813+
fn unresolved_assoc_item_container() {
814+
check_edit(
815+
"Item",
816+
r#"
817+
mod foo {
818+
pub struct Item;
819+
820+
impl Item {
821+
pub const TEST_ASSOC: usize = 3;
822+
}
823+
}
824+
825+
fn main() {
826+
Item::TEST_A$0;
827+
}
828+
"#,
829+
r#"
830+
use foo::Item;
831+
832+
mod foo {
833+
pub struct Item;
834+
835+
impl Item {
836+
pub const TEST_ASSOC: usize = 3;
837+
}
838+
}
839+
840+
fn main() {
841+
Item::TEST_ASSOC
842+
}
843+
"#,
844+
);
845+
}
846+
847+
#[test]
848+
fn unresolved_assoc_item_container_with_path() {
779849
check_edit(
780850
"Item",
781851
r#"
782852
mod foo {
783853
pub mod bar {
784854
pub struct Item;
855+
856+
impl Item {
857+
pub const TEST_ASSOC: usize = 3;
858+
}
785859
}
786860
}
787861
788862
fn main() {
789-
bar::Ite$0
863+
bar::Item::TEST_A$0;
790864
}
791865
"#,
792866
r#"
@@ -795,11 +869,15 @@ use foo::bar;
795869
mod foo {
796870
pub mod bar {
797871
pub struct Item;
872+
873+
impl Item {
874+
pub const TEST_ASSOC: usize = 3;
875+
}
798876
}
799877
}
800878
801879
fn main() {
802-
bar::Item
880+
bar::Item::TEST_ASSOC
803881
}
804882
"#,
805883
);

crates/ide_db/src/helpers/import_assets.rs

Lines changed: 136 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
//! Look up accessible paths for items.
22
use either::Either;
33
use hir::{
4-
AsAssocItem, AssocItem, Crate, ItemInNs, MacroDef, ModPath, Module, ModuleDef, PrefixKind,
5-
Semantics,
4+
AsAssocItem, AssocItem, Crate, ItemInNs, MacroDef, ModPath, Module, ModuleDef, Name,
5+
PrefixKind, Semantics,
66
};
77
use rustc_hash::FxHashSet;
88
use syntax::{ast, AstNode};
@@ -34,10 +34,16 @@ pub struct TraitImportCandidate {
3434

3535
#[derive(Debug)]
3636
pub struct PathImportCandidate {
37-
pub unresolved_qualifier: Option<ast::Path>,
37+
pub qualifier: Qualifier,
3838
pub name: NameToImport,
3939
}
4040

41+
#[derive(Debug)]
42+
pub enum Qualifier {
43+
Absent,
44+
FirstSegmentUnresolved(ast::PathSegment, ast::Path),
45+
}
46+
4147
#[derive(Debug)]
4248
pub enum NameToImport {
4349
Exact(String),
@@ -162,8 +168,9 @@ impl ImportAssets {
162168
let (assoc_item_search, limit) = if self.import_candidate.is_trait_candidate() {
163169
(AssocItemSearch::AssocItemsOnly, None)
164170
} else {
165-
(AssocItemSearch::Exclude, Some(DEFAULT_QUERY_SEARCH_LIMIT))
171+
(AssocItemSearch::Include, Some(DEFAULT_QUERY_SEARCH_LIMIT))
166172
};
173+
167174
imports_locator::find_similar_imports(
168175
sema,
169176
current_crate,
@@ -192,17 +199,16 @@ impl ImportAssets {
192199
let db = sema.db;
193200

194201
match &self.import_candidate {
195-
ImportCandidate::Path(path_candidate) => Box::new(path_applicable_items(
196-
sema,
197-
path_candidate,
198-
unfiltered_defs
199-
.into_iter()
200-
.map(|def| def.either(ItemInNs::from, ItemInNs::from))
201-
.filter_map(move |item_to_search| {
202-
get_mod_path(db, item_to_search, &self.module_with_candidate, prefixed)
203-
.zip(Some(item_to_search))
204-
}),
205-
)),
202+
ImportCandidate::Path(path_candidate) => Box::new(
203+
path_applicable_items(
204+
db,
205+
path_candidate,
206+
&self.module_with_candidate,
207+
prefixed,
208+
unfiltered_defs,
209+
)
210+
.into_iter(),
211+
),
206212
ImportCandidate::TraitAssocItem(trait_candidate) => Box::new(
207213
trait_applicable_defs(db, current_crate, trait_candidate, true, unfiltered_defs)
208214
.into_iter()
@@ -224,27 +230,110 @@ impl ImportAssets {
224230
}
225231

226232
fn path_applicable_items<'a>(
227-
sema: &'a Semantics<RootDatabase>,
228-
path_candidate: &PathImportCandidate,
229-
unfiltered_defs: impl Iterator<Item = (ModPath, ItemInNs)> + 'a,
230-
) -> Box<dyn Iterator<Item = (ModPath, ItemInNs)> + 'a> {
231-
let unresolved_qualifier = match &path_candidate.unresolved_qualifier {
232-
Some(qualifier) => qualifier,
233-
None => {
234-
return Box::new(unfiltered_defs);
233+
db: &'a RootDatabase,
234+
path_candidate: &'a PathImportCandidate,
235+
module_with_candidate: &hir::Module,
236+
prefixed: Option<hir::PrefixKind>,
237+
unfiltered_defs: impl Iterator<Item = Either<ModuleDef, MacroDef>> + 'a,
238+
) -> FxHashSet<(ModPath, ItemInNs)> {
239+
let applicable_items = unfiltered_defs
240+
.filter_map(|def| {
241+
let (assoc_original, candidate) = match def {
242+
Either::Left(module_def) => match module_def.as_assoc_item(db) {
243+
Some(assoc_item) => match assoc_item.container(db) {
244+
hir::AssocItemContainer::Trait(trait_) => {
245+
(Some(module_def), ItemInNs::from(ModuleDef::from(trait_)))
246+
}
247+
hir::AssocItemContainer::Impl(impl_) => (
248+
Some(module_def),
249+
ItemInNs::from(ModuleDef::from(impl_.target_ty(db).as_adt()?)),
250+
),
251+
},
252+
None => (None, ItemInNs::from(module_def)),
253+
},
254+
Either::Right(macro_def) => (None, ItemInNs::from(macro_def)),
255+
};
256+
Some((assoc_original, candidate))
257+
})
258+
.filter_map(|(assoc_original, candidate)| {
259+
get_mod_path(db, candidate, module_with_candidate, prefixed)
260+
.zip(Some((assoc_original, candidate)))
261+
});
262+
263+
let (unresolved_first_segment, unresolved_qualifier) = match &path_candidate.qualifier {
264+
Qualifier::Absent => {
265+
return applicable_items
266+
.map(|(candidate_path, (_, candidate))| (candidate_path, candidate))
267+
.collect();
235268
}
269+
Qualifier::FirstSegmentUnresolved(first_segment, qualifier) => (first_segment, qualifier),
236270
};
237271

238-
let qualifier_string = unresolved_qualifier.to_string();
239-
Box::new(unfiltered_defs.filter(move |(candidate_path, _)| {
240-
let mut candidate_qualifier = candidate_path.clone();
241-
candidate_qualifier.pop_segment();
272+
// TODO kb need to remove turbofish from the qualifier, maybe use the segments instead?
273+
let unresolved_qualifier_string = unresolved_qualifier.to_string();
274+
let unresolved_first_segment_string = unresolved_first_segment.to_string();
275+
276+
applicable_items
277+
.filter(|(candidate_path, _)| {
278+
let candidate_path_string = candidate_path.to_string();
279+
candidate_path_string.contains(&unresolved_qualifier_string)
280+
&& candidate_path_string.contains(&unresolved_first_segment_string)
281+
})
282+
// TODO kb need to adjust the return type: I get the results rendered rather badly
283+
.filter_map(|(candidate_path, (assoc_original, candidate))| {
284+
if let Some(assoc_original) = assoc_original {
285+
if item_name(db, candidate)?.to_string() == unresolved_first_segment_string {
286+
return Some((candidate_path, ItemInNs::from(assoc_original)));
287+
}
288+
}
289+
290+
let matching_module =
291+
module_with_matching_name(db, &unresolved_first_segment_string, candidate)?;
292+
let path = get_mod_path(
293+
db,
294+
ItemInNs::from(ModuleDef::from(matching_module)),
295+
module_with_candidate,
296+
prefixed,
297+
)?;
298+
Some((path, candidate))
299+
})
300+
.collect()
301+
}
302+
303+
fn item_name(db: &RootDatabase, item: ItemInNs) -> Option<Name> {
304+
match item {
305+
ItemInNs::Types(module_def_id) => ModuleDef::from(module_def_id).name(db),
306+
ItemInNs::Values(module_def_id) => ModuleDef::from(module_def_id).name(db),
307+
ItemInNs::Macros(macro_def_id) => MacroDef::from(macro_def_id).name(db),
308+
}
309+
}
242310

243-
// TODO kb
244-
// * take 1st segment of `unresolved_qualifier` and return it instead of the original `ItemInNs`
245-
// * Update `ModPath`: pop until 1st segment of `unresolved_qualifier` reached (do not rely on name comparison, nested mod names can repeat)
246-
candidate_qualifier.to_string().ends_with(&qualifier_string)
247-
}))
311+
fn item_module(db: &RootDatabase, item: ItemInNs) -> Option<Module> {
312+
match item {
313+
ItemInNs::Types(module_def_id) => ModuleDef::from(module_def_id).module(db),
314+
ItemInNs::Values(module_def_id) => ModuleDef::from(module_def_id).module(db),
315+
ItemInNs::Macros(macro_def_id) => MacroDef::from(macro_def_id).module(db),
316+
}
317+
}
318+
319+
fn module_with_matching_name(
320+
db: &RootDatabase,
321+
unresolved_first_segment_string: &str,
322+
candidate: ItemInNs,
323+
) -> Option<Module> {
324+
let mut current_module = item_module(db, candidate);
325+
while let Some(module) = current_module {
326+
match module.name(db) {
327+
Some(module_name) => {
328+
if module_name.to_string().as_str() == unresolved_first_segment_string {
329+
return Some(module);
330+
}
331+
}
332+
None => {}
333+
}
334+
current_module = module.parent(db);
335+
}
336+
None
248337
}
249338

250339
fn trait_applicable_defs<'a>(
@@ -367,10 +456,20 @@ fn path_import_candidate(
367456
) -> Option<ImportCandidate> {
368457
Some(match qualifier {
369458
Some(qualifier) => match sema.resolve_path(&qualifier) {
370-
None => ImportCandidate::Path(PathImportCandidate {
371-
unresolved_qualifier: Some(qualifier),
372-
name,
373-
}),
459+
None => {
460+
let qualifier_start =
461+
qualifier.syntax().descendants().find_map(ast::PathSegment::cast)?;
462+
let qualifier_start_path =
463+
qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
464+
if sema.resolve_path(&qualifier_start_path).is_none() {
465+
ImportCandidate::Path(PathImportCandidate {
466+
qualifier: Qualifier::FirstSegmentUnresolved(qualifier_start, qualifier),
467+
name,
468+
})
469+
} else {
470+
return None;
471+
}
472+
}
374473
Some(hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path))) => {
375474
ImportCandidate::TraitAssocItem(TraitImportCandidate {
376475
receiver_ty: assoc_item_path.ty(sema.db),
@@ -379,6 +478,6 @@ fn path_import_candidate(
379478
}
380479
Some(_) => return None,
381480
},
382-
None => ImportCandidate::Path(PathImportCandidate { unresolved_qualifier: None, name }),
481+
None => ImportCandidate::Path(PathImportCandidate { qualifier: Qualifier::Absent, name }),
383482
})
384483
}

crates/ide_db/src/imports_locator.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ pub fn find_exact_imports<'a>(
4040
))
4141
}
4242

43+
#[derive(Debug)]
4344
pub enum AssocItemSearch {
4445
Include,
4546
Exclude,

0 commit comments

Comments
 (0)