Skip to content

Commit 34ec665

Browse files
committed
Simplify and improve perf of import_assets::import_for_item
1 parent 18591ae commit 34ec665

File tree

3 files changed

+99
-116
lines changed

3 files changed

+99
-116
lines changed

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

Lines changed: 85 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
//! Look up accessible paths for items.
2+
23
use hir::{
3-
AsAssocItem, AssocItem, AssocItemContainer, Crate, ItemInNs, ModPath, Module, ModuleDef,
4+
AsAssocItem, AssocItem, AssocItemContainer, Crate, ItemInNs, ModPath, Module, ModuleDef, Name,
45
PathResolution, PrefixKind, ScopeDef, Semantics, SemanticsScope, Type,
56
};
6-
use itertools::Itertools;
7-
use rustc_hash::FxHashSet;
7+
use itertools::{EitherOrBoth, Itertools};
8+
use rustc_hash::{FxHashMap, FxHashSet};
89
use syntax::{
910
ast::{self, make, HasName},
10-
utils::path_to_string_stripping_turbo_fish,
11-
AstNode, SyntaxNode,
11+
AstNode, SmolStr, SyntaxNode,
1212
};
1313

1414
use crate::{
@@ -51,18 +51,11 @@ pub struct TraitImportCandidate {
5151
#[derive(Debug)]
5252
pub struct PathImportCandidate {
5353
/// Optional qualifier before name.
54-
pub qualifier: Option<FirstSegmentUnresolved>,
54+
pub qualifier: Option<Vec<SmolStr>>,
5555
/// The name the item (struct, trait, enum, etc.) should have.
5656
pub name: NameToImport,
5757
}
5858

59-
/// A qualifier that has a first segment and it's unresolved.
60-
#[derive(Debug)]
61-
pub struct FirstSegmentUnresolved {
62-
fist_segment: ast::NameRef,
63-
full_qualifier: ast::Path,
64-
}
65-
6659
/// A name that will be used during item lookups.
6760
#[derive(Debug, Clone)]
6861
pub enum NameToImport {
@@ -348,60 +341,71 @@ fn path_applicable_imports(
348341
})
349342
.collect()
350343
}
351-
Some(first_segment_unresolved) => {
352-
let unresolved_qualifier =
353-
path_to_string_stripping_turbo_fish(&first_segment_unresolved.full_qualifier);
354-
let unresolved_first_segment = first_segment_unresolved.fist_segment.text();
355-
items_locator::items_with_name(
356-
sema,
357-
current_crate,
358-
path_candidate.name.clone(),
359-
AssocSearchMode::Include,
360-
Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
361-
)
362-
.filter_map(|item| {
363-
import_for_item(
364-
sema.db,
365-
mod_path,
366-
&unresolved_first_segment,
367-
&unresolved_qualifier,
368-
item,
369-
)
370-
})
371-
.collect()
372-
}
344+
Some(qualifier) => items_locator::items_with_name(
345+
sema,
346+
current_crate,
347+
path_candidate.name.clone(),
348+
AssocSearchMode::Include,
349+
Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
350+
)
351+
.filter_map(|item| import_for_item(sema.db, mod_path, &qualifier, item))
352+
.collect(),
373353
}
374354
}
375355

376356
fn import_for_item(
377357
db: &RootDatabase,
378358
mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
379-
unresolved_first_segment: &str,
380-
unresolved_qualifier: &str,
359+
unresolved_qualifier: &[SmolStr],
381360
original_item: ItemInNs,
382361
) -> Option<LocatedImport> {
383362
let _p = profile::span("import_assets::import_for_item");
363+
let [first_segment, ..] = unresolved_qualifier else { return None };
384364

385-
let original_item_candidate = item_for_path_search(db, original_item)?;
365+
let item_as_assoc = item_as_assoc(db, original_item);
366+
367+
let (original_item_candidate, trait_item_to_import) = match item_as_assoc {
368+
Some(assoc_item) => match assoc_item.container(db) {
369+
AssocItemContainer::Trait(trait_) => {
370+
let trait_ = ItemInNs::from(ModuleDef::from(trait_));
371+
(trait_, Some(trait_))
372+
}
373+
AssocItemContainer::Impl(impl_) => {
374+
(ItemInNs::from(ModuleDef::from(impl_.self_ty(db).as_adt()?)), None)
375+
}
376+
},
377+
None => (original_item, None),
378+
};
386379
let import_path_candidate = mod_path(original_item_candidate)?;
387-
let import_path_string = import_path_candidate.display(db).to_string();
388380

389-
let expected_import_end = if item_as_assoc(db, original_item).is_some() {
390-
unresolved_qualifier.to_string()
391-
} else {
392-
format!("{unresolved_qualifier}::{}", item_name(db, original_item)?.display(db))
381+
let mut import_path_candidate_segments = import_path_candidate.segments().iter().rev();
382+
let predicate = |it: EitherOrBoth<&SmolStr, &Name>| match it {
383+
// segments match, check next one
384+
EitherOrBoth::Both(a, b) if b.as_str() == Some(&**a) => None,
385+
// segments mismatch / qualifier is longer than the path, bail out
386+
EitherOrBoth::Both(..) | EitherOrBoth::Left(_) => Some(false),
387+
// all segments match and we have exhausted the qualifier, proceed
388+
EitherOrBoth::Right(_) => Some(true),
393389
};
394-
if !import_path_string.contains(unresolved_first_segment)
395-
|| !import_path_string.ends_with(&expected_import_end)
396-
{
390+
if item_as_assoc.is_none() {
391+
let item_name = item_name(db, original_item)?.as_text()?;
392+
let last_segment = import_path_candidate_segments.next()?;
393+
if last_segment.as_str() != Some(&*item_name) {
394+
return None;
395+
}
396+
}
397+
let ends_with = unresolved_qualifier
398+
.iter()
399+
.rev()
400+
.zip_longest(import_path_candidate_segments)
401+
.find_map(predicate)
402+
.unwrap_or(true);
403+
if !ends_with {
397404
return None;
398405
}
399406

400-
let segment_import =
401-
find_import_for_segment(db, original_item_candidate, unresolved_first_segment)?;
402-
let trait_item_to_import = item_as_assoc(db, original_item)
403-
.and_then(|assoc| assoc.containing_trait(db))
404-
.map(|trait_| ItemInNs::from(ModuleDef::from(trait_)));
407+
let segment_import = find_import_for_segment(db, original_item_candidate, first_segment)?;
408+
405409
Some(match (segment_import == original_item_candidate, trait_item_to_import) {
406410
(true, Some(_)) => {
407411
// FIXME we should be able to import both the trait and the segment,
@@ -424,18 +428,22 @@ fn import_for_item(
424428
pub fn item_for_path_search(db: &RootDatabase, item: ItemInNs) -> Option<ItemInNs> {
425429
Some(match item {
426430
ItemInNs::Types(_) | ItemInNs::Values(_) => match item_as_assoc(db, item) {
427-
Some(assoc_item) => match assoc_item.container(db) {
428-
AssocItemContainer::Trait(trait_) => ItemInNs::from(ModuleDef::from(trait_)),
429-
AssocItemContainer::Impl(impl_) => {
430-
ItemInNs::from(ModuleDef::from(impl_.self_ty(db).as_adt()?))
431-
}
432-
},
431+
Some(assoc_item) => item_for_path_search_assoc(db, assoc_item)?,
433432
None => item,
434433
},
435434
ItemInNs::Macros(_) => item,
436435
})
437436
}
438437

438+
fn item_for_path_search_assoc(db: &RootDatabase, assoc_item: AssocItem) -> Option<ItemInNs> {
439+
Some(match assoc_item.container(db) {
440+
AssocItemContainer::Trait(trait_) => ItemInNs::from(ModuleDef::from(trait_)),
441+
AssocItemContainer::Impl(impl_) => {
442+
ItemInNs::from(ModuleDef::from(impl_.self_ty(db).as_adt()?))
443+
}
444+
})
445+
}
446+
439447
fn find_import_for_segment(
440448
db: &RootDatabase,
441449
original_item: ItemInNs,
@@ -512,6 +520,7 @@ fn trait_applicable_items(
512520
.collect();
513521

514522
let mut located_imports = FxHashSet::default();
523+
let mut trait_import_paths = FxHashMap::default();
515524

516525
if trait_assoc_item {
517526
trait_candidate.receiver_ty.iterate_path_candidates(
@@ -529,11 +538,14 @@ fn trait_applicable_items(
529538
}
530539
let located_trait = assoc.containing_trait(db)?;
531540
let trait_item = ItemInNs::from(ModuleDef::from(located_trait));
532-
let original_item = assoc_to_item(assoc);
541+
let import_path = trait_import_paths
542+
.entry(trait_item)
543+
.or_insert_with(|| mod_path(trait_item))
544+
.clone()?;
533545
located_imports.insert(LocatedImport::new(
534-
mod_path(trait_item)?,
546+
import_path,
535547
trait_item,
536-
original_item,
548+
assoc_to_item(assoc),
537549
));
538550
}
539551
None::<()>
@@ -551,11 +563,14 @@ fn trait_applicable_items(
551563
if required_assoc_items.contains(&assoc) {
552564
let located_trait = assoc.containing_trait(db)?;
553565
let trait_item = ItemInNs::from(ModuleDef::from(located_trait));
554-
let original_item = assoc_to_item(assoc);
566+
let import_path = trait_import_paths
567+
.entry(trait_item)
568+
.or_insert_with(|| mod_path(trait_item))
569+
.clone()?;
555570
located_imports.insert(LocatedImport::new(
556-
mod_path(trait_item)?,
571+
import_path,
557572
trait_item,
558-
original_item,
573+
assoc_to_item(assoc),
559574
));
560575
}
561576
None::<()>
@@ -653,18 +668,13 @@ fn path_import_candidate(
653668
Some(match qualifier {
654669
Some(qualifier) => match sema.resolve_path(&qualifier) {
655670
None => {
656-
let qualifier_start =
657-
qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
658-
let qualifier_start_path =
659-
qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
660-
if sema.resolve_path(&qualifier_start_path).is_none() {
661-
ImportCandidate::Path(PathImportCandidate {
662-
qualifier: Some(FirstSegmentUnresolved {
663-
fist_segment: qualifier_start,
664-
full_qualifier: qualifier,
665-
}),
666-
name,
667-
})
671+
if qualifier.first_qualifier().map_or(true, |it| sema.resolve_path(&it).is_none()) {
672+
let mut qualifier = qualifier
673+
.segments_of_this_path_only_rev()
674+
.map(|seg| seg.name_ref().map(|name| SmolStr::new(name.text())))
675+
.collect::<Option<Vec<_>>>()?;
676+
qualifier.reverse();
677+
ImportCandidate::Path(PathImportCandidate { qualifier: Some(qualifier), name })
668678
} else {
669679
return None;
670680
}

crates/syntax/src/ast/node_ext.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,10 +275,19 @@ impl ast::Path {
275275
successors(Some(self.clone()), ast::Path::qualifier).last().unwrap()
276276
}
277277

278+
pub fn first_qualifier(&self) -> Option<ast::Path> {
279+
successors(self.qualifier(), ast::Path::qualifier).last()
280+
}
281+
278282
pub fn first_segment(&self) -> Option<ast::PathSegment> {
279283
self.first_qualifier_or_self().segment()
280284
}
281285

286+
// FIXME: Check usages of Self::segments, they might be wrong because of the logic of the bloew function
287+
pub fn segments_of_this_path_only_rev(&self) -> impl Iterator<Item = ast::PathSegment> + Clone {
288+
self.qualifiers_and_self().filter_map(|it| it.segment())
289+
}
290+
282291
pub fn segments(&self) -> impl Iterator<Item = ast::PathSegment> + Clone {
283292
successors(self.first_segment(), |p| {
284293
p.parent_path().parent_path().and_then(|p| p.segment())
@@ -289,6 +298,10 @@ impl ast::Path {
289298
successors(self.qualifier(), |p| p.qualifier())
290299
}
291300

301+
pub fn qualifiers_and_self(&self) -> impl Iterator<Item = ast::Path> + Clone {
302+
successors(Some(self.clone()), |p| p.qualifier())
303+
}
304+
292305
pub fn top_path(&self) -> ast::Path {
293306
let mut this = self.clone();
294307
while let Some(path) = this.parent_path() {

crates/syntax/src/utils.rs

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,8 @@
11
//! A set of utils methods to reuse on other abstraction levels
22
3-
use itertools::Itertools;
4-
5-
use crate::{ast, match_ast, AstNode, SyntaxKind};
6-
7-
pub fn path_to_string_stripping_turbo_fish(path: &ast::Path) -> String {
8-
path.syntax()
9-
.children()
10-
.filter_map(|node| {
11-
match_ast! {
12-
match node {
13-
ast::PathSegment(it) => {
14-
Some(it.name_ref()?.to_string())
15-
},
16-
ast::Path(it) => {
17-
Some(path_to_string_stripping_turbo_fish(&it))
18-
},
19-
_ => None,
20-
}
21-
}
22-
})
23-
.join("::")
24-
}
3+
use crate::SyntaxKind;
254

265
pub fn is_raw_identifier(name: &str) -> bool {
276
let is_keyword = SyntaxKind::from_keyword(name).is_some();
287
is_keyword && !matches!(name, "self" | "crate" | "super" | "Self")
298
}
30-
31-
#[cfg(test)]
32-
mod tests {
33-
use super::path_to_string_stripping_turbo_fish;
34-
use crate::ast::make;
35-
36-
#[test]
37-
fn turbofishes_are_stripped() {
38-
assert_eq!("Vec", path_to_string_stripping_turbo_fish(&make::path_from_text("Vec::<i32>")),);
39-
assert_eq!(
40-
"Vec::new",
41-
path_to_string_stripping_turbo_fish(&make::path_from_text("Vec::<i32>::new")),
42-
);
43-
assert_eq!(
44-
"Vec::new",
45-
path_to_string_stripping_turbo_fish(&make::path_from_text("Vec::new()")),
46-
);
47-
}
48-
}

0 commit comments

Comments
 (0)