11//! Look up accessible paths for items.
2+
23use 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 } ;
89use syntax:: {
910 ast:: { self , make, HasName } ,
10- utils:: path_to_string_stripping_turbo_fish,
11- AstNode , SyntaxNode ,
11+ AstNode , SmolStr , SyntaxNode ,
1212} ;
1313
1414use crate :: {
@@ -51,18 +51,11 @@ pub struct TraitImportCandidate {
5151#[ derive( Debug ) ]
5252pub 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 ) ]
6861pub 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
376356fn 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(
424428pub 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+
439447fn 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 }
0 commit comments