]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs
Rollup merge of #98200 - ouz-a:issue-98177, r=oli-obk
[rust.git] / src / tools / rust-analyzer / crates / ide-db / src / imports / import_assets.rs
1 //! Look up accessible paths for items.
2 use hir::{
3     AsAssocItem, AssocItem, AssocItemContainer, Crate, ItemInNs, ModPath, Module, ModuleDef,
4     PathResolution, PrefixKind, ScopeDef, Semantics, SemanticsScope, Type,
5 };
6 use itertools::Itertools;
7 use rustc_hash::FxHashSet;
8 use syntax::{
9     ast::{self, HasName},
10     utils::path_to_string_stripping_turbo_fish,
11     AstNode, SyntaxNode,
12 };
13
14 use crate::{
15     helpers::item_name,
16     items_locator::{self, AssocItemSearch, DEFAULT_QUERY_SEARCH_LIMIT},
17     RootDatabase,
18 };
19
20 /// A candidate for import, derived during various IDE activities:
21 /// * completion with imports on the fly proposals
22 /// * completion edit resolve requests
23 /// * assists
24 /// * etc.
25 #[derive(Debug)]
26 pub enum ImportCandidate {
27     /// A path, qualified (`std::collections::HashMap`) or not (`HashMap`).
28     Path(PathImportCandidate),
29     /// A trait associated function (with no self parameter) or an associated constant.
30     /// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type
31     /// and `name` is the `test_function`
32     TraitAssocItem(TraitImportCandidate),
33     /// A trait method with self parameter.
34     /// For 'test_enum.test_method()', `ty` is the `test_enum` expression type
35     /// and `name` is the `test_method`
36     TraitMethod(TraitImportCandidate),
37 }
38
39 /// A trait import needed for a given associated item access.
40 /// For `some::path::SomeStruct::ASSOC_`, contains the
41 /// type of `some::path::SomeStruct` and `ASSOC_` as the item name.
42 #[derive(Debug)]
43 pub struct TraitImportCandidate {
44     /// A type of the item that has the associated item accessed at.
45     pub receiver_ty: Type,
46     /// The associated item name that the trait to import should contain.
47     pub assoc_item_name: NameToImport,
48 }
49
50 /// Path import for a given name, qualified or not.
51 #[derive(Debug)]
52 pub struct PathImportCandidate {
53     /// Optional qualifier before name.
54     pub qualifier: Option<FirstSegmentUnresolved>,
55     /// The name the item (struct, trait, enum, etc.) should have.
56     pub name: NameToImport,
57 }
58
59 /// A qualifier that has a first segment and it's unresolved.
60 #[derive(Debug)]
61 pub struct FirstSegmentUnresolved {
62     fist_segment: ast::NameRef,
63     full_qualifier: ast::Path,
64 }
65
66 /// A name that will be used during item lookups.
67 #[derive(Debug, Clone)]
68 pub enum NameToImport {
69     /// Requires items with names that exactly match the given string, bool indicates case-sensitivity.
70     Exact(String, bool),
71     /// Requires items with names that case-insensitively contain all letters from the string,
72     /// in the same order, but not necessary adjacent.
73     Fuzzy(String),
74 }
75
76 impl NameToImport {
77     pub fn exact_case_sensitive(s: String) -> NameToImport {
78         NameToImport::Exact(s, true)
79     }
80 }
81
82 impl NameToImport {
83     pub fn text(&self) -> &str {
84         match self {
85             NameToImport::Exact(text, _) => text.as_str(),
86             NameToImport::Fuzzy(text) => text.as_str(),
87         }
88     }
89 }
90
91 /// A struct to find imports in the project, given a certain name (or its part) and the context.
92 #[derive(Debug)]
93 pub struct ImportAssets {
94     import_candidate: ImportCandidate,
95     candidate_node: SyntaxNode,
96     module_with_candidate: Module,
97 }
98
99 impl ImportAssets {
100     pub fn for_method_call(
101         method_call: &ast::MethodCallExpr,
102         sema: &Semantics<'_, RootDatabase>,
103     ) -> Option<Self> {
104         let candidate_node = method_call.syntax().clone();
105         Some(Self {
106             import_candidate: ImportCandidate::for_method_call(sema, method_call)?,
107             module_with_candidate: sema.scope(&candidate_node)?.module(),
108             candidate_node,
109         })
110     }
111
112     pub fn for_exact_path(
113         fully_qualified_path: &ast::Path,
114         sema: &Semantics<'_, RootDatabase>,
115     ) -> Option<Self> {
116         let candidate_node = fully_qualified_path.syntax().clone();
117         if let Some(use_tree) = candidate_node.ancestors().find_map(ast::UseTree::cast) {
118             // Path is inside a use tree, then only continue if it is the first segment of a use statement.
119             if use_tree.syntax().parent().and_then(ast::Use::cast).is_none()
120                 || fully_qualified_path.qualifier().is_some()
121             {
122                 return None;
123             }
124         }
125         Some(Self {
126             import_candidate: ImportCandidate::for_regular_path(sema, fully_qualified_path)?,
127             module_with_candidate: sema.scope(&candidate_node)?.module(),
128             candidate_node,
129         })
130     }
131
132     pub fn for_ident_pat(sema: &Semantics<'_, RootDatabase>, pat: &ast::IdentPat) -> Option<Self> {
133         if !pat.is_simple_ident() {
134             return None;
135         }
136         let name = pat.name()?;
137         let candidate_node = pat.syntax().clone();
138         Some(Self {
139             import_candidate: ImportCandidate::for_name(sema, &name)?,
140             module_with_candidate: sema.scope(&candidate_node)?.module(),
141             candidate_node,
142         })
143     }
144
145     pub fn for_fuzzy_path(
146         module_with_candidate: Module,
147         qualifier: Option<ast::Path>,
148         fuzzy_name: String,
149         sema: &Semantics<'_, RootDatabase>,
150         candidate_node: SyntaxNode,
151     ) -> Option<Self> {
152         Some(Self {
153             import_candidate: ImportCandidate::for_fuzzy_path(qualifier, fuzzy_name, sema)?,
154             module_with_candidate,
155             candidate_node,
156         })
157     }
158
159     pub fn for_fuzzy_method_call(
160         module_with_method_call: Module,
161         receiver_ty: Type,
162         fuzzy_method_name: String,
163         candidate_node: SyntaxNode,
164     ) -> Option<Self> {
165         Some(Self {
166             import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate {
167                 receiver_ty,
168                 assoc_item_name: NameToImport::Fuzzy(fuzzy_method_name),
169             }),
170             module_with_candidate: module_with_method_call,
171             candidate_node,
172         })
173     }
174 }
175
176 /// An import (not necessary the only one) that corresponds a certain given [`PathImportCandidate`].
177 /// (the structure is not entirely correct, since there can be situations requiring two imports, see FIXME below for the details)
178 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
179 pub struct LocatedImport {
180     /// The path to use in the `use` statement for a given candidate to be imported.
181     pub import_path: ModPath,
182     /// An item that will be imported with the import path given.
183     pub item_to_import: ItemInNs,
184     /// The path import candidate, resolved.
185     ///
186     /// Not necessary matches the import:
187     /// For any associated constant from the trait, we try to access as `some::path::SomeStruct::ASSOC_`
188     /// the original item is the associated constant, but the import has to be a trait that
189     /// defines this constant.
190     pub original_item: ItemInNs,
191     /// A path of the original item.
192     pub original_path: Option<ModPath>,
193 }
194
195 impl LocatedImport {
196     pub fn new(
197         import_path: ModPath,
198         item_to_import: ItemInNs,
199         original_item: ItemInNs,
200         original_path: Option<ModPath>,
201     ) -> Self {
202         Self { import_path, item_to_import, original_item, original_path }
203     }
204 }
205
206 impl ImportAssets {
207     pub fn import_candidate(&self) -> &ImportCandidate {
208         &self.import_candidate
209     }
210
211     pub fn search_for_imports(
212         &self,
213         sema: &Semantics<'_, RootDatabase>,
214         prefix_kind: PrefixKind,
215     ) -> Vec<LocatedImport> {
216         let _p = profile::span("import_assets::search_for_imports");
217         self.search_for(sema, Some(prefix_kind))
218     }
219
220     /// This may return non-absolute paths if a part of the returned path is already imported into scope.
221     pub fn search_for_relative_paths(
222         &self,
223         sema: &Semantics<'_, RootDatabase>,
224     ) -> Vec<LocatedImport> {
225         let _p = profile::span("import_assets::search_for_relative_paths");
226         self.search_for(sema, None)
227     }
228
229     pub fn path_fuzzy_name_to_exact(&mut self, case_sensitive: bool) {
230         if let ImportCandidate::Path(PathImportCandidate { name: to_import, .. }) =
231             &mut self.import_candidate
232         {
233             let name = match to_import {
234                 NameToImport::Fuzzy(name) => std::mem::take(name),
235                 _ => return,
236             };
237             *to_import = NameToImport::Exact(name, case_sensitive);
238         }
239     }
240
241     fn search_for(
242         &self,
243         sema: &Semantics<'_, RootDatabase>,
244         prefixed: Option<PrefixKind>,
245     ) -> Vec<LocatedImport> {
246         let _p = profile::span("import_assets::search_for");
247
248         let scope_definitions = self.scope_definitions(sema);
249         let mod_path = |item| {
250             get_mod_path(
251                 sema.db,
252                 item_for_path_search(sema.db, item)?,
253                 &self.module_with_candidate,
254                 prefixed,
255             )
256         };
257
258         let krate = self.module_with_candidate.krate();
259         let scope = match sema.scope(&self.candidate_node) {
260             Some(it) => it,
261             None => return Vec::new(),
262         };
263
264         match &self.import_candidate {
265             ImportCandidate::Path(path_candidate) => {
266                 path_applicable_imports(sema, krate, path_candidate, mod_path)
267             }
268             ImportCandidate::TraitAssocItem(trait_candidate) => {
269                 trait_applicable_items(sema, krate, &scope, trait_candidate, true, mod_path)
270             }
271             ImportCandidate::TraitMethod(trait_candidate) => {
272                 trait_applicable_items(sema, krate, &scope, trait_candidate, false, mod_path)
273             }
274         }
275         .into_iter()
276         .filter(|import| import.import_path.len() > 1)
277         .filter(|import| !scope_definitions.contains(&ScopeDef::from(import.item_to_import)))
278         .sorted_by(|a, b| a.import_path.cmp(&b.import_path))
279         .collect()
280     }
281
282     fn scope_definitions(&self, sema: &Semantics<'_, RootDatabase>) -> FxHashSet<ScopeDef> {
283         let _p = profile::span("import_assets::scope_definitions");
284         let mut scope_definitions = FxHashSet::default();
285         if let Some(scope) = sema.scope(&self.candidate_node) {
286             scope.process_all_names(&mut |_, scope_def| {
287                 scope_definitions.insert(scope_def);
288             });
289         }
290         scope_definitions
291     }
292 }
293
294 fn path_applicable_imports(
295     sema: &Semantics<'_, RootDatabase>,
296     current_crate: Crate,
297     path_candidate: &PathImportCandidate,
298     mod_path: impl Fn(ItemInNs) -> Option<ModPath> + Copy,
299 ) -> FxHashSet<LocatedImport> {
300     let _p = profile::span("import_assets::path_applicable_imports");
301
302     match &path_candidate.qualifier {
303         None => {
304             items_locator::items_with_name(
305                 sema,
306                 current_crate,
307                 path_candidate.name.clone(),
308                 // FIXME: we could look up assoc items by the input and propose those in completion,
309                 // but that requires more preparation first:
310                 // * store non-trait assoc items in import_map to fully enable this lookup
311                 // * ensure that does not degrade the performance (benchmark it)
312                 // * write more logic to check for corresponding trait presence requirement (we're unable to flyimport multiple item right now)
313                 // * improve the associated completion item matching and/or scoring to ensure no noisy completions appear
314                 //
315                 // see also an ignored test under FIXME comment in the qualify_path.rs module
316                 AssocItemSearch::Exclude,
317                 Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
318             )
319             .filter_map(|item| {
320                 let mod_path = mod_path(item)?;
321                 Some(LocatedImport::new(mod_path.clone(), item, item, Some(mod_path)))
322             })
323             .collect()
324         }
325         Some(first_segment_unresolved) => {
326             let unresolved_qualifier =
327                 path_to_string_stripping_turbo_fish(&first_segment_unresolved.full_qualifier);
328             let unresolved_first_segment = first_segment_unresolved.fist_segment.text();
329             items_locator::items_with_name(
330                 sema,
331                 current_crate,
332                 path_candidate.name.clone(),
333                 AssocItemSearch::Include,
334                 Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
335             )
336             .filter_map(|item| {
337                 import_for_item(
338                     sema.db,
339                     mod_path,
340                     &unresolved_first_segment,
341                     &unresolved_qualifier,
342                     item,
343                 )
344             })
345             .collect()
346         }
347     }
348 }
349
350 fn import_for_item(
351     db: &RootDatabase,
352     mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
353     unresolved_first_segment: &str,
354     unresolved_qualifier: &str,
355     original_item: ItemInNs,
356 ) -> Option<LocatedImport> {
357     let _p = profile::span("import_assets::import_for_item");
358
359     let original_item_candidate = item_for_path_search(db, original_item)?;
360     let import_path_candidate = mod_path(original_item_candidate)?;
361     let import_path_string = import_path_candidate.to_string();
362
363     let expected_import_end = if item_as_assoc(db, original_item).is_some() {
364         unresolved_qualifier.to_string()
365     } else {
366         format!("{}::{}", unresolved_qualifier, item_name(db, original_item)?)
367     };
368     if !import_path_string.contains(unresolved_first_segment)
369         || !import_path_string.ends_with(&expected_import_end)
370     {
371         return None;
372     }
373
374     let segment_import =
375         find_import_for_segment(db, original_item_candidate, unresolved_first_segment)?;
376     let trait_item_to_import = item_as_assoc(db, original_item)
377         .and_then(|assoc| assoc.containing_trait(db))
378         .map(|trait_| ItemInNs::from(ModuleDef::from(trait_)));
379     Some(match (segment_import == original_item_candidate, trait_item_to_import) {
380         (true, Some(_)) => {
381             // FIXME we should be able to import both the trait and the segment,
382             // but it's unclear what to do with overlapping edits (merge imports?)
383             // especially in case of lazy completion edit resolutions.
384             return None;
385         }
386         (false, Some(trait_to_import)) => LocatedImport::new(
387             mod_path(trait_to_import)?,
388             trait_to_import,
389             original_item,
390             mod_path(original_item),
391         ),
392         (true, None) => LocatedImport::new(
393             import_path_candidate,
394             original_item_candidate,
395             original_item,
396             mod_path(original_item),
397         ),
398         (false, None) => LocatedImport::new(
399             mod_path(segment_import)?,
400             segment_import,
401             original_item,
402             mod_path(original_item),
403         ),
404     })
405 }
406
407 pub fn item_for_path_search(db: &RootDatabase, item: ItemInNs) -> Option<ItemInNs> {
408     Some(match item {
409         ItemInNs::Types(_) | ItemInNs::Values(_) => match item_as_assoc(db, item) {
410             Some(assoc_item) => match assoc_item.container(db) {
411                 AssocItemContainer::Trait(trait_) => ItemInNs::from(ModuleDef::from(trait_)),
412                 AssocItemContainer::Impl(impl_) => {
413                     ItemInNs::from(ModuleDef::from(impl_.self_ty(db).as_adt()?))
414                 }
415             },
416             None => item,
417         },
418         ItemInNs::Macros(_) => item,
419     })
420 }
421
422 fn find_import_for_segment(
423     db: &RootDatabase,
424     original_item: ItemInNs,
425     unresolved_first_segment: &str,
426 ) -> Option<ItemInNs> {
427     let segment_is_name = item_name(db, original_item)
428         .map(|name| name.to_smol_str() == unresolved_first_segment)
429         .unwrap_or(false);
430
431     Some(if segment_is_name {
432         original_item
433     } else {
434         let matching_module =
435             module_with_segment_name(db, unresolved_first_segment, original_item)?;
436         ItemInNs::from(ModuleDef::from(matching_module))
437     })
438 }
439
440 fn module_with_segment_name(
441     db: &RootDatabase,
442     segment_name: &str,
443     candidate: ItemInNs,
444 ) -> Option<Module> {
445     let mut current_module = match candidate {
446         ItemInNs::Types(module_def_id) => module_def_id.module(db),
447         ItemInNs::Values(module_def_id) => module_def_id.module(db),
448         ItemInNs::Macros(macro_def_id) => ModuleDef::from(macro_def_id).module(db),
449     };
450     while let Some(module) = current_module {
451         if let Some(module_name) = module.name(db) {
452             if module_name.to_smol_str() == segment_name {
453                 return Some(module);
454             }
455         }
456         current_module = module.parent(db);
457     }
458     None
459 }
460
461 fn trait_applicable_items(
462     sema: &Semantics<'_, RootDatabase>,
463     current_crate: Crate,
464     scope: &SemanticsScope<'_>,
465     trait_candidate: &TraitImportCandidate,
466     trait_assoc_item: bool,
467     mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
468 ) -> FxHashSet<LocatedImport> {
469     let _p = profile::span("import_assets::trait_applicable_items");
470
471     let db = sema.db;
472
473     let inherent_traits = trait_candidate.receiver_ty.applicable_inherent_traits(db);
474     let env_traits = trait_candidate.receiver_ty.env_traits(db);
475     let related_traits = inherent_traits.chain(env_traits).collect::<FxHashSet<_>>();
476
477     let mut required_assoc_items = FxHashSet::default();
478     let trait_candidates = items_locator::items_with_name(
479         sema,
480         current_crate,
481         trait_candidate.assoc_item_name.clone(),
482         AssocItemSearch::AssocItemsOnly,
483         Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
484     )
485     .filter_map(|input| item_as_assoc(db, input))
486     .filter_map(|assoc| {
487         let assoc_item_trait = assoc.containing_trait(db)?;
488         if related_traits.contains(&assoc_item_trait) {
489             None
490         } else {
491             required_assoc_items.insert(assoc);
492             Some(assoc_item_trait.into())
493         }
494     })
495     .collect();
496
497     let mut located_imports = FxHashSet::default();
498
499     if trait_assoc_item {
500         trait_candidate.receiver_ty.iterate_path_candidates(
501             db,
502             scope,
503             &trait_candidates,
504             None,
505             None,
506             |assoc| {
507                 if required_assoc_items.contains(&assoc) {
508                     if let AssocItem::Function(f) = assoc {
509                         if f.self_param(db).is_some() {
510                             return None;
511                         }
512                     }
513                     let located_trait = assoc.containing_trait(db)?;
514                     let trait_item = ItemInNs::from(ModuleDef::from(located_trait));
515                     let original_item = assoc_to_item(assoc);
516                     located_imports.insert(LocatedImport::new(
517                         mod_path(trait_item)?,
518                         trait_item,
519                         original_item,
520                         mod_path(original_item),
521                     ));
522                 }
523                 None::<()>
524             },
525         )
526     } else {
527         trait_candidate.receiver_ty.iterate_method_candidates(
528             db,
529             scope,
530             &trait_candidates,
531             None,
532             None,
533             |function| {
534                 let assoc = function.as_assoc_item(db)?;
535                 if required_assoc_items.contains(&assoc) {
536                     let located_trait = assoc.containing_trait(db)?;
537                     let trait_item = ItemInNs::from(ModuleDef::from(located_trait));
538                     let original_item = assoc_to_item(assoc);
539                     located_imports.insert(LocatedImport::new(
540                         mod_path(trait_item)?,
541                         trait_item,
542                         original_item,
543                         mod_path(original_item),
544                     ));
545                 }
546                 None::<()>
547             },
548         )
549     };
550
551     located_imports
552 }
553
554 fn assoc_to_item(assoc: AssocItem) -> ItemInNs {
555     match assoc {
556         AssocItem::Function(f) => ItemInNs::from(ModuleDef::from(f)),
557         AssocItem::Const(c) => ItemInNs::from(ModuleDef::from(c)),
558         AssocItem::TypeAlias(t) => ItemInNs::from(ModuleDef::from(t)),
559     }
560 }
561
562 fn get_mod_path(
563     db: &RootDatabase,
564     item_to_search: ItemInNs,
565     module_with_candidate: &Module,
566     prefixed: Option<PrefixKind>,
567 ) -> Option<ModPath> {
568     if let Some(prefix_kind) = prefixed {
569         module_with_candidate.find_use_path_prefixed(db, item_to_search, prefix_kind)
570     } else {
571         module_with_candidate.find_use_path(db, item_to_search)
572     }
573 }
574
575 impl ImportCandidate {
576     fn for_method_call(
577         sema: &Semantics<'_, RootDatabase>,
578         method_call: &ast::MethodCallExpr,
579     ) -> Option<Self> {
580         match sema.resolve_method_call(method_call) {
581             Some(_) => None,
582             None => Some(Self::TraitMethod(TraitImportCandidate {
583                 receiver_ty: sema.type_of_expr(&method_call.receiver()?)?.adjusted(),
584                 assoc_item_name: NameToImport::exact_case_sensitive(
585                     method_call.name_ref()?.to_string(),
586                 ),
587             })),
588         }
589     }
590
591     fn for_regular_path(sema: &Semantics<'_, RootDatabase>, path: &ast::Path) -> Option<Self> {
592         if sema.resolve_path(path).is_some() {
593             return None;
594         }
595         path_import_candidate(
596             sema,
597             path.qualifier(),
598             NameToImport::exact_case_sensitive(path.segment()?.name_ref()?.to_string()),
599         )
600     }
601
602     fn for_name(sema: &Semantics<'_, RootDatabase>, name: &ast::Name) -> Option<Self> {
603         if sema
604             .scope(name.syntax())?
605             .speculative_resolve(&ast::make::ext::ident_path(&name.text()))
606             .is_some()
607         {
608             return None;
609         }
610         Some(ImportCandidate::Path(PathImportCandidate {
611             qualifier: None,
612             name: NameToImport::exact_case_sensitive(name.to_string()),
613         }))
614     }
615
616     fn for_fuzzy_path(
617         qualifier: Option<ast::Path>,
618         fuzzy_name: String,
619         sema: &Semantics<'_, RootDatabase>,
620     ) -> Option<Self> {
621         path_import_candidate(sema, qualifier, NameToImport::Fuzzy(fuzzy_name))
622     }
623 }
624
625 fn path_import_candidate(
626     sema: &Semantics<'_, RootDatabase>,
627     qualifier: Option<ast::Path>,
628     name: NameToImport,
629 ) -> Option<ImportCandidate> {
630     Some(match qualifier {
631         Some(qualifier) => match sema.resolve_path(&qualifier) {
632             None => {
633                 let qualifier_start =
634                     qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
635                 let qualifier_start_path =
636                     qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
637                 if sema.resolve_path(&qualifier_start_path).is_none() {
638                     ImportCandidate::Path(PathImportCandidate {
639                         qualifier: Some(FirstSegmentUnresolved {
640                             fist_segment: qualifier_start,
641                             full_qualifier: qualifier,
642                         }),
643                         name,
644                     })
645                 } else {
646                     return None;
647                 }
648             }
649             Some(PathResolution::Def(ModuleDef::Adt(assoc_item_path))) => {
650                 ImportCandidate::TraitAssocItem(TraitImportCandidate {
651                     receiver_ty: assoc_item_path.ty(sema.db),
652                     assoc_item_name: name,
653                 })
654             }
655             Some(PathResolution::Def(ModuleDef::TypeAlias(alias))) => {
656                 let ty = alias.ty(sema.db);
657                 if ty.as_adt().is_some() {
658                     ImportCandidate::TraitAssocItem(TraitImportCandidate {
659                         receiver_ty: ty,
660                         assoc_item_name: name,
661                     })
662                 } else {
663                     return None;
664                 }
665             }
666             Some(_) => return None,
667         },
668         None => ImportCandidate::Path(PathImportCandidate { qualifier: None, name }),
669     })
670 }
671
672 fn item_as_assoc(db: &RootDatabase, item: ItemInNs) -> Option<AssocItem> {
673     item.as_module_def().and_then(|module_def| module_def.as_assoc_item(db))
674 }