ide_db/imports/
import_assets.rs

1//! Look up accessible paths for items.
2
3use std::ops::ControlFlow;
4
5use hir::{
6    AsAssocItem, AssocItem, AssocItemContainer, Complete, Crate, HasCrate, ImportPathConfig,
7    ItemInNs, ModPath, Module, ModuleDef, Name, PathResolution, PrefixKind, ScopeDef, Semantics,
8    SemanticsScope, Trait, TyFingerprint, Type, db::HirDatabase,
9};
10use itertools::Itertools;
11use rustc_hash::{FxHashMap, FxHashSet};
12use syntax::{
13    AstNode, SyntaxNode,
14    ast::{self, HasName, make},
15};
16
17use crate::{
18    FxIndexSet, RootDatabase,
19    items_locator::{self, AssocSearchMode, DEFAULT_QUERY_SEARCH_LIMIT},
20};
21
22/// A candidate for import, derived during various IDE activities:
23/// * completion with imports on the fly proposals
24/// * completion edit resolve requests
25/// * assists
26/// * etc.
27#[derive(Debug)]
28pub enum ImportCandidate<'db> {
29    /// A path, qualified (`std::collections::HashMap`) or not (`HashMap`).
30    Path(PathImportCandidate),
31    /// A trait associated function (with no self parameter) or an associated constant.
32    /// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type
33    /// and `name` is the `test_function`
34    TraitAssocItem(TraitImportCandidate<'db>),
35    /// A trait method with self parameter.
36    /// For 'test_enum.test_method()', `ty` is the `test_enum` expression type
37    /// and `name` is the `test_method`
38    TraitMethod(TraitImportCandidate<'db>),
39}
40
41/// A trait import needed for a given associated item access.
42/// For `some::path::SomeStruct::ASSOC_`, contains the
43/// type of `some::path::SomeStruct` and `ASSOC_` as the item name.
44#[derive(Debug)]
45pub struct TraitImportCandidate<'db> {
46    /// A type of the item that has the associated item accessed at.
47    pub receiver_ty: Type<'db>,
48    /// The associated item name that the trait to import should contain.
49    pub assoc_item_name: NameToImport,
50}
51
52/// Path import for a given name, qualified or not.
53#[derive(Debug)]
54pub struct PathImportCandidate {
55    /// Optional qualifier before name.
56    pub qualifier: Vec<Name>,
57    /// The name the item (struct, trait, enum, etc.) should have.
58    pub name: NameToImport,
59}
60
61/// A name that will be used during item lookups.
62#[derive(Debug, Clone)]
63pub enum NameToImport {
64    /// Requires items with names that exactly match the given string, bool indicates case-sensitivity.
65    Exact(String, bool),
66    /// Requires items with names that match the given string by prefix, bool indicates case-sensitivity.
67    Prefix(String, bool),
68    /// Requires items with names contain all letters from the string,
69    /// in the same order, but not necessary adjacent.
70    Fuzzy(String, bool),
71}
72
73impl NameToImport {
74    pub fn exact_case_sensitive(s: String) -> NameToImport {
75        let s = match s.strip_prefix("r#") {
76            Some(s) => s.to_owned(),
77            None => s,
78        };
79        NameToImport::Exact(s, true)
80    }
81
82    pub fn fuzzy(s: String) -> NameToImport {
83        let s = match s.strip_prefix("r#") {
84            Some(s) => s.to_owned(),
85            None => s,
86        };
87        // unless all chars are lowercase, we do a case sensitive search
88        let case_sensitive = s.chars().any(|c| c.is_uppercase());
89        NameToImport::Fuzzy(s, case_sensitive)
90    }
91
92    pub fn text(&self) -> &str {
93        match self {
94            NameToImport::Prefix(text, _)
95            | NameToImport::Exact(text, _)
96            | NameToImport::Fuzzy(text, _) => text.as_str(),
97        }
98    }
99}
100
101/// A struct to find imports in the project, given a certain name (or its part) and the context.
102#[derive(Debug)]
103pub struct ImportAssets<'db> {
104    import_candidate: ImportCandidate<'db>,
105    candidate_node: SyntaxNode,
106    module_with_candidate: Module,
107}
108
109impl<'db> ImportAssets<'db> {
110    pub fn for_method_call(
111        method_call: &ast::MethodCallExpr,
112        sema: &Semantics<'db, RootDatabase>,
113    ) -> Option<Self> {
114        let candidate_node = method_call.syntax().clone();
115        Some(Self {
116            import_candidate: ImportCandidate::for_method_call(sema, method_call)?,
117            module_with_candidate: sema.scope(&candidate_node)?.module(),
118            candidate_node,
119        })
120    }
121
122    pub fn for_exact_path(
123        fully_qualified_path: &ast::Path,
124        sema: &Semantics<'db, RootDatabase>,
125    ) -> Option<Self> {
126        let candidate_node = fully_qualified_path.syntax().clone();
127        if let Some(use_tree) = candidate_node.ancestors().find_map(ast::UseTree::cast) {
128            // Path is inside a use tree, then only continue if it is the first segment of a use statement.
129            if use_tree.syntax().parent().and_then(ast::Use::cast).is_none()
130                || fully_qualified_path.qualifier().is_some()
131            {
132                return None;
133            }
134        }
135        Some(Self {
136            import_candidate: ImportCandidate::for_regular_path(sema, fully_qualified_path)?,
137            module_with_candidate: sema.scope(&candidate_node)?.module(),
138            candidate_node,
139        })
140    }
141
142    pub fn for_ident_pat(sema: &Semantics<'db, RootDatabase>, pat: &ast::IdentPat) -> Option<Self> {
143        if !pat.is_simple_ident() {
144            return None;
145        }
146        let name = pat.name()?;
147        let candidate_node = pat.syntax().clone();
148        Some(Self {
149            import_candidate: ImportCandidate::for_name(sema, &name)?,
150            module_with_candidate: sema.scope(&candidate_node)?.module(),
151            candidate_node,
152        })
153    }
154
155    pub fn for_fuzzy_path(
156        module_with_candidate: Module,
157        qualifier: Option<ast::Path>,
158        fuzzy_name: String,
159        sema: &Semantics<'db, RootDatabase>,
160        candidate_node: SyntaxNode,
161    ) -> Option<Self> {
162        Some(Self {
163            import_candidate: ImportCandidate::for_fuzzy_path(qualifier, fuzzy_name, sema)?,
164            module_with_candidate,
165            candidate_node,
166        })
167    }
168
169    pub fn for_fuzzy_method_call(
170        module_with_method_call: Module,
171        receiver_ty: Type<'db>,
172        fuzzy_method_name: String,
173        candidate_node: SyntaxNode,
174    ) -> Option<Self> {
175        Some(Self {
176            import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate {
177                receiver_ty,
178                assoc_item_name: NameToImport::fuzzy(fuzzy_method_name),
179            }),
180            module_with_candidate: module_with_method_call,
181            candidate_node,
182        })
183    }
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
187pub struct CompleteInFlyimport(pub bool);
188
189/// An import (not necessary the only one) that corresponds a certain given [`PathImportCandidate`].
190/// (the structure is not entirely correct, since there can be situations requiring two imports, see FIXME below for the details)
191#[derive(Debug, Clone, PartialEq, Eq, Hash)]
192pub struct LocatedImport {
193    /// The path to use in the `use` statement for a given candidate to be imported.
194    pub import_path: ModPath,
195    /// An item that will be imported with the import path given.
196    pub item_to_import: ItemInNs,
197    /// The path import candidate, resolved.
198    ///
199    /// Not necessary matches the import:
200    /// For any associated constant from the trait, we try to access as `some::path::SomeStruct::ASSOC_`
201    /// the original item is the associated constant, but the import has to be a trait that
202    /// defines this constant.
203    pub original_item: ItemInNs,
204    /// The value of `#[rust_analyzer::completions(...)]`, if existing.
205    pub complete_in_flyimport: CompleteInFlyimport,
206}
207
208impl LocatedImport {
209    pub fn new(
210        import_path: ModPath,
211        item_to_import: ItemInNs,
212        original_item: ItemInNs,
213        complete_in_flyimport: CompleteInFlyimport,
214    ) -> Self {
215        Self { import_path, item_to_import, original_item, complete_in_flyimport }
216    }
217
218    pub fn new_no_completion(
219        import_path: ModPath,
220        item_to_import: ItemInNs,
221        original_item: ItemInNs,
222    ) -> Self {
223        Self {
224            import_path,
225            item_to_import,
226            original_item,
227            complete_in_flyimport: CompleteInFlyimport(true),
228        }
229    }
230}
231
232impl<'db> ImportAssets<'db> {
233    pub fn import_candidate(&self) -> &ImportCandidate<'db> {
234        &self.import_candidate
235    }
236
237    pub fn search_for_imports(
238        &self,
239        sema: &Semantics<'db, RootDatabase>,
240        cfg: ImportPathConfig,
241        prefix_kind: PrefixKind,
242    ) -> impl Iterator<Item = LocatedImport> {
243        let _p = tracing::info_span!("ImportAssets::search_for_imports").entered();
244        self.search_for(sema, Some(prefix_kind), cfg)
245    }
246
247    /// This may return non-absolute paths if a part of the returned path is already imported into scope.
248    pub fn search_for_relative_paths(
249        &self,
250        sema: &Semantics<'db, RootDatabase>,
251        cfg: ImportPathConfig,
252    ) -> impl Iterator<Item = LocatedImport> {
253        let _p = tracing::info_span!("ImportAssets::search_for_relative_paths").entered();
254        self.search_for(sema, None, cfg)
255    }
256
257    /// Requires imports to by prefix instead of fuzzily.
258    pub fn path_fuzzy_name_to_prefix(&mut self) {
259        if let ImportCandidate::Path(PathImportCandidate { name: to_import, .. }) =
260            &mut self.import_candidate
261        {
262            let (name, case_sensitive) = match to_import {
263                NameToImport::Fuzzy(name, case_sensitive) => {
264                    (std::mem::take(name), *case_sensitive)
265                }
266                _ => return,
267            };
268            *to_import = NameToImport::Prefix(name, case_sensitive);
269        }
270    }
271
272    /// Requires imports to match exactly instead of fuzzily.
273    pub fn path_fuzzy_name_to_exact(&mut self) {
274        if let ImportCandidate::Path(PathImportCandidate { name: to_import, .. }) =
275            &mut self.import_candidate
276        {
277            let (name, case_sensitive) = match to_import {
278                NameToImport::Fuzzy(name, case_sensitive) => {
279                    (std::mem::take(name), *case_sensitive)
280                }
281                _ => return,
282            };
283            *to_import = NameToImport::Exact(name, case_sensitive);
284        }
285    }
286
287    fn search_for(
288        &self,
289        sema: &Semantics<'db, RootDatabase>,
290        prefixed: Option<PrefixKind>,
291        cfg: ImportPathConfig,
292    ) -> impl Iterator<Item = LocatedImport> {
293        let _p = tracing::info_span!("ImportAssets::search_for").entered();
294
295        let scope = match sema.scope(&self.candidate_node) {
296            Some(it) => it,
297            None => return <FxIndexSet<_>>::default().into_iter(),
298        };
299        let db = sema.db;
300        let krate = self.module_with_candidate.krate();
301        let scope_definitions = self.scope_definitions(sema);
302        let mod_path = |item| {
303            get_mod_path(
304                db,
305                item_for_path_search(db, item)?,
306                &self.module_with_candidate,
307                prefixed,
308                cfg,
309            )
310            .filter(|path| path.len() > 1)
311        };
312
313        match &self.import_candidate {
314            ImportCandidate::Path(path_candidate) => path_applicable_imports(
315                db,
316                &scope,
317                krate,
318                path_candidate,
319                mod_path,
320                |item_to_import| !scope_definitions.contains(&ScopeDef::from(item_to_import)),
321            ),
322            ImportCandidate::TraitAssocItem(trait_candidate)
323            | ImportCandidate::TraitMethod(trait_candidate) => trait_applicable_items(
324                db,
325                krate,
326                &scope,
327                trait_candidate,
328                matches!(self.import_candidate, ImportCandidate::TraitAssocItem(_)),
329                mod_path,
330                |trait_to_import| {
331                    !scope_definitions
332                        .contains(&ScopeDef::ModuleDef(ModuleDef::Trait(trait_to_import)))
333                },
334            ),
335        }
336        .into_iter()
337    }
338
339    fn scope_definitions(&self, sema: &Semantics<'_, RootDatabase>) -> FxHashSet<ScopeDef> {
340        let _p = tracing::info_span!("ImportAssets::scope_definitions").entered();
341        let mut scope_definitions = FxHashSet::default();
342        if let Some(scope) = sema.scope(&self.candidate_node) {
343            scope.process_all_names(&mut |_, scope_def| {
344                scope_definitions.insert(scope_def);
345            });
346        }
347        scope_definitions
348    }
349}
350
351fn path_applicable_imports(
352    db: &RootDatabase,
353    scope: &SemanticsScope<'_>,
354    current_crate: Crate,
355    path_candidate: &PathImportCandidate,
356    mod_path: impl Fn(ItemInNs) -> Option<ModPath> + Copy,
357    scope_filter: impl Fn(ItemInNs) -> bool + Copy,
358) -> FxIndexSet<LocatedImport> {
359    let _p = tracing::info_span!("ImportAssets::path_applicable_imports").entered();
360
361    match &*path_candidate.qualifier {
362        [] => {
363            items_locator::items_with_name(
364                db,
365                current_crate,
366                path_candidate.name.clone(),
367                // FIXME: we could look up assoc items by the input and propose those in completion,
368                // but that requires more preparation first:
369                // * store non-trait assoc items in import_map to fully enable this lookup
370                // * ensure that does not degrade the performance (benchmark it)
371                // * write more logic to check for corresponding trait presence requirement (we're unable to flyimport multiple item right now)
372                // * improve the associated completion item matching and/or scoring to ensure no noisy completions appear
373                //
374                // see also an ignored test under FIXME comment in the qualify_path.rs module
375                AssocSearchMode::Exclude,
376            )
377            .filter_map(|(item, do_not_complete)| {
378                if !scope_filter(item) {
379                    return None;
380                }
381                let mod_path = mod_path(item)?;
382                Some(LocatedImport::new(
383                    mod_path,
384                    item,
385                    item,
386                    CompleteInFlyimport(do_not_complete != Complete::IgnoreFlyimport),
387                ))
388            })
389            .take(DEFAULT_QUERY_SEARCH_LIMIT)
390            .collect()
391        }
392        // we have some unresolved qualifier that we search an import for
393        // The key here is that whatever we import must form a resolved path for the remainder of
394        // what follows
395        // FIXME: This doesn't handle visibility
396        [first_qsegment, qualifier_rest @ ..] => items_locator::items_with_name(
397            db,
398            current_crate,
399            NameToImport::Exact(first_qsegment.as_str().to_owned(), true),
400            AssocSearchMode::Exclude,
401        )
402        .filter_map(|(item, do_not_complete)| {
403            // we found imports for `first_qsegment`, now we need to filter these imports by whether
404            // they result in resolving the rest of the path successfully
405            validate_resolvable(
406                db,
407                scope,
408                mod_path,
409                scope_filter,
410                &path_candidate.name,
411                item,
412                qualifier_rest,
413                CompleteInFlyimport(do_not_complete != Complete::IgnoreFlyimport),
414            )
415        })
416        .take(DEFAULT_QUERY_SEARCH_LIMIT)
417        .collect(),
418    }
419}
420
421/// Validates and builds an import for `resolved_qualifier` if the `unresolved_qualifier` appended
422/// to it resolves and there is a validate `candidate` after that.
423fn validate_resolvable(
424    db: &RootDatabase,
425    scope: &SemanticsScope<'_>,
426    mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
427    scope_filter: impl Fn(ItemInNs) -> bool,
428    candidate: &NameToImport,
429    resolved_qualifier: ItemInNs,
430    unresolved_qualifier: &[Name],
431    complete_in_flyimport: CompleteInFlyimport,
432) -> Option<LocatedImport> {
433    let _p = tracing::info_span!("ImportAssets::import_for_item").entered();
434
435    let qualifier = {
436        let mut adjusted_resolved_qualifier = resolved_qualifier;
437        if !unresolved_qualifier.is_empty() {
438            match resolved_qualifier {
439                ItemInNs::Types(ModuleDef::Module(module)) => {
440                    adjusted_resolved_qualifier = module
441                        .resolve_mod_path(db, unresolved_qualifier.iter().cloned())?
442                        .next()?;
443                }
444                // can't resolve multiple segments for non-module item path bases
445                _ => return None,
446            }
447        }
448
449        match adjusted_resolved_qualifier {
450            ItemInNs::Types(def) => def,
451            _ => return None,
452        }
453    };
454    let import_path_candidate = mod_path(resolved_qualifier)?;
455    let ty = match qualifier {
456        ModuleDef::Module(module) => {
457            return items_locator::items_with_name_in_module(
458                db,
459                module,
460                candidate.clone(),
461                AssocSearchMode::Exclude,
462                |it| match scope_filter(it) {
463                    true => ControlFlow::Break(it),
464                    false => ControlFlow::Continue(()),
465                },
466            )
467            .map(|item| {
468                LocatedImport::new(
469                    import_path_candidate,
470                    resolved_qualifier,
471                    item,
472                    complete_in_flyimport,
473                )
474            });
475        }
476        // FIXME
477        ModuleDef::Trait(_) => return None,
478        ModuleDef::TypeAlias(alias) => alias.ty(db),
479        ModuleDef::BuiltinType(builtin) => builtin.ty(db),
480        ModuleDef::Adt(adt) => adt.ty(db),
481        _ => return None,
482    };
483    ty.iterate_path_candidates(db, scope, &FxHashSet::default(), None, None, |assoc| {
484        // FIXME: Support extra trait imports
485        if assoc.container_or_implemented_trait(db).is_some() {
486            return None;
487        }
488        let name = assoc.name(db)?;
489        let is_match = match candidate {
490            NameToImport::Prefix(text, true) => name.as_str().starts_with(text),
491            NameToImport::Prefix(text, false) => {
492                name.as_str().chars().zip(text.chars()).all(|(name_char, candidate_char)| {
493                    name_char.eq_ignore_ascii_case(&candidate_char)
494                })
495            }
496            NameToImport::Exact(text, true) => name.as_str() == text,
497            NameToImport::Exact(text, false) => name.as_str().eq_ignore_ascii_case(text),
498            NameToImport::Fuzzy(text, true) => text.chars().all(|c| name.as_str().contains(c)),
499            NameToImport::Fuzzy(text, false) => text
500                .chars()
501                .all(|c| name.as_str().chars().any(|name_char| name_char.eq_ignore_ascii_case(&c))),
502        };
503        if !is_match {
504            return None;
505        }
506        Some(LocatedImport::new(
507            import_path_candidate.clone(),
508            resolved_qualifier,
509            assoc_to_item(assoc),
510            complete_in_flyimport,
511        ))
512    })
513}
514
515pub fn item_for_path_search(db: &RootDatabase, item: ItemInNs) -> Option<ItemInNs> {
516    Some(match item {
517        ItemInNs::Types(_) | ItemInNs::Values(_) => match item_as_assoc(db, item) {
518            Some(assoc_item) => item_for_path_search_assoc(db, assoc_item)?,
519            None => item,
520        },
521        ItemInNs::Macros(_) => item,
522    })
523}
524
525fn item_for_path_search_assoc(db: &RootDatabase, assoc_item: AssocItem) -> Option<ItemInNs> {
526    Some(match assoc_item.container(db) {
527        AssocItemContainer::Trait(trait_) => ItemInNs::from(ModuleDef::from(trait_)),
528        AssocItemContainer::Impl(impl_) => {
529            ItemInNs::from(ModuleDef::from(impl_.self_ty(db).as_adt()?))
530        }
531    })
532}
533
534fn trait_applicable_items<'db>(
535    db: &'db RootDatabase,
536    current_crate: Crate,
537    scope: &SemanticsScope<'db>,
538    trait_candidate: &TraitImportCandidate<'db>,
539    trait_assoc_item: bool,
540    mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
541    scope_filter: impl Fn(hir::Trait) -> bool,
542) -> FxIndexSet<LocatedImport> {
543    let _p = tracing::info_span!("ImportAssets::trait_applicable_items").entered();
544
545    let inherent_traits = trait_candidate.receiver_ty.applicable_inherent_traits(db);
546    let env_traits = trait_candidate.receiver_ty.env_traits(db);
547    let related_traits = inherent_traits.chain(env_traits).collect::<FxHashSet<_>>();
548
549    let mut required_assoc_items = FxHashMap::default();
550    let mut trait_candidates: FxHashSet<_> = items_locator::items_with_name(
551        db,
552        current_crate,
553        trait_candidate.assoc_item_name.clone(),
554        AssocSearchMode::AssocItemsOnly,
555    )
556    .filter_map(|(input, do_not_complete)| Some((item_as_assoc(db, input)?, do_not_complete)))
557    .filter_map(|(assoc, do_not_complete)| {
558        if !trait_assoc_item && matches!(assoc, AssocItem::Const(_) | AssocItem::TypeAlias(_)) {
559            return None;
560        }
561
562        let assoc_item_trait = assoc.container_trait(db)?;
563        if related_traits.contains(&assoc_item_trait) {
564            return None;
565        }
566        required_assoc_items
567            .insert(assoc, CompleteInFlyimport(do_not_complete != Complete::IgnoreFlyimport));
568        Some(assoc_item_trait.into())
569    })
570    .collect();
571
572    let autoderef_method_receiver = {
573        let mut deref_chain = trait_candidate.receiver_ty.autoderef(db).collect::<Vec<_>>();
574        // As a last step, we can do array unsizing (that's the only unsizing that rustc does for method receivers!)
575        if let Some((ty, _len)) = deref_chain.last().and_then(|ty| ty.as_array(db)) {
576            let slice = Type::new_slice(ty);
577            deref_chain.push(slice);
578        }
579        deref_chain
580            .into_iter()
581            .filter_map(|ty| Some((ty.krate(db).into(), ty.fingerprint_for_trait_impl()?)))
582            .sorted()
583            .unique()
584            .collect::<Vec<_>>()
585    };
586
587    // can be empty if the entire deref chain is has no valid trait impl fingerprints
588    if autoderef_method_receiver.is_empty() {
589        return Default::default();
590    }
591
592    // in order to handle implied bounds through an associated type, keep all traits if any
593    // type in the deref chain matches `TyFingerprint::Unnameable`. This fingerprint
594    // won't be in `TraitImpls` anyways, as `TraitImpls` only contains actual implementations.
595    if !autoderef_method_receiver
596        .iter()
597        .any(|(_, fingerprint)| matches!(fingerprint, TyFingerprint::Unnameable))
598    {
599        trait_candidates.retain(|&candidate_trait_id| {
600            // we care about the following cases:
601            // 1. Trait's definition crate
602            // 2. Definition crates for all trait's generic arguments
603            //     a. This is recursive for fundamental types: `Into<Box<A>> for ()`` is OK, but
604            //        `Into<Vec<A>> for ()`` is *not*.
605            // 3. Receiver type definition crate
606            //    a. This is recursive for fundamental types
607            let defining_crate_for_trait = Trait::from(candidate_trait_id).krate(db);
608
609            let trait_impls_in_crate = db.trait_impls_in_crate(defining_crate_for_trait.into());
610            let definitions_exist_in_trait_crate =
611                autoderef_method_receiver.iter().any(|&(_, fingerprint)| {
612                    trait_impls_in_crate
613                        .has_impls_for_trait_and_self_ty(candidate_trait_id, fingerprint)
614                });
615            // this is a closure for laziness: if `definitions_exist_in_trait_crate` is true,
616            // we can avoid a second db lookup.
617            let definitions_exist_in_receiver_crate = || {
618                autoderef_method_receiver.iter().any(|&(krate, fingerprint)| {
619                    db.trait_impls_in_crate(krate)
620                        .has_impls_for_trait_and_self_ty(candidate_trait_id, fingerprint)
621                })
622            };
623
624            definitions_exist_in_trait_crate || definitions_exist_in_receiver_crate()
625        });
626    }
627
628    let mut located_imports = FxIndexSet::default();
629    let mut trait_import_paths = FxHashMap::default();
630
631    if trait_assoc_item {
632        trait_candidate.receiver_ty.iterate_path_candidates(
633            db,
634            scope,
635            &trait_candidates,
636            None,
637            None,
638            |assoc| {
639                if let Some(&complete_in_flyimport) = required_assoc_items.get(&assoc) {
640                    let located_trait = assoc.container_trait(db).filter(|&it| scope_filter(it))?;
641                    let trait_item = ItemInNs::from(ModuleDef::from(located_trait));
642                    let import_path = trait_import_paths
643                        .entry(trait_item)
644                        .or_insert_with(|| mod_path(trait_item))
645                        .clone()?;
646                    located_imports.insert(LocatedImport::new(
647                        import_path,
648                        trait_item,
649                        assoc_to_item(assoc),
650                        complete_in_flyimport,
651                    ));
652                }
653                None::<()>
654            },
655        )
656    } else {
657        trait_candidate.receiver_ty.iterate_method_candidates_with_traits(
658            db,
659            scope,
660            &trait_candidates,
661            None,
662            None,
663            |function| {
664                let assoc = function.as_assoc_item(db)?;
665                if let Some(&complete_in_flyimport) = required_assoc_items.get(&assoc) {
666                    let located_trait = assoc.container_trait(db).filter(|&it| scope_filter(it))?;
667                    let trait_item = ItemInNs::from(ModuleDef::from(located_trait));
668                    let import_path = trait_import_paths
669                        .entry(trait_item)
670                        .or_insert_with(|| mod_path(trait_item))
671                        .clone()?;
672                    located_imports.insert(LocatedImport::new(
673                        import_path,
674                        trait_item,
675                        assoc_to_item(assoc),
676                        complete_in_flyimport,
677                    ));
678                }
679                None::<()>
680            },
681        )
682    };
683
684    located_imports
685}
686
687fn assoc_to_item(assoc: AssocItem) -> ItemInNs {
688    match assoc {
689        AssocItem::Function(f) => ItemInNs::from(ModuleDef::from(f)),
690        AssocItem::Const(c) => ItemInNs::from(ModuleDef::from(c)),
691        AssocItem::TypeAlias(t) => ItemInNs::from(ModuleDef::from(t)),
692    }
693}
694
695#[tracing::instrument(skip_all)]
696fn get_mod_path(
697    db: &RootDatabase,
698    item_to_search: ItemInNs,
699    module_with_candidate: &Module,
700    prefixed: Option<PrefixKind>,
701    cfg: ImportPathConfig,
702) -> Option<ModPath> {
703    if let Some(prefix_kind) = prefixed {
704        module_with_candidate.find_use_path(db, item_to_search, prefix_kind, cfg)
705    } else {
706        module_with_candidate.find_path(db, item_to_search, cfg)
707    }
708}
709
710impl<'db> ImportCandidate<'db> {
711    fn for_method_call(
712        sema: &Semantics<'db, RootDatabase>,
713        method_call: &ast::MethodCallExpr,
714    ) -> Option<Self> {
715        match sema.resolve_method_call(method_call) {
716            Some(_) => None,
717            None => Some(Self::TraitMethod(TraitImportCandidate {
718                receiver_ty: sema.type_of_expr(&method_call.receiver()?)?.adjusted(),
719                assoc_item_name: NameToImport::exact_case_sensitive(
720                    method_call.name_ref()?.to_string(),
721                ),
722            })),
723        }
724    }
725
726    fn for_regular_path(sema: &Semantics<'db, RootDatabase>, path: &ast::Path) -> Option<Self> {
727        if sema.resolve_path(path).is_some() {
728            return None;
729        }
730        path_import_candidate(
731            sema,
732            path.qualifier(),
733            NameToImport::exact_case_sensitive(path.segment()?.name_ref()?.to_string()),
734        )
735    }
736
737    fn for_name(sema: &Semantics<'db, RootDatabase>, name: &ast::Name) -> Option<Self> {
738        if sema
739            .scope(name.syntax())?
740            .speculative_resolve(&make::ext::ident_path(&name.text()))
741            .is_some()
742        {
743            return None;
744        }
745        Some(ImportCandidate::Path(PathImportCandidate {
746            qualifier: vec![],
747            name: NameToImport::exact_case_sensitive(name.to_string()),
748        }))
749    }
750
751    fn for_fuzzy_path(
752        qualifier: Option<ast::Path>,
753        fuzzy_name: String,
754        sema: &Semantics<'db, RootDatabase>,
755    ) -> Option<Self> {
756        path_import_candidate(sema, qualifier, NameToImport::fuzzy(fuzzy_name))
757    }
758}
759
760fn path_import_candidate<'db>(
761    sema: &Semantics<'db, RootDatabase>,
762    qualifier: Option<ast::Path>,
763    name: NameToImport,
764) -> Option<ImportCandidate<'db>> {
765    Some(match qualifier {
766        Some(qualifier) => match sema.resolve_path(&qualifier) {
767            Some(PathResolution::Def(ModuleDef::BuiltinType(_))) | None => {
768                if qualifier.first_qualifier().is_none_or(|it| sema.resolve_path(&it).is_none()) {
769                    let qualifier = qualifier
770                        .segments()
771                        .map(|seg| seg.name_ref().map(|name| Name::new_root(&name.text())))
772                        .collect::<Option<Vec<_>>>()?;
773                    ImportCandidate::Path(PathImportCandidate { qualifier, name })
774                } else {
775                    return None;
776                }
777            }
778            Some(PathResolution::Def(ModuleDef::Adt(assoc_item_path))) => {
779                ImportCandidate::TraitAssocItem(TraitImportCandidate {
780                    receiver_ty: assoc_item_path.ty(sema.db),
781                    assoc_item_name: name,
782                })
783            }
784            Some(PathResolution::Def(ModuleDef::TypeAlias(alias))) => {
785                let ty = alias.ty(sema.db);
786                if ty.as_adt().is_some() {
787                    ImportCandidate::TraitAssocItem(TraitImportCandidate {
788                        receiver_ty: ty,
789                        assoc_item_name: name,
790                    })
791                } else {
792                    return None;
793                }
794            }
795            Some(_) => return None,
796        },
797        None => ImportCandidate::Path(PathImportCandidate { qualifier: vec![], name }),
798    })
799}
800
801fn item_as_assoc(db: &RootDatabase, item: ItemInNs) -> Option<AssocItem> {
802    item.into_module_def().as_assoc_item(db)
803}