Skip to main content

ide_db/imports/
import_assets.rs

1//! Look up accessible paths for items.
2
3use std::{convert::Infallible, ops::ControlFlow};
4
5use hir::{
6    AsAssocItem, AssocItem, AssocItemContainer, Complete, Crate, FindPathConfig, HasCrate,
7    ItemInNs, ModPath, Module, ModuleDef, Name, PathResolution, PrefixKind, ScopeDef, Semantics,
8    SemanticsScope, Trait, Type,
9};
10use itertools::Itertools;
11use parser::SyntaxKind;
12use rustc_hash::{FxHashMap, FxHashSet};
13use smallvec::{SmallVec, smallvec};
14use stdx::never;
15use syntax::{
16    AstNode, SyntaxNode,
17    ast::{self, HasName, make},
18};
19
20use crate::{
21    FxIndexSet, RootDatabase,
22    items_locator::{self, AssocSearchMode, DEFAULT_QUERY_SEARCH_LIMIT},
23};
24
25#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
26pub struct ImportPathConfig {
27    /// If true, prefer to unconditionally use imports of the `core` and `alloc` crate
28    /// over the std.
29    pub prefer_no_std: bool,
30    /// If true, prefer import paths containing a prelude module.
31    pub prefer_prelude: bool,
32    /// If true, prefer abs path (starting with `::`) where it is available.
33    pub prefer_absolute: bool,
34}
35
36/// A candidate for import, derived during various IDE activities:
37/// * completion with imports on the fly proposals
38/// * completion edit resolve requests
39/// * assists
40/// * etc.
41#[derive(Debug)]
42pub enum ImportCandidate<'db> {
43    /// A path, qualified (`std::collections::HashMap`) or not (`HashMap`).
44    Path(PathImportCandidate),
45    /// A trait associated function (with no self parameter) or an associated constant.
46    /// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type
47    /// and `name` is the `test_function`
48    TraitAssocItem(TraitImportCandidate<'db>),
49    /// A trait method with self parameter.
50    /// For 'test_enum.test_method()', `ty` is the `test_enum` expression type
51    /// and `name` is the `test_method`
52    TraitMethod(TraitImportCandidate<'db>),
53}
54
55/// A trait import needed for a given associated item access.
56/// For `some::path::SomeStruct::ASSOC_`, contains the
57/// type of `some::path::SomeStruct` and `ASSOC_` as the item name.
58#[derive(Debug)]
59pub struct TraitImportCandidate<'db> {
60    /// A type of the item that has the associated item accessed at.
61    pub receiver_ty: Type<'db>,
62    /// The associated item name that the trait to import should contain.
63    pub assoc_item_name: NameToImport,
64}
65
66#[derive(Debug)]
67struct 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
84impl 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::PATH_META | SyntaxKind::KEY_VALUE_META | SyntaxKind::TOKEN_TREE_META => {
121                Self { attr_macros: true, ..Self::ALL_DISABLED }
122            }
123            SyntaxKind::USE_TREE => {
124                if ast::UseTree::cast(parent).unwrap().use_tree_list().is_some() {
125                    Self { modules: true, ..Self::ALL_DISABLED }
126                } else {
127                    Self::ALL_ENABLED
128                }
129            }
130            SyntaxKind::VISIBILITY => Self { modules: true, ..Self::ALL_DISABLED },
131            SyntaxKind::ASM_SYM => Self { value_namespace: true, ..Self::ALL_DISABLED },
132            // `bang_macros = true` because you can still type the `!`.
133            // `type_namespace = true` because you can type `::method()`.
134            SyntaxKind::PATH_EXPR => Self {
135                value_namespace: true,
136                bang_macros: true,
137                type_namespace: true,
138                ..Self::ALL_DISABLED
139            },
140            SyntaxKind::PATH_PAT => Self::PATH_PAT_KINDS,
141            SyntaxKind::TUPLE_STRUCT_PAT => {
142                Self { tuple_structs: true, bang_macros: true, ..Self::ALL_DISABLED }
143            }
144            SyntaxKind::RECORD_EXPR | SyntaxKind::RECORD_PAT => {
145                Self { records: true, bang_macros: true, ..Self::ALL_DISABLED }
146            }
147            SyntaxKind::PATH_TYPE => {
148                Self { type_namespace: true, bang_macros: true, ..Self::ALL_DISABLED }
149            }
150            SyntaxKind::ERROR => Self::ALL_ENABLED,
151            _ => {
152                never!("this match should cover all possible parents of paths\nparent={parent:#?}");
153                Self::ALL_ENABLED
154            }
155        };
156        if !exact {
157            // When the path is not required to be exact, there could be additional segments to be filled.
158            result.modules = true;
159            result.type_namespace = true;
160        }
161        result
162    }
163}
164
165/// Path import for a given name, qualified or not.
166#[derive(Debug)]
167pub struct PathImportCandidate {
168    /// Optional qualifier before name.
169    pub qualifier: Vec<Name>,
170    /// The name the item (struct, trait, enum, etc.) should have.
171    pub name: NameToImport,
172    /// Potentially more segments that should resolve in the candidate.
173    pub after: Vec<Name>,
174    /// The kind of definitions that we can include.
175    definition_kinds: PathDefinitionKinds,
176}
177
178/// A name that will be used during item lookups.
179#[derive(Debug, Clone)]
180pub enum NameToImport {
181    /// Requires items with names that exactly match the given string, bool indicates case-sensitivity.
182    Exact(String, bool),
183    /// Requires items with names that match the given string by prefix, bool indicates case-sensitivity.
184    Prefix(String, bool),
185    /// Requires items with names contain all letters from the string,
186    /// in the same order, but not necessary adjacent.
187    Fuzzy(String, bool),
188}
189
190impl NameToImport {
191    pub fn exact_case_sensitive(s: String) -> NameToImport {
192        let s = match s.strip_prefix("r#") {
193            Some(s) => s.to_owned(),
194            None => s,
195        };
196        NameToImport::Exact(s, true)
197    }
198
199    pub fn fuzzy(s: String) -> NameToImport {
200        let s = match s.strip_prefix("r#") {
201            Some(s) => s.to_owned(),
202            None => s,
203        };
204        // unless all chars are lowercase, we do a case sensitive search
205        let case_sensitive = s.chars().any(|c| c.is_uppercase());
206        NameToImport::Fuzzy(s, case_sensitive)
207    }
208
209    pub fn text(&self) -> &str {
210        match self {
211            NameToImport::Prefix(text, _)
212            | NameToImport::Exact(text, _)
213            | NameToImport::Fuzzy(text, _) => text.as_str(),
214        }
215    }
216}
217
218/// A struct to find imports in the project, given a certain name (or its part) and the context.
219#[derive(Debug)]
220pub struct ImportAssets<'db> {
221    import_candidate: ImportCandidate<'db>,
222    candidate_node: SyntaxNode,
223    module_with_candidate: Module,
224}
225
226impl<'db> ImportAssets<'db> {
227    pub fn for_method_call(
228        method_call: &ast::MethodCallExpr,
229        sema: &Semantics<'db, RootDatabase>,
230    ) -> Option<Self> {
231        let candidate_node = method_call.syntax().clone();
232        Some(Self {
233            import_candidate: ImportCandidate::for_method_call(sema, method_call)?,
234            module_with_candidate: sema.scope(&candidate_node)?.module(),
235            candidate_node,
236        })
237    }
238
239    pub fn for_exact_path(
240        fully_qualified_path: &ast::Path,
241        sema: &Semantics<'db, RootDatabase>,
242    ) -> Option<Self> {
243        let candidate_node = fully_qualified_path.syntax().clone();
244        if let Some(use_tree) = candidate_node.ancestors().find_map(ast::UseTree::cast) {
245            // Path is inside a use tree, then only continue if it is the first segment of a use statement.
246            if use_tree.syntax().parent().and_then(ast::Use::cast).is_none()
247                || fully_qualified_path.qualifier().is_some()
248            {
249                return None;
250            }
251        }
252        Some(Self {
253            import_candidate: ImportCandidate::for_regular_path(sema, fully_qualified_path)?,
254            module_with_candidate: sema.scope(&candidate_node)?.module(),
255            candidate_node,
256        })
257    }
258
259    pub fn for_ident_pat(sema: &Semantics<'db, RootDatabase>, pat: &ast::IdentPat) -> Option<Self> {
260        if !pat.is_simple_ident() {
261            return None;
262        }
263        let name = pat.name()?;
264        let candidate_node = pat.syntax().clone();
265        Some(Self {
266            import_candidate: ImportCandidate::for_name(sema, &name)?,
267            module_with_candidate: sema.scope(&candidate_node)?.module(),
268            candidate_node,
269        })
270    }
271
272    pub fn for_fuzzy_path(
273        module_with_candidate: Module,
274        path: Option<&ast::Path>,
275        qualifier: Option<ast::Path>,
276        fuzzy_name: String,
277        sema: &Semantics<'db, RootDatabase>,
278        candidate_node: SyntaxNode,
279    ) -> Option<Self> {
280        Some(Self {
281            import_candidate: ImportCandidate::for_fuzzy_path(path, qualifier, fuzzy_name, sema)?,
282            module_with_candidate,
283            candidate_node,
284        })
285    }
286
287    pub fn for_fuzzy_method_call(
288        module_with_method_call: Module,
289        receiver_ty: Type<'db>,
290        fuzzy_method_name: String,
291        candidate_node: SyntaxNode,
292    ) -> Option<Self> {
293        Some(Self {
294            import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate {
295                receiver_ty,
296                assoc_item_name: NameToImport::fuzzy(fuzzy_method_name),
297            }),
298            module_with_candidate: module_with_method_call,
299            candidate_node,
300        })
301    }
302}
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
305pub struct CompleteInFlyimport(pub bool);
306
307/// An import (not necessary the only one) that corresponds a certain given [`PathImportCandidate`].
308/// (the structure is not entirely correct, since there can be situations requiring two imports, see FIXME below for the details)
309#[derive(Debug, Clone, PartialEq, Eq, Hash)]
310pub struct LocatedImport {
311    /// The path to use in the `use` statement for a given candidate to be imported.
312    pub import_path: ModPath,
313    /// An item that will be imported with the import path given.
314    pub item_to_import: ItemInNs,
315    /// The path import candidate, resolved.
316    ///
317    /// Not necessary matches the import:
318    /// For any associated constant from the trait, we try to access as `some::path::SomeStruct::ASSOC_`
319    /// the original item is the associated constant, but the import has to be a trait that
320    /// defines this constant.
321    pub original_item: ItemInNs,
322    /// The value of `#[rust_analyzer::completions(...)]`, if existing.
323    pub complete_in_flyimport: CompleteInFlyimport,
324}
325
326impl LocatedImport {
327    pub fn new(
328        import_path: ModPath,
329        item_to_import: ItemInNs,
330        original_item: ItemInNs,
331        complete_in_flyimport: CompleteInFlyimport,
332    ) -> Self {
333        Self { import_path, item_to_import, original_item, complete_in_flyimport }
334    }
335
336    pub fn new_no_completion(
337        import_path: ModPath,
338        item_to_import: ItemInNs,
339        original_item: ItemInNs,
340    ) -> Self {
341        Self {
342            import_path,
343            item_to_import,
344            original_item,
345            complete_in_flyimport: CompleteInFlyimport(true),
346        }
347    }
348}
349
350impl<'db> ImportAssets<'db> {
351    pub fn import_candidate(&self) -> &ImportCandidate<'db> {
352        &self.import_candidate
353    }
354
355    pub fn search_for_imports(
356        &self,
357        sema: &Semantics<'db, RootDatabase>,
358        cfg: ImportPathConfig,
359        prefix_kind: PrefixKind,
360    ) -> impl Iterator<Item = LocatedImport> {
361        let _p = tracing::info_span!("ImportAssets::search_for_imports").entered();
362        self.search_for(sema, Some(prefix_kind), cfg)
363    }
364
365    /// This may return non-absolute paths if a part of the returned path is already imported into scope.
366    pub fn search_for_relative_paths(
367        &self,
368        sema: &Semantics<'db, RootDatabase>,
369        cfg: ImportPathConfig,
370    ) -> impl Iterator<Item = LocatedImport> {
371        let _p = tracing::info_span!("ImportAssets::search_for_relative_paths").entered();
372        self.search_for(sema, None, cfg)
373    }
374
375    /// Requires imports to by prefix instead of fuzzily.
376    pub fn path_fuzzy_name_to_prefix(&mut self) {
377        if let ImportCandidate::Path(PathImportCandidate { name: to_import, .. }) =
378            &mut self.import_candidate
379        {
380            let (name, case_sensitive) = match to_import {
381                NameToImport::Fuzzy(name, case_sensitive) => {
382                    (std::mem::take(name), *case_sensitive)
383                }
384                _ => return,
385            };
386            *to_import = NameToImport::Prefix(name, case_sensitive);
387        }
388    }
389
390    /// Requires imports to match exactly instead of fuzzily.
391    pub fn path_fuzzy_name_to_exact(&mut self) {
392        if let ImportCandidate::Path(PathImportCandidate { name: to_import, .. }) =
393            &mut self.import_candidate
394        {
395            let (name, case_sensitive) = match to_import {
396                NameToImport::Fuzzy(name, case_sensitive) => {
397                    (std::mem::take(name), *case_sensitive)
398                }
399                _ => return,
400            };
401            *to_import = NameToImport::Exact(name, case_sensitive);
402        }
403    }
404
405    fn search_for(
406        &self,
407        sema: &Semantics<'db, RootDatabase>,
408        prefixed: Option<PrefixKind>,
409        cfg: ImportPathConfig,
410    ) -> impl Iterator<Item = LocatedImport> {
411        let _p = tracing::info_span!("ImportAssets::search_for").entered();
412
413        let scope = match sema.scope(&self.candidate_node) {
414            Some(it) => it,
415            None => return <FxIndexSet<_>>::default().into_iter(),
416        };
417        let cfg = FindPathConfig {
418            prefer_no_std: cfg.prefer_no_std,
419            prefer_prelude: cfg.prefer_prelude,
420            prefer_absolute: cfg.prefer_absolute,
421            allow_unstable: sema.is_nightly(scope.krate()),
422        };
423        let db = sema.db;
424        let krate = self.module_with_candidate.krate(sema.db);
425        let scope_definitions = self.scope_definitions(sema);
426        let mod_path = |item| {
427            get_mod_path(
428                db,
429                item_for_path_search(db, item)?,
430                &self.module_with_candidate,
431                prefixed,
432                cfg,
433            )
434            .filter(|path| path.len() > 1)
435        };
436
437        match &self.import_candidate {
438            ImportCandidate::Path(path_candidate) => path_applicable_imports(
439                db,
440                &scope,
441                krate,
442                path_candidate,
443                mod_path,
444                |item_to_import| !scope_definitions.contains(&ScopeDef::from(item_to_import)),
445            ),
446            ImportCandidate::TraitAssocItem(trait_candidate)
447            | ImportCandidate::TraitMethod(trait_candidate) => trait_applicable_items(
448                db,
449                krate,
450                &scope,
451                trait_candidate,
452                matches!(self.import_candidate, ImportCandidate::TraitAssocItem(_)),
453                mod_path,
454                |trait_to_import| {
455                    !scope_definitions
456                        .contains(&ScopeDef::ModuleDef(ModuleDef::Trait(trait_to_import)))
457                },
458            ),
459        }
460        .into_iter()
461    }
462
463    fn scope_definitions(&self, sema: &Semantics<'_, RootDatabase>) -> FxHashSet<ScopeDef> {
464        let _p = tracing::info_span!("ImportAssets::scope_definitions").entered();
465        let mut scope_definitions = FxHashSet::default();
466        if let Some(scope) = sema.scope(&self.candidate_node) {
467            scope.process_all_names(&mut |_, scope_def| {
468                scope_definitions.insert(scope_def);
469            });
470        }
471        scope_definitions
472    }
473}
474
475fn path_applicable_imports(
476    db: &RootDatabase,
477    scope: &SemanticsScope<'_>,
478    current_crate: Crate,
479    path_candidate: &PathImportCandidate,
480    mod_path: impl Fn(ItemInNs) -> Option<ModPath> + Copy,
481    scope_filter: impl Fn(ItemInNs) -> bool + Copy,
482) -> FxIndexSet<LocatedImport> {
483    let _p = tracing::info_span!("ImportAssets::path_applicable_imports").entered();
484
485    let mut result = match &*path_candidate.qualifier {
486        [] => {
487            items_locator::items_with_name(
488                db,
489                current_crate,
490                path_candidate.name.clone(),
491                // FIXME: we could look up assoc items by the input and propose those in completion,
492                // but that requires more preparation first:
493                // * store non-trait assoc items in import_map to fully enable this lookup
494                // * ensure that does not degrade the performance (benchmark it)
495                // * write more logic to check for corresponding trait presence requirement (we're unable to flyimport multiple item right now)
496                // * improve the associated completion item matching and/or scoring to ensure no noisy completions appear
497                //
498                // see also an ignored test under FIXME comment in the qualify_path.rs module
499                AssocSearchMode::Exclude,
500            )
501            .filter(|(item, _)| {
502                filter_by_definition_kind(db, *item, &path_candidate.definition_kinds)
503            })
504            .filter_map(|(item, do_not_complete)| {
505                if !scope_filter(item) {
506                    return None;
507                }
508                let mod_path = mod_path(item)?;
509                Some(LocatedImport::new(
510                    mod_path,
511                    item,
512                    item,
513                    CompleteInFlyimport(do_not_complete != Complete::IgnoreFlyimport),
514                ))
515            })
516            .take(DEFAULT_QUERY_SEARCH_LIMIT)
517            .collect()
518        }
519        // we have some unresolved qualifier that we search an import for
520        // The key here is that whatever we import must form a resolved path for the remainder of
521        // what follows
522        // FIXME: This doesn't handle visibility
523        [first_qsegment, qualifier_rest @ ..] => items_locator::items_with_name(
524            db,
525            current_crate,
526            NameToImport::Exact(first_qsegment.as_str().to_owned(), true),
527            AssocSearchMode::Exclude,
528        )
529        .flat_map(|(item, do_not_complete)| {
530            // we found imports for `first_qsegment`, now we need to filter these imports by whether
531            // they result in resolving the rest of the path successfully
532            validate_resolvable(
533                db,
534                scope,
535                mod_path,
536                scope_filter,
537                &path_candidate.name,
538                item,
539                qualifier_rest,
540                CompleteInFlyimport(do_not_complete != Complete::IgnoreFlyimport),
541            )
542        })
543        .take(DEFAULT_QUERY_SEARCH_LIMIT)
544        .collect(),
545    };
546
547    filter_candidates_by_after_path(db, scope, path_candidate, &mut result);
548
549    result
550}
551
552fn filter_by_definition_kind(
553    db: &RootDatabase,
554    item: ItemInNs,
555    allowed: &PathDefinitionKinds,
556) -> bool {
557    let item = item.into_module_def();
558    let struct_per_kind = |struct_kind| {
559        allowed.structs_and_consts
560            || match struct_kind {
561                hir::StructKind::Record => allowed.records,
562                hir::StructKind::Tuple => allowed.value_namespace || allowed.tuple_structs,
563                hir::StructKind::Unit => allowed.value_namespace,
564            }
565    };
566    match item {
567        ModuleDef::Module(_) => allowed.modules,
568        ModuleDef::Function(_) => allowed.value_namespace,
569        ModuleDef::Adt(hir::Adt::Struct(item)) => {
570            allowed.type_namespace || struct_per_kind(item.kind(db))
571        }
572        ModuleDef::Adt(hir::Adt::Enum(_)) => allowed.type_namespace,
573        ModuleDef::Adt(hir::Adt::Union(_)) => {
574            allowed.type_namespace || allowed.records || allowed.structs_and_consts
575        }
576        ModuleDef::EnumVariant(item) => struct_per_kind(item.kind(db)),
577        ModuleDef::Const(_) => allowed.value_namespace || allowed.structs_and_consts,
578        ModuleDef::Static(_) => allowed.value_namespace,
579        ModuleDef::Trait(_) => allowed.type_namespace,
580        ModuleDef::TypeAlias(_) => allowed.type_namespace,
581        ModuleDef::BuiltinType(_) => allowed.type_namespace,
582        ModuleDef::Macro(item) => {
583            if item.is_fn_like(db) {
584                allowed.bang_macros
585            } else {
586                allowed.attr_macros
587            }
588        }
589    }
590}
591
592fn filter_candidates_by_after_path(
593    db: &RootDatabase,
594    scope: &SemanticsScope<'_>,
595    path_candidate: &PathImportCandidate,
596    imports: &mut FxIndexSet<LocatedImport>,
597) {
598    if imports.len() <= 1 {
599        // Short-circuit, as even if it doesn't match fully we want it.
600        return;
601    }
602
603    let Some((last_after, after_except_last)) = path_candidate.after.split_last() else {
604        return;
605    };
606
607    let original_imports = imports.clone();
608
609    let traits_in_scope = scope.visible_traits();
610    imports.retain(|import| {
611        let items = if after_except_last.is_empty() {
612            smallvec![import.original_item]
613        } else {
614            let ItemInNs::Types(ModuleDef::Module(item)) = import.original_item else {
615                return false;
616            };
617            // FIXME: This doesn't consider visibilities.
618            item.resolve_mod_path(db, after_except_last.iter().cloned())
619                .into_iter()
620                .flatten()
621                .collect::<SmallVec<[_; 3]>>()
622        };
623        items.into_iter().any(|item| {
624            let has_last_method = |ty: hir::Type<'_>| {
625                ty.iterate_path_candidates(db, scope, &traits_in_scope, Some(last_after), |_| {
626                    Some(())
627                })
628                .is_some()
629            };
630            // FIXME: A trait can have an assoc type that has a function/const, that's two segments before last.
631            match item {
632                // A module? Can we resolve one more segment?
633                ItemInNs::Types(ModuleDef::Module(module)) => module
634                    .resolve_mod_path(db, [last_after.clone()])
635                    .is_some_and(|mut it| it.any(|_| true)),
636                // And ADT/Type Alias? That might be a method.
637                ItemInNs::Types(ModuleDef::Adt(it)) => has_last_method(it.ty(db)),
638                ItemInNs::Types(ModuleDef::BuiltinType(it)) => has_last_method(it.ty(db)),
639                ItemInNs::Types(ModuleDef::TypeAlias(it)) => has_last_method(it.ty(db)),
640                // A trait? Might have an associated item.
641                ItemInNs::Types(ModuleDef::Trait(it)) => it
642                    .items(db)
643                    .into_iter()
644                    .any(|assoc_item| assoc_item.name(db) == Some(last_after.clone())),
645                // Other items? can't resolve one more segment.
646                _ => false,
647            }
648        })
649    });
650
651    if imports.is_empty() {
652        // Better one half-match than zero full matches.
653        *imports = original_imports;
654    }
655}
656
657/// Validates and builds an import for `resolved_qualifier` if the `unresolved_qualifier` appended
658/// to it resolves and there is a validate `candidate` after that.
659fn validate_resolvable(
660    db: &RootDatabase,
661    scope: &SemanticsScope<'_>,
662    mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
663    scope_filter: impl Fn(ItemInNs) -> bool,
664    candidate: &NameToImport,
665    resolved_qualifier: ItemInNs,
666    unresolved_qualifier: &[Name],
667    complete_in_flyimport: CompleteInFlyimport,
668) -> SmallVec<[LocatedImport; 1]> {
669    let _p = tracing::info_span!("ImportAssets::import_for_item").entered();
670
671    let qualifier = (|| {
672        let mut adjusted_resolved_qualifier = resolved_qualifier;
673        if !unresolved_qualifier.is_empty() {
674            match resolved_qualifier {
675                ItemInNs::Types(ModuleDef::Module(module)) => {
676                    adjusted_resolved_qualifier = module
677                        .resolve_mod_path(db, unresolved_qualifier.iter().cloned())?
678                        .next()?;
679                }
680                // can't resolve multiple segments for non-module item path bases
681                _ => return None,
682            }
683        }
684
685        match adjusted_resolved_qualifier {
686            ItemInNs::Types(def) => Some(def),
687            _ => None,
688        }
689    })();
690    let Some(qualifier) = qualifier else { return SmallVec::new() };
691    let Some(import_path_candidate) = mod_path(resolved_qualifier) else { return SmallVec::new() };
692    let mut result = SmallVec::new();
693    let ty = match qualifier {
694        ModuleDef::Module(module) => {
695            items_locator::items_with_name_in_module::<Infallible>(
696                db,
697                module,
698                candidate.clone(),
699                AssocSearchMode::Exclude,
700                |item| {
701                    if scope_filter(item) {
702                        result.push(LocatedImport::new(
703                            import_path_candidate.clone(),
704                            resolved_qualifier,
705                            item,
706                            complete_in_flyimport,
707                        ));
708                    }
709                    ControlFlow::Continue(())
710                },
711            );
712            return result;
713        }
714        // FIXME
715        ModuleDef::Trait(_) => return SmallVec::new(),
716        ModuleDef::TypeAlias(alias) => alias.ty(db),
717        ModuleDef::BuiltinType(builtin) => builtin.ty(db),
718        ModuleDef::Adt(adt) => adt.ty(db),
719        _ => return SmallVec::new(),
720    };
721    ty.iterate_path_candidates::<Infallible>(db, scope, &FxHashSet::default(), None, |assoc| {
722        // FIXME: Support extra trait imports
723        if assoc.container_or_implemented_trait(db).is_some() {
724            return None;
725        }
726        let name = assoc.name(db)?;
727        let is_match = match candidate {
728            NameToImport::Prefix(text, true) => name.as_str().starts_with(text),
729            NameToImport::Prefix(text, false) => {
730                name.as_str().chars().zip(text.chars()).all(|(name_char, candidate_char)| {
731                    name_char.eq_ignore_ascii_case(&candidate_char)
732                })
733            }
734            NameToImport::Exact(text, true) => name.as_str() == text,
735            NameToImport::Exact(text, false) => name.as_str().eq_ignore_ascii_case(text),
736            NameToImport::Fuzzy(text, true) => text.chars().all(|c| name.as_str().contains(c)),
737            NameToImport::Fuzzy(text, false) => text
738                .chars()
739                .all(|c| name.as_str().chars().any(|name_char| name_char.eq_ignore_ascii_case(&c))),
740        };
741        if !is_match {
742            return None;
743        }
744        result.push(LocatedImport::new(
745            import_path_candidate.clone(),
746            resolved_qualifier,
747            assoc_to_item(assoc),
748            complete_in_flyimport,
749        ));
750        None
751    });
752    result
753}
754
755pub fn item_for_path_search(db: &RootDatabase, item: ItemInNs) -> Option<ItemInNs> {
756    Some(match item {
757        ItemInNs::Types(_) | ItemInNs::Values(_) => match item_as_assoc(db, item) {
758            Some(assoc_item) => item_for_path_search_assoc(db, assoc_item)?,
759            None => item,
760        },
761        ItemInNs::Macros(_) => item,
762    })
763}
764
765fn item_for_path_search_assoc(db: &RootDatabase, assoc_item: AssocItem) -> Option<ItemInNs> {
766    Some(match assoc_item.container(db) {
767        AssocItemContainer::Trait(trait_) => ItemInNs::from(ModuleDef::from(trait_)),
768        AssocItemContainer::Impl(impl_) => {
769            ItemInNs::from(ModuleDef::from(impl_.self_ty(db).as_adt()?))
770        }
771    })
772}
773
774fn trait_applicable_items<'db>(
775    db: &'db RootDatabase,
776    current_crate: Crate,
777    scope: &SemanticsScope<'db>,
778    trait_candidate: &TraitImportCandidate<'db>,
779    trait_assoc_item: bool,
780    mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
781    scope_filter: impl Fn(hir::Trait) -> bool,
782) -> FxIndexSet<LocatedImport> {
783    let _p = tracing::info_span!("ImportAssets::trait_applicable_items").entered();
784
785    let inherent_traits = trait_candidate.receiver_ty.applicable_inherent_traits(db);
786    let env_traits = trait_candidate.receiver_ty.env_traits(db);
787    let related_traits = inherent_traits.chain(env_traits).collect::<FxHashSet<_>>();
788
789    let mut required_assoc_items = FxHashMap::default();
790    let mut trait_candidates: FxHashSet<_> = items_locator::items_with_name(
791        db,
792        current_crate,
793        trait_candidate.assoc_item_name.clone(),
794        AssocSearchMode::AssocItemsOnly,
795    )
796    .filter_map(|(input, do_not_complete)| Some((item_as_assoc(db, input)?, do_not_complete)))
797    .filter_map(|(assoc, do_not_complete)| {
798        if !trait_assoc_item && matches!(assoc, AssocItem::Const(_) | AssocItem::TypeAlias(_)) {
799            return None;
800        }
801
802        let assoc_item_trait = assoc.container_trait(db)?;
803        if related_traits.contains(&assoc_item_trait) {
804            return None;
805        }
806        required_assoc_items
807            .insert(assoc, CompleteInFlyimport(do_not_complete != Complete::IgnoreFlyimport));
808        Some(assoc_item_trait.into())
809    })
810    .collect();
811
812    let autoderef_method_receiver = {
813        let mut deref_chain = trait_candidate.receiver_ty.autoderef(db).collect::<Vec<_>>();
814        // As a last step, we can do array unsizing (that's the only unsizing that rustc does for method receivers!)
815        if let Some((ty, _len)) = deref_chain.last().and_then(|ty| ty.as_array(db)) {
816            let slice = Type::new_slice(ty);
817            deref_chain.push(slice);
818        }
819        deref_chain
820            .into_iter()
821            .flat_map(|ty| {
822                let fingerprint = ty.fingerprint_for_trait_impl()?;
823                let mut crates = vec![];
824
825                if let Some(adt) = ty.as_adt() {
826                    // Push crate where ADT was defined
827                    crates.push((adt.krate(db).into(), fingerprint));
828                }
829                // Always include environment crate
830                crates.push((ty.krate(db).into(), fingerprint));
831                Some(crates)
832            })
833            .flatten()
834            .unique()
835            .collect::<Vec<_>>()
836    };
837
838    // can be empty if the entire deref chain is has no valid trait impl fingerprints
839    if autoderef_method_receiver.is_empty() {
840        return Default::default();
841    }
842
843    // in order to handle implied bounds through an associated type, keep all traits if any
844    // type in the deref chain matches `SimplifiedType::Placeholder`. This fingerprint
845    // won't be in `TraitImpls` anyways, as `TraitImpls` only contains actual implementations.
846    if !autoderef_method_receiver
847        .iter()
848        .any(|(_, fingerprint)| matches!(fingerprint, hir::SimplifiedType::Placeholder))
849    {
850        trait_candidates.retain(|&candidate_trait_id| {
851            // we care about the following cases:
852            // 1. Trait's definition crate
853            // 2. Definition crates for all trait's generic arguments
854            //     a. This is recursive for fundamental types: `Into<Box<A>> for ()`` is OK, but
855            //        `Into<Vec<A>> for ()`` is *not*.
856            // 3. Receiver type definition crate
857            //    a. This is recursive for fundamental types
858            let defining_crate_for_trait = Trait::from(candidate_trait_id).krate(db);
859
860            let trait_impls_in_crate =
861                hir::TraitImpls::for_crate(db, defining_crate_for_trait.into());
862            let definitions_exist_in_trait_crate =
863                autoderef_method_receiver.iter().any(|(_, fingerprint)| {
864                    trait_impls_in_crate
865                        .has_impls_for_trait_and_self_ty(candidate_trait_id, fingerprint)
866                });
867            // this is a closure for laziness: if `definitions_exist_in_trait_crate` is true,
868            // we can avoid a second db lookup.
869            let definitions_exist_in_receiver_crate = || {
870                autoderef_method_receiver.iter().any(|(krate, fingerprint)| {
871                    hir::TraitImpls::for_crate(db, *krate)
872                        .has_impls_for_trait_and_self_ty(candidate_trait_id, fingerprint)
873                })
874            };
875
876            definitions_exist_in_trait_crate || definitions_exist_in_receiver_crate()
877        });
878    }
879
880    let mut located_imports = FxIndexSet::default();
881    let mut trait_import_paths = FxHashMap::default();
882
883    if trait_assoc_item {
884        trait_candidate.receiver_ty.iterate_path_candidates(
885            db,
886            scope,
887            &trait_candidates,
888            None,
889            |assoc| {
890                if let Some(&complete_in_flyimport) = required_assoc_items.get(&assoc) {
891                    let located_trait = assoc.container_trait(db).filter(|&it| scope_filter(it))?;
892                    let trait_item = ItemInNs::from(ModuleDef::from(located_trait));
893                    let import_path = trait_import_paths
894                        .entry(trait_item)
895                        .or_insert_with(|| mod_path(trait_item))
896                        .clone()?;
897                    located_imports.insert(LocatedImport::new(
898                        import_path,
899                        trait_item,
900                        assoc_to_item(assoc),
901                        complete_in_flyimport,
902                    ));
903                }
904                None::<()>
905            },
906        )
907    } else {
908        trait_candidate.receiver_ty.iterate_method_candidates_with_traits(
909            db,
910            scope,
911            &trait_candidates,
912            None,
913            |function| {
914                let assoc = function.as_assoc_item(db)?;
915                if let Some(&complete_in_flyimport) = required_assoc_items.get(&assoc) {
916                    let located_trait = assoc.container_trait(db).filter(|&it| scope_filter(it))?;
917                    let trait_item = ItemInNs::from(ModuleDef::from(located_trait));
918                    let import_path = trait_import_paths
919                        .entry(trait_item)
920                        .or_insert_with(|| mod_path(trait_item))
921                        .clone()?;
922                    located_imports.insert(LocatedImport::new(
923                        import_path,
924                        trait_item,
925                        assoc_to_item(assoc),
926                        complete_in_flyimport,
927                    ));
928                }
929                None::<()>
930            },
931        )
932    };
933
934    located_imports
935}
936
937fn assoc_to_item(assoc: AssocItem) -> ItemInNs {
938    match assoc {
939        AssocItem::Function(f) => ItemInNs::from(ModuleDef::from(f)),
940        AssocItem::Const(c) => ItemInNs::from(ModuleDef::from(c)),
941        AssocItem::TypeAlias(t) => ItemInNs::from(ModuleDef::from(t)),
942    }
943}
944
945#[tracing::instrument(skip_all)]
946fn get_mod_path(
947    db: &RootDatabase,
948    item_to_search: ItemInNs,
949    module_with_candidate: &Module,
950    prefixed: Option<PrefixKind>,
951    cfg: FindPathConfig,
952) -> Option<ModPath> {
953    if let Some(prefix_kind) = prefixed {
954        module_with_candidate.find_use_path(db, item_to_search, prefix_kind, cfg)
955    } else {
956        module_with_candidate.find_path(db, item_to_search, cfg)
957    }
958}
959
960impl<'db> ImportCandidate<'db> {
961    fn for_method_call(
962        sema: &Semantics<'db, RootDatabase>,
963        method_call: &ast::MethodCallExpr,
964    ) -> Option<Self> {
965        match sema.resolve_method_call(method_call) {
966            Some(_) => None,
967            None => Some(Self::TraitMethod(TraitImportCandidate {
968                receiver_ty: sema.type_of_expr(&method_call.receiver()?)?.adjusted(),
969                assoc_item_name: NameToImport::exact_case_sensitive(
970                    method_call.name_ref()?.to_string(),
971                ),
972            })),
973        }
974    }
975
976    fn for_regular_path(sema: &Semantics<'db, RootDatabase>, path: &ast::Path) -> Option<Self> {
977        if sema.resolve_path(path).is_some() {
978            return None;
979        }
980        let after = std::iter::successors(path.parent_path(), |it| it.parent_path())
981            .map(|seg| seg.segment()?.name_ref().map(|name| Name::new_root(&name.text())))
982            .collect::<Option<_>>()?;
983        path_import_candidate(
984            sema,
985            Some(path),
986            path.qualifier(),
987            NameToImport::exact_case_sensitive(path.segment()?.name_ref()?.to_string()),
988            after,
989        )
990    }
991
992    fn for_name(sema: &Semantics<'db, RootDatabase>, name: &ast::Name) -> Option<Self> {
993        if sema
994            .scope(name.syntax())?
995            .speculative_resolve(&make::ext::ident_path(&name.text()))
996            .is_some()
997        {
998            return None;
999        }
1000        Some(ImportCandidate::Path(PathImportCandidate {
1001            qualifier: vec![],
1002            name: NameToImport::exact_case_sensitive(name.to_string()),
1003            after: vec![],
1004            definition_kinds: PathDefinitionKinds::PATH_PAT_KINDS,
1005        }))
1006    }
1007
1008    fn for_fuzzy_path(
1009        path: Option<&ast::Path>,
1010        qualifier: Option<ast::Path>,
1011        fuzzy_name: String,
1012        sema: &Semantics<'db, RootDatabase>,
1013    ) -> Option<Self> {
1014        // Assume a fuzzy match does not want the segments after. Because... I guess why not?
1015        path_import_candidate(sema, path, qualifier, NameToImport::fuzzy(fuzzy_name), Vec::new())
1016    }
1017}
1018
1019fn path_import_candidate<'db>(
1020    sema: &Semantics<'db, RootDatabase>,
1021    path: Option<&ast::Path>,
1022    qualifier: Option<ast::Path>,
1023    name: NameToImport,
1024    after: Vec<Name>,
1025) -> Option<ImportCandidate<'db>> {
1026    let definition_kinds = path.map_or(PathDefinitionKinds::ALL_ENABLED, |path| {
1027        PathDefinitionKinds::deduce_from_path(path, matches!(name, NameToImport::Exact(..)))
1028    });
1029    Some(match qualifier {
1030        Some(qualifier) => match sema.resolve_path(&qualifier) {
1031            Some(PathResolution::Def(ModuleDef::BuiltinType(_))) | None => {
1032                if qualifier.first_qualifier().is_none_or(|it| sema.resolve_path(&it).is_none()) {
1033                    let qualifier = qualifier
1034                        .segments()
1035                        .map(|seg| seg.name_ref().map(|name| Name::new_root(&name.text())))
1036                        .collect::<Option<Vec<_>>>()?;
1037                    ImportCandidate::Path(PathImportCandidate {
1038                        qualifier,
1039                        name,
1040                        after,
1041                        definition_kinds,
1042                    })
1043                } else {
1044                    return None;
1045                }
1046            }
1047            Some(PathResolution::Def(ModuleDef::Adt(assoc_item_path))) => {
1048                ImportCandidate::TraitAssocItem(TraitImportCandidate {
1049                    receiver_ty: assoc_item_path.ty(sema.db),
1050                    assoc_item_name: name,
1051                })
1052            }
1053            Some(PathResolution::Def(ModuleDef::TypeAlias(alias))) => {
1054                let ty = alias.ty(sema.db);
1055                if ty.as_adt().is_some() {
1056                    ImportCandidate::TraitAssocItem(TraitImportCandidate {
1057                        receiver_ty: ty,
1058                        assoc_item_name: name,
1059                    })
1060                } else {
1061                    return None;
1062                }
1063            }
1064            Some(_) => return None,
1065        },
1066        None => ImportCandidate::Path(PathImportCandidate {
1067            qualifier: vec![],
1068            name,
1069            after,
1070            definition_kinds,
1071        }),
1072    })
1073}
1074
1075fn item_as_assoc(db: &RootDatabase, item: ItemInNs) -> Option<AssocItem> {
1076    item.into_module_def().as_assoc_item(db)
1077}