]> git.proxmox.com Git - rustc.git/blob - src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs
New upstream version 1.73.0+dfsg1
[rustc.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, AssocSearchMode, 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 prefer_no_std: bool,
216 ) -> Vec<LocatedImport> {
217 let _p = profile::span("import_assets::search_for_imports");
218 self.search_for(sema, Some(prefix_kind), prefer_no_std)
219 }
220
221 /// This may return non-absolute paths if a part of the returned path is already imported into scope.
222 pub fn search_for_relative_paths(
223 &self,
224 sema: &Semantics<'_, RootDatabase>,
225 prefer_no_std: bool,
226 ) -> Vec<LocatedImport> {
227 let _p = profile::span("import_assets::search_for_relative_paths");
228 self.search_for(sema, None, prefer_no_std)
229 }
230
231 pub fn path_fuzzy_name_to_exact(&mut self, case_sensitive: bool) {
232 if let ImportCandidate::Path(PathImportCandidate { name: to_import, .. }) =
233 &mut self.import_candidate
234 {
235 let name = match to_import {
236 NameToImport::Fuzzy(name) => std::mem::take(name),
237 _ => return,
238 };
239 *to_import = NameToImport::Exact(name, case_sensitive);
240 }
241 }
242
243 fn search_for(
244 &self,
245 sema: &Semantics<'_, RootDatabase>,
246 prefixed: Option<PrefixKind>,
247 prefer_no_std: bool,
248 ) -> Vec<LocatedImport> {
249 let _p = profile::span("import_assets::search_for");
250
251 let scope_definitions = self.scope_definitions(sema);
252 let mod_path = |item| {
253 get_mod_path(
254 sema.db,
255 item_for_path_search(sema.db, item)?,
256 &self.module_with_candidate,
257 prefixed,
258 prefer_no_std,
259 )
260 };
261
262 let krate = self.module_with_candidate.krate();
263 let scope = match sema.scope(&self.candidate_node) {
264 Some(it) => it,
265 None => return Vec::new(),
266 };
267
268 match &self.import_candidate {
269 ImportCandidate::Path(path_candidate) => {
270 path_applicable_imports(sema, krate, path_candidate, mod_path)
271 }
272 ImportCandidate::TraitAssocItem(trait_candidate) => {
273 trait_applicable_items(sema, krate, &scope, trait_candidate, true, mod_path)
274 }
275 ImportCandidate::TraitMethod(trait_candidate) => {
276 trait_applicable_items(sema, krate, &scope, trait_candidate, false, mod_path)
277 }
278 }
279 .into_iter()
280 .filter(|import| import.import_path.len() > 1)
281 .filter(|import| !scope_definitions.contains(&ScopeDef::from(import.item_to_import)))
282 .sorted_by(|a, b| a.import_path.cmp(&b.import_path))
283 .collect()
284 }
285
286 fn scope_definitions(&self, sema: &Semantics<'_, RootDatabase>) -> FxHashSet<ScopeDef> {
287 let _p = profile::span("import_assets::scope_definitions");
288 let mut scope_definitions = FxHashSet::default();
289 if let Some(scope) = sema.scope(&self.candidate_node) {
290 scope.process_all_names(&mut |_, scope_def| {
291 scope_definitions.insert(scope_def);
292 });
293 }
294 scope_definitions
295 }
296 }
297
298 fn path_applicable_imports(
299 sema: &Semantics<'_, RootDatabase>,
300 current_crate: Crate,
301 path_candidate: &PathImportCandidate,
302 mod_path: impl Fn(ItemInNs) -> Option<ModPath> + Copy,
303 ) -> FxHashSet<LocatedImport> {
304 let _p = profile::span("import_assets::path_applicable_imports");
305
306 match &path_candidate.qualifier {
307 None => {
308 items_locator::items_with_name(
309 sema,
310 current_crate,
311 path_candidate.name.clone(),
312 // FIXME: we could look up assoc items by the input and propose those in completion,
313 // but that requires more preparation first:
314 // * store non-trait assoc items in import_map to fully enable this lookup
315 // * ensure that does not degrade the performance (benchmark it)
316 // * write more logic to check for corresponding trait presence requirement (we're unable to flyimport multiple item right now)
317 // * improve the associated completion item matching and/or scoring to ensure no noisy completions appear
318 //
319 // see also an ignored test under FIXME comment in the qualify_path.rs module
320 AssocSearchMode::Exclude,
321 Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
322 )
323 .filter_map(|item| {
324 let mod_path = mod_path(item)?;
325 Some(LocatedImport::new(mod_path.clone(), item, item, Some(mod_path)))
326 })
327 .collect()
328 }
329 Some(first_segment_unresolved) => {
330 let unresolved_qualifier =
331 path_to_string_stripping_turbo_fish(&first_segment_unresolved.full_qualifier);
332 let unresolved_first_segment = first_segment_unresolved.fist_segment.text();
333 items_locator::items_with_name(
334 sema,
335 current_crate,
336 path_candidate.name.clone(),
337 AssocSearchMode::Include,
338 Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
339 )
340 .filter_map(|item| {
341 import_for_item(
342 sema.db,
343 mod_path,
344 &unresolved_first_segment,
345 &unresolved_qualifier,
346 item,
347 )
348 })
349 .collect()
350 }
351 }
352 }
353
354 fn import_for_item(
355 db: &RootDatabase,
356 mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
357 unresolved_first_segment: &str,
358 unresolved_qualifier: &str,
359 original_item: ItemInNs,
360 ) -> Option<LocatedImport> {
361 let _p = profile::span("import_assets::import_for_item");
362
363 let original_item_candidate = item_for_path_search(db, original_item)?;
364 let import_path_candidate = mod_path(original_item_candidate)?;
365 let import_path_string = import_path_candidate.display(db).to_string();
366
367 let expected_import_end = if item_as_assoc(db, original_item).is_some() {
368 unresolved_qualifier.to_string()
369 } else {
370 format!("{unresolved_qualifier}::{}", item_name(db, original_item)?.display(db))
371 };
372 if !import_path_string.contains(unresolved_first_segment)
373 || !import_path_string.ends_with(&expected_import_end)
374 {
375 return None;
376 }
377
378 let segment_import =
379 find_import_for_segment(db, original_item_candidate, unresolved_first_segment)?;
380 let trait_item_to_import = item_as_assoc(db, original_item)
381 .and_then(|assoc| assoc.containing_trait(db))
382 .map(|trait_| ItemInNs::from(ModuleDef::from(trait_)));
383 Some(match (segment_import == original_item_candidate, trait_item_to_import) {
384 (true, Some(_)) => {
385 // FIXME we should be able to import both the trait and the segment,
386 // but it's unclear what to do with overlapping edits (merge imports?)
387 // especially in case of lazy completion edit resolutions.
388 return None;
389 }
390 (false, Some(trait_to_import)) => LocatedImport::new(
391 mod_path(trait_to_import)?,
392 trait_to_import,
393 original_item,
394 mod_path(original_item),
395 ),
396 (true, None) => LocatedImport::new(
397 import_path_candidate,
398 original_item_candidate,
399 original_item,
400 mod_path(original_item),
401 ),
402 (false, None) => LocatedImport::new(
403 mod_path(segment_import)?,
404 segment_import,
405 original_item,
406 mod_path(original_item),
407 ),
408 })
409 }
410
411 pub fn item_for_path_search(db: &RootDatabase, item: ItemInNs) -> Option<ItemInNs> {
412 Some(match item {
413 ItemInNs::Types(_) | ItemInNs::Values(_) => match item_as_assoc(db, item) {
414 Some(assoc_item) => match assoc_item.container(db) {
415 AssocItemContainer::Trait(trait_) => ItemInNs::from(ModuleDef::from(trait_)),
416 AssocItemContainer::Impl(impl_) => {
417 ItemInNs::from(ModuleDef::from(impl_.self_ty(db).as_adt()?))
418 }
419 },
420 None => item,
421 },
422 ItemInNs::Macros(_) => item,
423 })
424 }
425
426 fn find_import_for_segment(
427 db: &RootDatabase,
428 original_item: ItemInNs,
429 unresolved_first_segment: &str,
430 ) -> Option<ItemInNs> {
431 let segment_is_name = item_name(db, original_item)
432 .map(|name| name.to_smol_str() == unresolved_first_segment)
433 .unwrap_or(false);
434
435 Some(if segment_is_name {
436 original_item
437 } else {
438 let matching_module =
439 module_with_segment_name(db, unresolved_first_segment, original_item)?;
440 ItemInNs::from(ModuleDef::from(matching_module))
441 })
442 }
443
444 fn module_with_segment_name(
445 db: &RootDatabase,
446 segment_name: &str,
447 candidate: ItemInNs,
448 ) -> Option<Module> {
449 let mut current_module = match candidate {
450 ItemInNs::Types(module_def_id) => module_def_id.module(db),
451 ItemInNs::Values(module_def_id) => module_def_id.module(db),
452 ItemInNs::Macros(macro_def_id) => ModuleDef::from(macro_def_id).module(db),
453 };
454 while let Some(module) = current_module {
455 if let Some(module_name) = module.name(db) {
456 if module_name.to_smol_str() == segment_name {
457 return Some(module);
458 }
459 }
460 current_module = module.parent(db);
461 }
462 None
463 }
464
465 fn trait_applicable_items(
466 sema: &Semantics<'_, RootDatabase>,
467 current_crate: Crate,
468 scope: &SemanticsScope<'_>,
469 trait_candidate: &TraitImportCandidate,
470 trait_assoc_item: bool,
471 mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
472 ) -> FxHashSet<LocatedImport> {
473 let _p = profile::span("import_assets::trait_applicable_items");
474
475 let db = sema.db;
476
477 let inherent_traits = trait_candidate.receiver_ty.applicable_inherent_traits(db);
478 let env_traits = trait_candidate.receiver_ty.env_traits(db);
479 let related_traits = inherent_traits.chain(env_traits).collect::<FxHashSet<_>>();
480
481 let mut required_assoc_items = FxHashSet::default();
482 let trait_candidates = items_locator::items_with_name(
483 sema,
484 current_crate,
485 trait_candidate.assoc_item_name.clone(),
486 AssocSearchMode::AssocItemsOnly,
487 Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
488 )
489 .filter_map(|input| item_as_assoc(db, input))
490 .filter_map(|assoc| {
491 let assoc_item_trait = assoc.containing_trait(db)?;
492 if related_traits.contains(&assoc_item_trait) {
493 None
494 } else {
495 required_assoc_items.insert(assoc);
496 Some(assoc_item_trait.into())
497 }
498 })
499 .collect();
500
501 let mut located_imports = FxHashSet::default();
502
503 if trait_assoc_item {
504 trait_candidate.receiver_ty.iterate_path_candidates(
505 db,
506 scope,
507 &trait_candidates,
508 None,
509 None,
510 |assoc| {
511 if required_assoc_items.contains(&assoc) {
512 if let AssocItem::Function(f) = assoc {
513 if f.self_param(db).is_some() {
514 return None;
515 }
516 }
517 let located_trait = assoc.containing_trait(db)?;
518 let trait_item = ItemInNs::from(ModuleDef::from(located_trait));
519 let original_item = assoc_to_item(assoc);
520 located_imports.insert(LocatedImport::new(
521 mod_path(trait_item)?,
522 trait_item,
523 original_item,
524 mod_path(original_item),
525 ));
526 }
527 None::<()>
528 },
529 )
530 } else {
531 trait_candidate.receiver_ty.iterate_method_candidates_with_traits(
532 db,
533 scope,
534 &trait_candidates,
535 None,
536 None,
537 |function| {
538 let assoc = function.as_assoc_item(db)?;
539 if required_assoc_items.contains(&assoc) {
540 let located_trait = assoc.containing_trait(db)?;
541 let trait_item = ItemInNs::from(ModuleDef::from(located_trait));
542 let original_item = assoc_to_item(assoc);
543 located_imports.insert(LocatedImport::new(
544 mod_path(trait_item)?,
545 trait_item,
546 original_item,
547 mod_path(original_item),
548 ));
549 }
550 None::<()>
551 },
552 )
553 };
554
555 located_imports
556 }
557
558 fn assoc_to_item(assoc: AssocItem) -> ItemInNs {
559 match assoc {
560 AssocItem::Function(f) => ItemInNs::from(ModuleDef::from(f)),
561 AssocItem::Const(c) => ItemInNs::from(ModuleDef::from(c)),
562 AssocItem::TypeAlias(t) => ItemInNs::from(ModuleDef::from(t)),
563 }
564 }
565
566 fn get_mod_path(
567 db: &RootDatabase,
568 item_to_search: ItemInNs,
569 module_with_candidate: &Module,
570 prefixed: Option<PrefixKind>,
571 prefer_no_std: bool,
572 ) -> Option<ModPath> {
573 if let Some(prefix_kind) = prefixed {
574 module_with_candidate.find_use_path_prefixed(db, item_to_search, prefix_kind, prefer_no_std)
575 } else {
576 module_with_candidate.find_use_path(db, item_to_search, prefer_no_std)
577 }
578 }
579
580 impl ImportCandidate {
581 fn for_method_call(
582 sema: &Semantics<'_, RootDatabase>,
583 method_call: &ast::MethodCallExpr,
584 ) -> Option<Self> {
585 match sema.resolve_method_call(method_call) {
586 Some(_) => None,
587 None => Some(Self::TraitMethod(TraitImportCandidate {
588 receiver_ty: sema.type_of_expr(&method_call.receiver()?)?.adjusted(),
589 assoc_item_name: NameToImport::exact_case_sensitive(
590 method_call.name_ref()?.to_string(),
591 ),
592 })),
593 }
594 }
595
596 fn for_regular_path(sema: &Semantics<'_, RootDatabase>, path: &ast::Path) -> Option<Self> {
597 if sema.resolve_path(path).is_some() {
598 return None;
599 }
600 path_import_candidate(
601 sema,
602 path.qualifier(),
603 NameToImport::exact_case_sensitive(path.segment()?.name_ref()?.to_string()),
604 )
605 }
606
607 fn for_name(sema: &Semantics<'_, RootDatabase>, name: &ast::Name) -> Option<Self> {
608 if sema
609 .scope(name.syntax())?
610 .speculative_resolve(&ast::make::ext::ident_path(&name.text()))
611 .is_some()
612 {
613 return None;
614 }
615 Some(ImportCandidate::Path(PathImportCandidate {
616 qualifier: None,
617 name: NameToImport::exact_case_sensitive(name.to_string()),
618 }))
619 }
620
621 fn for_fuzzy_path(
622 qualifier: Option<ast::Path>,
623 fuzzy_name: String,
624 sema: &Semantics<'_, RootDatabase>,
625 ) -> Option<Self> {
626 path_import_candidate(sema, qualifier, NameToImport::Fuzzy(fuzzy_name))
627 }
628 }
629
630 fn path_import_candidate(
631 sema: &Semantics<'_, RootDatabase>,
632 qualifier: Option<ast::Path>,
633 name: NameToImport,
634 ) -> Option<ImportCandidate> {
635 Some(match qualifier {
636 Some(qualifier) => match sema.resolve_path(&qualifier) {
637 None => {
638 let qualifier_start =
639 qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
640 let qualifier_start_path =
641 qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
642 if sema.resolve_path(&qualifier_start_path).is_none() {
643 ImportCandidate::Path(PathImportCandidate {
644 qualifier: Some(FirstSegmentUnresolved {
645 fist_segment: qualifier_start,
646 full_qualifier: qualifier,
647 }),
648 name,
649 })
650 } else {
651 return None;
652 }
653 }
654 Some(PathResolution::Def(ModuleDef::Adt(assoc_item_path))) => {
655 ImportCandidate::TraitAssocItem(TraitImportCandidate {
656 receiver_ty: assoc_item_path.ty(sema.db),
657 assoc_item_name: name,
658 })
659 }
660 Some(PathResolution::Def(ModuleDef::TypeAlias(alias))) => {
661 let ty = alias.ty(sema.db);
662 if ty.as_adt().is_some() {
663 ImportCandidate::TraitAssocItem(TraitImportCandidate {
664 receiver_ty: ty,
665 assoc_item_name: name,
666 })
667 } else {
668 return None;
669 }
670 }
671 Some(_) => return None,
672 },
673 None => ImportCandidate::Path(PathImportCandidate { qualifier: None, name }),
674 })
675 }
676
677 fn item_as_assoc(db: &RootDatabase, item: ItemInNs) -> Option<AssocItem> {
678 item.as_module_def().and_then(|module_def| module_def.as_assoc_item(db))
679 }