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 necessarily 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                        && !scope.can_use_trait_methods(trait_to_import)
458                },
459            ),
460        }
461        .into_iter()
462    }
463
464    fn scope_definitions(&self, sema: &Semantics<'_, RootDatabase>) -> FxHashSet<ScopeDef> {
465        let _p = tracing::info_span!("ImportAssets::scope_definitions").entered();
466        let mut scope_definitions = FxHashSet::default();
467        if let Some(scope) = sema.scope(&self.candidate_node) {
468            scope.process_all_names(&mut |_, scope_def| {
469                scope_definitions.insert(scope_def);
470            });
471        }
472        scope_definitions
473    }
474}
475
476fn path_applicable_imports(
477    db: &RootDatabase,
478    scope: &SemanticsScope<'_>,
479    current_crate: Crate,
480    path_candidate: &PathImportCandidate,
481    mod_path: impl Fn(ItemInNs) -> Option<ModPath> + Copy,
482    scope_filter: impl Fn(ItemInNs) -> bool + Copy,
483) -> FxIndexSet<LocatedImport> {
484    let _p = tracing::info_span!("ImportAssets::path_applicable_imports").entered();
485
486    let mut result = match &*path_candidate.qualifier {
487        [] => {
488            items_locator::items_with_name(
489                db,
490                current_crate,
491                path_candidate.name.clone(),
492                // FIXME: we could look up assoc items by the input and propose those in completion,
493                // but that requires more preparation first:
494                // * store non-trait assoc items in import_map to fully enable this lookup
495                // * ensure that does not degrade the performance (benchmark it)
496                // * write more logic to check for corresponding trait presence requirement (we're unable to flyimport multiple item right now)
497                // * improve the associated completion item matching and/or scoring to ensure no noisy completions appear
498                //
499                // see also an ignored test under FIXME comment in the qualify_path.rs module
500                AssocSearchMode::Exclude,
501            )
502            .filter(|(item, _)| {
503                filter_by_definition_kind(db, *item, &path_candidate.definition_kinds)
504            })
505            .filter_map(|(item, do_not_complete)| {
506                if !scope_filter(item) {
507                    return None;
508                }
509                let mod_path = mod_path(item)?;
510                Some(LocatedImport::new(
511                    mod_path,
512                    item,
513                    item,
514                    CompleteInFlyimport(do_not_complete != Complete::IgnoreFlyimport),
515                ))
516            })
517            .take(DEFAULT_QUERY_SEARCH_LIMIT)
518            .collect()
519        }
520        // we have some unresolved qualifier that we search an import for
521        // The key here is that whatever we import must form a resolved path for the remainder of
522        // what follows
523        // FIXME: This doesn't handle visibility
524        [first_qsegment, qualifier_rest @ ..] => items_locator::items_with_name(
525            db,
526            current_crate,
527            NameToImport::Exact(first_qsegment.as_str().to_owned(), true),
528            AssocSearchMode::Exclude,
529        )
530        .flat_map(|(item, do_not_complete)| {
531            // we found imports for `first_qsegment`, now we need to filter these imports by whether
532            // they result in resolving the rest of the path successfully
533            validate_resolvable(
534                db,
535                scope,
536                mod_path,
537                scope_filter,
538                &path_candidate.name,
539                item,
540                qualifier_rest,
541                CompleteInFlyimport(do_not_complete != Complete::IgnoreFlyimport),
542            )
543        })
544        .take(DEFAULT_QUERY_SEARCH_LIMIT)
545        .collect(),
546    };
547
548    filter_candidates_by_after_path(db, scope, path_candidate, &mut result);
549
550    result
551}
552
553fn filter_by_definition_kind(
554    db: &RootDatabase,
555    item: ItemInNs,
556    allowed: &PathDefinitionKinds,
557) -> bool {
558    let item = item.into_module_def();
559    let struct_per_kind = |struct_kind| {
560        allowed.structs_and_consts
561            || match struct_kind {
562                hir::StructKind::Record => allowed.records,
563                hir::StructKind::Tuple => allowed.value_namespace || allowed.tuple_structs,
564                hir::StructKind::Unit => allowed.value_namespace,
565            }
566    };
567    match item {
568        ModuleDef::Module(_) => allowed.modules,
569        ModuleDef::Function(_) => allowed.value_namespace,
570        ModuleDef::Adt(hir::Adt::Struct(item)) => {
571            allowed.type_namespace || struct_per_kind(item.kind(db))
572        }
573        ModuleDef::Adt(hir::Adt::Enum(_)) => allowed.type_namespace,
574        ModuleDef::Adt(hir::Adt::Union(_)) => {
575            allowed.type_namespace || allowed.records || allowed.structs_and_consts
576        }
577        ModuleDef::EnumVariant(item) => struct_per_kind(item.kind(db)),
578        ModuleDef::Const(_) => allowed.value_namespace || allowed.structs_and_consts,
579        ModuleDef::Static(_) => allowed.value_namespace,
580        ModuleDef::Trait(_) => allowed.type_namespace,
581        ModuleDef::TypeAlias(_) => allowed.type_namespace,
582        ModuleDef::BuiltinType(_) => allowed.type_namespace,
583        ModuleDef::Macro(item) => {
584            if item.is_fn_like(db) {
585                allowed.bang_macros
586            } else {
587                allowed.attr_macros
588            }
589        }
590    }
591}
592
593fn filter_candidates_by_after_path(
594    db: &RootDatabase,
595    scope: &SemanticsScope<'_>,
596    path_candidate: &PathImportCandidate,
597    imports: &mut FxIndexSet<LocatedImport>,
598) {
599    if imports.len() <= 1 {
600        // Short-circuit, as even if it doesn't match fully we want it.
601        return;
602    }
603
604    let Some((last_after, after_except_last)) = path_candidate.after.split_last() else {
605        return;
606    };
607
608    let original_imports = imports.clone();
609
610    let traits_in_scope = scope.visible_traits();
611    imports.retain(|import| {
612        let items = if after_except_last.is_empty() {
613            smallvec![import.original_item]
614        } else {
615            let ItemInNs::Types(ModuleDef::Module(item)) = import.original_item else {
616                return false;
617            };
618            // FIXME: This doesn't consider visibilities.
619            item.resolve_mod_path(db, after_except_last.iter().cloned())
620                .into_iter()
621                .flatten()
622                .collect::<SmallVec<[_; 3]>>()
623        };
624        items.into_iter().any(|item| {
625            let has_last_method = |ty: hir::Type<'_>| {
626                ty.iterate_path_candidates(db, scope, &traits_in_scope, Some(last_after), |_| {
627                    Some(())
628                })
629                .is_some()
630            };
631            // FIXME: A trait can have an assoc type that has a function/const, that's two segments before last.
632            match item {
633                // A module? Can we resolve one more segment?
634                ItemInNs::Types(ModuleDef::Module(module)) => module
635                    .resolve_mod_path(db, [last_after.clone()])
636                    .is_some_and(|mut it| it.any(|_| true)),
637                // And ADT/Type Alias? That might be a method.
638                ItemInNs::Types(ModuleDef::Adt(it)) => has_last_method(it.ty(db)),
639                ItemInNs::Types(ModuleDef::BuiltinType(it)) => has_last_method(it.ty(db)),
640                ItemInNs::Types(ModuleDef::TypeAlias(it)) => has_last_method(it.ty(db)),
641                // A trait? Might have an associated item.
642                ItemInNs::Types(ModuleDef::Trait(it)) => it
643                    .items(db)
644                    .into_iter()
645                    .any(|assoc_item| assoc_item.name(db) == Some(last_after.clone())),
646                // Other items? can't resolve one more segment.
647                _ => false,
648            }
649        })
650    });
651
652    if imports.is_empty() {
653        // Better one half-match than zero full matches.
654        *imports = original_imports;
655    }
656}
657
658/// Validates and builds an import for `resolved_qualifier` if the `unresolved_qualifier` appended
659/// to it resolves and there is a validate `candidate` after that.
660fn validate_resolvable(
661    db: &RootDatabase,
662    scope: &SemanticsScope<'_>,
663    mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
664    scope_filter: impl Fn(ItemInNs) -> bool,
665    candidate: &NameToImport,
666    resolved_qualifier: ItemInNs,
667    unresolved_qualifier: &[Name],
668    complete_in_flyimport: CompleteInFlyimport,
669) -> SmallVec<[LocatedImport; 1]> {
670    let _p = tracing::info_span!("ImportAssets::import_for_item").entered();
671
672    let qualifier = (|| {
673        let mut adjusted_resolved_qualifier = resolved_qualifier;
674        if !unresolved_qualifier.is_empty() {
675            match resolved_qualifier {
676                ItemInNs::Types(ModuleDef::Module(module)) => {
677                    adjusted_resolved_qualifier = module
678                        .resolve_mod_path(db, unresolved_qualifier.iter().cloned())?
679                        .next()?;
680                }
681                // can't resolve multiple segments for non-module item path bases
682                _ => return None,
683            }
684        }
685
686        match adjusted_resolved_qualifier {
687            ItemInNs::Types(def) => Some(def),
688            _ => None,
689        }
690    })();
691    let Some(qualifier) = qualifier else { return SmallVec::new() };
692    let Some(import_path_candidate) = mod_path(resolved_qualifier) else { return SmallVec::new() };
693    let mut result = SmallVec::new();
694    let ty = match qualifier {
695        ModuleDef::Module(module) => {
696            items_locator::items_with_name_in_module::<Infallible>(
697                db,
698                module,
699                candidate.clone(),
700                AssocSearchMode::Exclude,
701                |item| {
702                    if scope_filter(item) {
703                        result.push(LocatedImport::new(
704                            import_path_candidate.clone(),
705                            resolved_qualifier,
706                            item,
707                            complete_in_flyimport,
708                        ));
709                    }
710                    ControlFlow::Continue(())
711                },
712            );
713            return result;
714        }
715        // FIXME
716        ModuleDef::Trait(_) => return SmallVec::new(),
717        ModuleDef::TypeAlias(alias) => alias.ty(db),
718        ModuleDef::BuiltinType(builtin) => builtin.ty(db),
719        ModuleDef::Adt(adt) => adt.ty(db),
720        _ => return SmallVec::new(),
721    };
722    ty.iterate_path_candidates::<Infallible>(db, scope, &FxHashSet::default(), None, |assoc| {
723        // FIXME: Support extra trait imports
724        if assoc.container_or_implemented_trait(db).is_some() {
725            return None;
726        }
727        let name = assoc.name(db)?;
728        let is_match = match candidate {
729            NameToImport::Prefix(text, true) => name.as_str().starts_with(text),
730            NameToImport::Prefix(text, false) => {
731                name.as_str().chars().zip(text.chars()).all(|(name_char, candidate_char)| {
732                    name_char.eq_ignore_ascii_case(&candidate_char)
733                })
734            }
735            NameToImport::Exact(text, true) => name.as_str() == text,
736            NameToImport::Exact(text, false) => name.as_str().eq_ignore_ascii_case(text),
737            NameToImport::Fuzzy(text, true) => text.chars().all(|c| name.as_str().contains(c)),
738            NameToImport::Fuzzy(text, false) => text
739                .chars()
740                .all(|c| name.as_str().chars().any(|name_char| name_char.eq_ignore_ascii_case(&c))),
741        };
742        if !is_match {
743            return None;
744        }
745        result.push(LocatedImport::new(
746            import_path_candidate.clone(),
747            resolved_qualifier,
748            assoc_to_item(assoc),
749            complete_in_flyimport,
750        ));
751        None
752    });
753    result
754}
755
756pub fn item_for_path_search(db: &RootDatabase, item: ItemInNs) -> Option<ItemInNs> {
757    Some(match item {
758        ItemInNs::Types(_) | ItemInNs::Values(_) => match item_as_assoc(db, item) {
759            Some(assoc_item) => item_for_path_search_assoc(db, assoc_item)?,
760            None => item,
761        },
762        ItemInNs::Macros(_) => item,
763    })
764}
765
766fn item_for_path_search_assoc(db: &RootDatabase, assoc_item: AssocItem) -> Option<ItemInNs> {
767    Some(match assoc_item.container(db) {
768        AssocItemContainer::Trait(trait_) => ItemInNs::from(ModuleDef::from(trait_)),
769        AssocItemContainer::Impl(impl_) => {
770            ItemInNs::from(ModuleDef::from(impl_.self_ty(db).as_adt()?))
771        }
772    })
773}
774
775fn trait_applicable_items<'db>(
776    db: &'db RootDatabase,
777    current_crate: Crate,
778    scope: &SemanticsScope<'db>,
779    trait_candidate: &TraitImportCandidate<'db>,
780    trait_assoc_item: bool,
781    mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
782    scope_filter: impl Fn(hir::Trait) -> bool,
783) -> FxIndexSet<LocatedImport> {
784    let _p = tracing::info_span!("ImportAssets::trait_applicable_items").entered();
785
786    let inherent_traits = trait_candidate.receiver_ty.applicable_inherent_traits(db);
787    let env_traits = trait_candidate.receiver_ty.env_traits(db);
788    let related_traits = inherent_traits.chain(env_traits).collect::<FxHashSet<_>>();
789
790    let mut required_assoc_items = FxHashMap::default();
791    let mut trait_candidates: FxHashSet<_> = items_locator::items_with_name(
792        db,
793        current_crate,
794        trait_candidate.assoc_item_name.clone(),
795        AssocSearchMode::AssocItemsOnly,
796    )
797    .filter_map(|(input, do_not_complete)| Some((item_as_assoc(db, input)?, do_not_complete)))
798    .filter_map(|(assoc, do_not_complete)| {
799        if !trait_assoc_item && matches!(assoc, AssocItem::Const(_) | AssocItem::TypeAlias(_)) {
800            return None;
801        }
802
803        let assoc_item_trait = assoc.container_trait(db)?;
804        if related_traits.contains(&assoc_item_trait) {
805            return None;
806        }
807        required_assoc_items
808            .insert(assoc, CompleteInFlyimport(do_not_complete != Complete::IgnoreFlyimport));
809        Some(assoc_item_trait.into())
810    })
811    .collect();
812
813    let autoderef_method_receiver = {
814        let mut deref_chain = trait_candidate.receiver_ty.autoderef(db).collect::<Vec<_>>();
815        // As a last step, we can do array unsizing (that's the only unsizing that rustc does for method receivers!)
816        if let Some((ty, _len)) = deref_chain.last().and_then(|ty| ty.as_array(db)) {
817            let slice = Type::new_slice(ty);
818            deref_chain.push(slice);
819        }
820        deref_chain
821            .into_iter()
822            .flat_map(|ty| {
823                let fingerprint = ty.fingerprint_for_trait_impl()?;
824                let mut crates = vec![];
825
826                if let Some(adt) = ty.as_adt() {
827                    // Push crate where ADT was defined
828                    crates.push((adt.krate(db).into(), fingerprint));
829                }
830                // Always include environment crate
831                crates.push((ty.krate(db).into(), fingerprint));
832                Some(crates)
833            })
834            .flatten()
835            .unique()
836            .collect::<Vec<_>>()
837    };
838
839    // can be empty if the entire deref chain is has no valid trait impl fingerprints
840    if autoderef_method_receiver.is_empty() {
841        return Default::default();
842    }
843
844    // in order to handle implied bounds through an associated type, keep all traits if any
845    // type in the deref chain matches `SimplifiedType::Placeholder`. This fingerprint
846    // won't be in `TraitImpls` anyways, as `TraitImpls` only contains actual implementations.
847    if !autoderef_method_receiver
848        .iter()
849        .any(|(_, fingerprint)| matches!(fingerprint, hir::SimplifiedType::Placeholder))
850    {
851        trait_candidates.retain(|&candidate_trait_id| {
852            // we care about the following cases:
853            // 1. Trait's definition crate
854            // 2. Definition crates for all trait's generic arguments
855            //     a. This is recursive for fundamental types: `Into<Box<A>> for ()`` is OK, but
856            //        `Into<Vec<A>> for ()`` is *not*.
857            // 3. Receiver type definition crate
858            //    a. This is recursive for fundamental types
859            let defining_crate_for_trait = Trait::from(candidate_trait_id).krate(db);
860
861            let trait_impls_in_crate =
862                hir::TraitImpls::for_crate(db, defining_crate_for_trait.into());
863            let definitions_exist_in_trait_crate =
864                autoderef_method_receiver.iter().any(|(_, fingerprint)| {
865                    trait_impls_in_crate
866                        .has_impls_for_trait_and_self_ty(candidate_trait_id, fingerprint)
867                });
868            // this is a closure for laziness: if `definitions_exist_in_trait_crate` is true,
869            // we can avoid a second db lookup.
870            let definitions_exist_in_receiver_crate = || {
871                autoderef_method_receiver.iter().any(|(krate, fingerprint)| {
872                    hir::TraitImpls::for_crate(db, *krate)
873                        .has_impls_for_trait_and_self_ty(candidate_trait_id, fingerprint)
874                })
875            };
876
877            definitions_exist_in_trait_crate || definitions_exist_in_receiver_crate()
878        });
879    }
880
881    let mut located_imports = FxIndexSet::default();
882    let mut trait_import_paths = FxHashMap::default();
883
884    if trait_assoc_item {
885        trait_candidate.receiver_ty.iterate_path_candidates(
886            db,
887            scope,
888            &trait_candidates,
889            None,
890            |assoc| {
891                if let Some(&complete_in_flyimport) = required_assoc_items.get(&assoc) {
892                    let located_trait = assoc.container_trait(db).filter(|&it| scope_filter(it))?;
893                    let trait_item = ItemInNs::from(ModuleDef::from(located_trait));
894                    let import_path = trait_import_paths
895                        .entry(trait_item)
896                        .or_insert_with(|| mod_path(trait_item))
897                        .clone()?;
898                    located_imports.insert(LocatedImport::new(
899                        import_path,
900                        trait_item,
901                        assoc_to_item(assoc),
902                        complete_in_flyimport,
903                    ));
904                }
905                None::<()>
906            },
907        )
908    } else {
909        trait_candidate.receiver_ty.iterate_method_candidates_with_traits(
910            db,
911            scope,
912            &trait_candidates,
913            None,
914            |function| {
915                let assoc = function.as_assoc_item(db)?;
916                if let Some(&complete_in_flyimport) = required_assoc_items.get(&assoc) {
917                    let located_trait = assoc.container_trait(db).filter(|&it| scope_filter(it))?;
918                    let trait_item = ItemInNs::from(ModuleDef::from(located_trait));
919                    let import_path = trait_import_paths
920                        .entry(trait_item)
921                        .or_insert_with(|| mod_path(trait_item))
922                        .clone()?;
923                    located_imports.insert(LocatedImport::new(
924                        import_path,
925                        trait_item,
926                        assoc_to_item(assoc),
927                        complete_in_flyimport,
928                    ));
929                }
930                None::<()>
931            },
932        )
933    };
934
935    located_imports
936}
937
938fn assoc_to_item(assoc: AssocItem) -> ItemInNs {
939    match assoc {
940        AssocItem::Function(f) => ItemInNs::from(ModuleDef::from(f)),
941        AssocItem::Const(c) => ItemInNs::from(ModuleDef::from(c)),
942        AssocItem::TypeAlias(t) => ItemInNs::from(ModuleDef::from(t)),
943    }
944}
945
946#[tracing::instrument(skip_all)]
947fn get_mod_path(
948    db: &RootDatabase,
949    item_to_search: ItemInNs,
950    module_with_candidate: &Module,
951    prefixed: Option<PrefixKind>,
952    cfg: FindPathConfig,
953) -> Option<ModPath> {
954    if let Some(prefix_kind) = prefixed {
955        module_with_candidate.find_use_path(db, item_to_search, prefix_kind, cfg)
956    } else {
957        module_with_candidate.find_path(db, item_to_search, cfg)
958    }
959}
960
961impl<'db> ImportCandidate<'db> {
962    fn for_method_call(
963        sema: &Semantics<'db, RootDatabase>,
964        method_call: &ast::MethodCallExpr,
965    ) -> Option<Self> {
966        match sema.resolve_method_call(method_call) {
967            Some(_) => None,
968            None => Some(Self::TraitMethod(TraitImportCandidate {
969                receiver_ty: sema.type_of_expr(&method_call.receiver()?)?.adjusted(),
970                assoc_item_name: NameToImport::exact_case_sensitive(
971                    method_call.name_ref()?.to_string(),
972                ),
973            })),
974        }
975    }
976
977    fn for_regular_path(sema: &Semantics<'db, RootDatabase>, path: &ast::Path) -> Option<Self> {
978        if sema.resolve_path(path).is_some() {
979            return None;
980        }
981        let after = std::iter::successors(path.parent_path(), |it| it.parent_path())
982            .map(|seg| seg.segment()?.name_ref().map(|name| Name::new_root(&name.text())))
983            .collect::<Option<_>>()?;
984        path_import_candidate(
985            sema,
986            Some(path),
987            path.qualifier(),
988            NameToImport::exact_case_sensitive(path.segment()?.name_ref()?.to_string()),
989            after,
990        )
991    }
992
993    fn for_name(sema: &Semantics<'db, RootDatabase>, name: &ast::Name) -> Option<Self> {
994        if sema
995            .scope(name.syntax())?
996            .speculative_resolve(&make::ext::ident_path(&name.text()))
997            .is_some()
998        {
999            return None;
1000        }
1001        Some(ImportCandidate::Path(PathImportCandidate {
1002            qualifier: vec![],
1003            name: NameToImport::exact_case_sensitive(name.to_string()),
1004            after: vec![],
1005            definition_kinds: PathDefinitionKinds::PATH_PAT_KINDS,
1006        }))
1007    }
1008
1009    fn for_fuzzy_path(
1010        path: Option<&ast::Path>,
1011        qualifier: Option<ast::Path>,
1012        fuzzy_name: String,
1013        sema: &Semantics<'db, RootDatabase>,
1014    ) -> Option<Self> {
1015        // Assume a fuzzy match does not want the segments after. Because... I guess why not?
1016        path_import_candidate(sema, path, qualifier, NameToImport::fuzzy(fuzzy_name), Vec::new())
1017    }
1018}
1019
1020fn path_import_candidate<'db>(
1021    sema: &Semantics<'db, RootDatabase>,
1022    path: Option<&ast::Path>,
1023    qualifier: Option<ast::Path>,
1024    name: NameToImport,
1025    after: Vec<Name>,
1026) -> Option<ImportCandidate<'db>> {
1027    let definition_kinds = path.map_or(PathDefinitionKinds::ALL_ENABLED, |path| {
1028        PathDefinitionKinds::deduce_from_path(path, matches!(name, NameToImport::Exact(..)))
1029    });
1030    Some(match qualifier {
1031        Some(qualifier) => match sema.resolve_path(&qualifier) {
1032            Some(PathResolution::Def(ModuleDef::BuiltinType(_))) | None => {
1033                if qualifier.first_qualifier().is_none_or(|it| sema.resolve_path(&it).is_none()) {
1034                    let qualifier = qualifier
1035                        .segments()
1036                        .map(|seg| seg.name_ref().map(|name| Name::new_root(&name.text())))
1037                        .collect::<Option<Vec<_>>>()?;
1038                    ImportCandidate::Path(PathImportCandidate {
1039                        qualifier,
1040                        name,
1041                        after,
1042                        definition_kinds,
1043                    })
1044                } else {
1045                    return None;
1046                }
1047            }
1048            Some(PathResolution::Def(ModuleDef::Adt(assoc_item_path))) => {
1049                ImportCandidate::TraitAssocItem(TraitImportCandidate {
1050                    receiver_ty: assoc_item_path.ty(sema.db),
1051                    assoc_item_name: name,
1052                })
1053            }
1054            Some(PathResolution::Def(ModuleDef::TypeAlias(alias))) => {
1055                let ty = alias.ty(sema.db);
1056                if ty.as_adt().is_some() {
1057                    ImportCandidate::TraitAssocItem(TraitImportCandidate {
1058                        receiver_ty: ty,
1059                        assoc_item_name: name,
1060                    })
1061                } else {
1062                    return None;
1063                }
1064            }
1065            Some(_) => return None,
1066        },
1067        None => ImportCandidate::Path(PathImportCandidate {
1068            qualifier: vec![],
1069            name,
1070            after,
1071            definition_kinds,
1072        }),
1073    })
1074}
1075
1076fn item_as_assoc(db: &RootDatabase, item: ItemInNs) -> Option<AssocItem> {
1077    item.into_module_def().as_assoc_item(db)
1078}