hir/semantics/
source_to_def.rs

1//! Maps *syntax* of various definitions to their semantic ids.
2//!
3//! This is a very interesting module, and, in some sense, can be considered the
4//! heart of the IDE parts of rust-analyzer.
5//!
6//! This module solves the following problem:
7//!
8//! > Given a piece of syntax, find the corresponding semantic definition (def).
9//!
10//! This problem is a part of more-or-less every IDE feature implemented. Every
11//! IDE functionality (like goto to definition), conceptually starts with a
12//! specific cursor position in a file. Starting with this text offset, we first
13//! figure out what syntactic construct are we at: is this a pattern, an
14//! expression, an item definition.
15//!
16//! Knowing only the syntax gives us relatively little info. For example,
17//! looking at the syntax of the function we can realize that it is a part of an
18//! `impl` block, but we won't be able to tell what trait function the current
19//! function overrides, and whether it does that correctly. For that, we need to
20//! go from [`ast::Fn`] to [`crate::Function`], and that's exactly what this
21//! module does.
22//!
23//! As syntax trees are values and don't know their place of origin/identity,
24//! this module also requires [`InFile`] wrappers to understand which specific
25//! real or macro-expanded file the tree comes from.
26//!
27//! The actual algorithm to resolve syntax to def is curious in two aspects:
28//!
29//! * It is recursive
30//! * It uses the inverse algorithm (what is the syntax for this def?)
31//!
32//! Specifically, the algorithm goes like this:
33//!
34//! 1. Find the syntactic container for the syntax. For example, field's
35//!    container is the struct, and structs container is a module.
36//! 2. Recursively get the def corresponding to container.
37//! 3. Ask the container def for all child defs. These child defs contain
38//!    the answer and answer's siblings.
39//! 4. For each child def, ask for it's source.
40//! 5. The child def whose source is the syntax node we've started with
41//!    is the answer.
42//!
43//! It's interesting that both Roslyn and Kotlin contain very similar code
44//! shape.
45//!
46//! Let's take a look at Roslyn:
47//!
48//!   <https://github.com/dotnet/roslyn/blob/36a0c338d6621cc5fe34b79d414074a95a6a489c/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs#L1403-L1429>
49//!   <https://sourceroslyn.io/#Microsoft.CodeAnalysis.CSharp/Compilation/SyntaxTreeSemanticModel.cs,1403>
50//!
51//! The `GetDeclaredType` takes `Syntax` as input, and returns `Symbol` as
52//! output. First, it retrieves a `Symbol` for parent `Syntax`:
53//!
54//! * <https://sourceroslyn.io/#Microsoft.CodeAnalysis.CSharp/Compilation/SyntaxTreeSemanticModel.cs,1423>
55//!
56//! Then, it iterates parent symbol's children, looking for one which has the
57//! same text span as the original node:
58//!
59//!   <https://sourceroslyn.io/#Microsoft.CodeAnalysis.CSharp/Compilation/SyntaxTreeSemanticModel.cs,1786>
60//!
61//! Now, let's look at Kotlin:
62//!
63//!   <https://github.com/JetBrains/kotlin/blob/a288b8b00e4754a1872b164999c6d3f3b8c8994a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/FirModuleResolveStateImpl.kt#L93-L125>
64//!
65//! This function starts with a syntax node (`KtExpression` is syntax, like all
66//! `Kt` nodes), and returns a def. It uses
67//! `getNonLocalContainingOrThisDeclaration` to get syntactic container for a
68//! current node. Then, `findSourceNonLocalFirDeclaration` gets `Fir` for this
69//! parent. Finally, `findElementIn` function traverses `Fir` children to find
70//! one with the same source we originally started with.
71//!
72//! One question is left though -- where does the recursion stops? This happens
73//! when we get to the file syntax node, which doesn't have a syntactic parent.
74//! In that case, we loop through all the crates that might contain this file
75//! and look for a module whose source is the given file.
76//!
77//! Note that the logic in this module is somewhat fundamentally imprecise --
78//! due to conditional compilation and `#[path]` attributes, there's no
79//! injective mapping from syntax nodes to defs. This is not an edge case --
80//! more or less every item in a `lib.rs` is a part of two distinct crates: a
81//! library with `--cfg test` and a library without.
82//!
83//! At the moment, we don't really handle this well and return the first answer
84//! that works. Ideally, we should first let the caller to pick a specific
85//! active crate for a given position, and then provide an API to resolve all
86//! syntax nodes against this specific crate.
87
88use either::Either;
89use hir_def::{
90    AdtId, BlockId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExternBlockId,
91    ExternCrateId, FieldId, FunctionId, GenericDefId, GenericParamId, ImplId, LifetimeParamId,
92    Lookup, MacroId, ModuleId, StaticId, StructId, TraitId, TypeAliasId, TypeParamId, UnionId,
93    UseId, VariantId,
94    dyn_map::{
95        DynMap,
96        keys::{self, Key},
97    },
98    hir::{BindingId, Expr, LabelId},
99    nameres::{block_def_map, crate_def_map},
100};
101use hir_expand::{
102    EditionedFileId, ExpansionInfo, HirFileId, InMacroFile, MacroCallId, attrs::AttrId,
103    name::AsName,
104};
105use rustc_hash::FxHashMap;
106use smallvec::SmallVec;
107use span::FileId;
108use stdx::impl_from;
109use syntax::{
110    AstNode, AstPtr, SyntaxNode,
111    ast::{self, HasAttrs, HasName},
112};
113use tt::TextRange;
114
115use crate::{InFile, InlineAsmOperand, db::HirDatabase, semantics::child_by_source::ChildBySource};
116
117#[derive(Default)]
118pub(super) struct SourceToDefCache {
119    pub(super) dynmap_cache: FxHashMap<(ChildContainer, HirFileId), DynMap>,
120    expansion_info_cache: FxHashMap<MacroCallId, ExpansionInfo>,
121    pub(super) file_to_def_cache: FxHashMap<FileId, SmallVec<[ModuleId; 1]>>,
122    pub(super) included_file_cache: FxHashMap<EditionedFileId, Option<MacroCallId>>,
123    /// Rootnode to HirFileId cache
124    pub(super) root_to_file_cache: FxHashMap<SyntaxNode, HirFileId>,
125}
126
127impl SourceToDefCache {
128    pub(super) fn cache(
129        root_to_file_cache: &mut FxHashMap<SyntaxNode, HirFileId>,
130        root_node: SyntaxNode,
131        file_id: HirFileId,
132    ) {
133        assert!(root_node.parent().is_none());
134        let prev = root_to_file_cache.insert(root_node, file_id);
135        assert!(prev.is_none() || prev == Some(file_id));
136    }
137
138    pub(super) fn get_or_insert_include_for(
139        &mut self,
140        db: &dyn HirDatabase,
141        file: EditionedFileId,
142    ) -> Option<MacroCallId> {
143        if let Some(&m) = self.included_file_cache.get(&file) {
144            return m;
145        }
146        self.included_file_cache.insert(file, None);
147        for &crate_id in db.relevant_crates(file.file_id(db)).iter() {
148            db.include_macro_invoc(crate_id).iter().for_each(|&(macro_call_id, file_id)| {
149                self.included_file_cache.insert(file_id, Some(macro_call_id));
150            });
151        }
152        self.included_file_cache.get(&file).copied().flatten()
153    }
154
155    pub(super) fn get_or_insert_expansion(
156        &mut self,
157        db: &dyn HirDatabase,
158        macro_file: MacroCallId,
159    ) -> &ExpansionInfo {
160        self.expansion_info_cache.entry(macro_file).or_insert_with(|| {
161            let exp_info = macro_file.expansion_info(db);
162
163            let InMacroFile { file_id, value } = exp_info.expanded();
164            Self::cache(&mut self.root_to_file_cache, value, file_id.into());
165
166            exp_info
167        })
168    }
169}
170
171pub(super) struct SourceToDefCtx<'db, 'cache> {
172    pub(super) db: &'db dyn HirDatabase,
173    pub(super) cache: &'cache mut SourceToDefCache,
174}
175
176impl SourceToDefCtx<'_, '_> {
177    pub(super) fn file_to_def(&mut self, file: FileId) -> &SmallVec<[ModuleId; 1]> {
178        let _p = tracing::info_span!("SourceToDefCtx::file_to_def").entered();
179        self.cache.file_to_def_cache.entry(file).or_insert_with(|| {
180            let mut mods = SmallVec::new();
181
182            for &crate_id in self.db.relevant_crates(file).iter() {
183                // Note: `mod` declarations in block modules cannot be supported here
184                let crate_def_map = crate_def_map(self.db, crate_id);
185                let n_mods = mods.len();
186                let modules = |file| crate_def_map.modules_for_file(self.db, file);
187                mods.extend(modules(file));
188                if mods.len() == n_mods {
189                    mods.extend(
190                        self.db
191                            .include_macro_invoc(crate_id)
192                            .iter()
193                            .filter(|&&(_, file_id)| file_id.file_id(self.db) == file)
194                            .flat_map(|&(macro_call_id, file_id)| {
195                                self.cache.included_file_cache.insert(file_id, Some(macro_call_id));
196                                modules(
197                                    macro_call_id
198                                        .lookup(self.db)
199                                        .kind
200                                        .file_id()
201                                        .original_file(self.db)
202                                        .file_id(self.db),
203                                )
204                            }),
205                    );
206                }
207            }
208            if mods.is_empty() {
209                // FIXME: detached file
210            }
211            mods
212        })
213    }
214
215    pub(super) fn module_to_def(&mut self, src: InFile<&ast::Module>) -> Option<ModuleId> {
216        let _p = tracing::info_span!("module_to_def").entered();
217        let parent_declaration = self
218            .parent_ancestors_with_macros(src.syntax_ref(), |_, ancestor, _| {
219                ancestor.map(Either::<ast::Module, ast::BlockExpr>::cast).transpose()
220            })
221            .map(|it| it.transpose());
222
223        let parent_module = match parent_declaration {
224            Some(Either::Right(parent_block)) => self
225                .block_to_def(parent_block.as_ref())
226                .map(|block| block_def_map(self.db, block).root_module_id()),
227            Some(Either::Left(parent_declaration)) => {
228                self.module_to_def(parent_declaration.as_ref())
229            }
230            None => {
231                let file_id = src.file_id.original_file(self.db);
232                self.file_to_def(file_id.file_id(self.db)).first().copied()
233            }
234        }?;
235
236        let child_name = src.value.name()?.as_name();
237        let def_map = parent_module.def_map(self.db);
238        let &child_id = def_map[parent_module].children.get(&child_name)?;
239        Some(child_id)
240    }
241
242    pub(super) fn source_file_to_def(&mut self, src: InFile<&ast::SourceFile>) -> Option<ModuleId> {
243        let _p = tracing::info_span!("source_file_to_def").entered();
244        let file_id = src.file_id.original_file(self.db);
245        self.file_to_def(file_id.file_id(self.db)).first().copied()
246    }
247
248    pub(super) fn trait_to_def(&mut self, src: InFile<&ast::Trait>) -> Option<TraitId> {
249        self.to_def(src, keys::TRAIT)
250    }
251    pub(super) fn impl_to_def(&mut self, src: InFile<&ast::Impl>) -> Option<ImplId> {
252        self.to_def(src, keys::IMPL)
253    }
254    pub(super) fn fn_to_def(&mut self, src: InFile<&ast::Fn>) -> Option<FunctionId> {
255        self.to_def(src, keys::FUNCTION)
256    }
257    pub(super) fn struct_to_def(&mut self, src: InFile<&ast::Struct>) -> Option<StructId> {
258        self.to_def(src, keys::STRUCT)
259    }
260    pub(super) fn enum_to_def(&mut self, src: InFile<&ast::Enum>) -> Option<EnumId> {
261        self.to_def(src, keys::ENUM)
262    }
263    pub(super) fn union_to_def(&mut self, src: InFile<&ast::Union>) -> Option<UnionId> {
264        self.to_def(src, keys::UNION)
265    }
266    pub(super) fn static_to_def(&mut self, src: InFile<&ast::Static>) -> Option<StaticId> {
267        self.to_def(src, keys::STATIC)
268    }
269    pub(super) fn const_to_def(&mut self, src: InFile<&ast::Const>) -> Option<ConstId> {
270        self.to_def(src, keys::CONST)
271    }
272    pub(super) fn type_alias_to_def(
273        &mut self,
274        src: InFile<&ast::TypeAlias>,
275    ) -> Option<TypeAliasId> {
276        self.to_def(src, keys::TYPE_ALIAS)
277    }
278    pub(super) fn record_field_to_def(
279        &mut self,
280        src: InFile<&ast::RecordField>,
281    ) -> Option<FieldId> {
282        self.to_def(src, keys::RECORD_FIELD)
283    }
284    pub(super) fn tuple_field_to_def(&mut self, src: InFile<&ast::TupleField>) -> Option<FieldId> {
285        self.to_def(src, keys::TUPLE_FIELD)
286    }
287    pub(super) fn block_to_def(&mut self, src: InFile<&ast::BlockExpr>) -> Option<BlockId> {
288        self.to_def(src, keys::BLOCK)
289    }
290    pub(super) fn enum_variant_to_def(
291        &mut self,
292        src: InFile<&ast::Variant>,
293    ) -> Option<EnumVariantId> {
294        self.to_def(src, keys::ENUM_VARIANT)
295    }
296    pub(super) fn extern_crate_to_def(
297        &mut self,
298        src: InFile<&ast::ExternCrate>,
299    ) -> Option<ExternCrateId> {
300        self.to_def(src, keys::EXTERN_CRATE)
301    }
302    pub(super) fn extern_block_to_def(
303        &mut self,
304        src: InFile<&ast::ExternBlock>,
305    ) -> Option<ExternBlockId> {
306        self.to_def(src, keys::EXTERN_BLOCK)
307    }
308    #[allow(dead_code)]
309    pub(super) fn use_to_def(&mut self, src: InFile<&ast::Use>) -> Option<UseId> {
310        self.to_def(src, keys::USE)
311    }
312    pub(super) fn adt_to_def(
313        &mut self,
314        InFile { file_id, value }: InFile<&ast::Adt>,
315    ) -> Option<AdtId> {
316        match value {
317            ast::Adt::Enum(it) => self.enum_to_def(InFile::new(file_id, it)).map(AdtId::EnumId),
318            ast::Adt::Struct(it) => {
319                self.struct_to_def(InFile::new(file_id, it)).map(AdtId::StructId)
320            }
321            ast::Adt::Union(it) => self.union_to_def(InFile::new(file_id, it)).map(AdtId::UnionId),
322        }
323    }
324
325    pub(super) fn asm_operand_to_def(
326        &mut self,
327        src: InFile<&ast::AsmOperandNamed>,
328    ) -> Option<InlineAsmOperand> {
329        let asm = src.value.syntax().parent().and_then(ast::AsmExpr::cast)?;
330        let index = asm
331            .asm_pieces()
332            .filter_map(|it| match it {
333                ast::AsmPiece::AsmOperandNamed(it) => Some(it),
334                _ => None,
335            })
336            .position(|it| it == *src.value)?;
337        let container = self.find_pat_or_label_container(src.syntax_ref())?;
338        let source_map = self.db.body_with_source_map(container).1;
339        let expr = source_map.node_expr(src.with_value(&ast::Expr::AsmExpr(asm)))?.as_expr()?;
340        Some(InlineAsmOperand { owner: container, expr, index })
341    }
342
343    pub(super) fn bind_pat_to_def(
344        &mut self,
345        src: InFile<&ast::IdentPat>,
346    ) -> Option<(DefWithBodyId, BindingId)> {
347        let container = self.find_pat_or_label_container(src.syntax_ref())?;
348        let (body, source_map) = self.db.body_with_source_map(container);
349        let src = src.cloned().map(ast::Pat::from);
350        let pat_id = source_map.node_pat(src.as_ref())?;
351        // the pattern could resolve to a constant, verify that this is not the case
352        if let crate::Pat::Bind { id, .. } = body[pat_id.as_pat()?] {
353            Some((container, id))
354        } else {
355            None
356        }
357    }
358    pub(super) fn self_param_to_def(
359        &mut self,
360        src: InFile<&ast::SelfParam>,
361    ) -> Option<(DefWithBodyId, BindingId)> {
362        let container = self.find_pat_or_label_container(src.syntax_ref())?;
363        let body = self.db.body(container);
364        Some((container, body.self_param?))
365    }
366    pub(super) fn label_to_def(
367        &mut self,
368        src: InFile<&ast::Label>,
369    ) -> Option<(DefWithBodyId, LabelId)> {
370        let container = self.find_pat_or_label_container(src.syntax_ref())?;
371        let source_map = self.db.body_with_source_map(container).1;
372
373        let label_id = source_map.node_label(src)?;
374        Some((container, label_id))
375    }
376
377    pub(super) fn label_ref_to_def(
378        &mut self,
379        src: InFile<&ast::Lifetime>,
380    ) -> Option<(DefWithBodyId, LabelId)> {
381        let break_or_continue = ast::Expr::cast(src.value.syntax().parent()?)?;
382        let container = self.find_pat_or_label_container(src.syntax_ref())?;
383        let (body, source_map) = self.db.body_with_source_map(container);
384        let break_or_continue =
385            source_map.node_expr(src.with_value(&break_or_continue))?.as_expr()?;
386        let (Expr::Break { label, .. } | Expr::Continue { label }) = body[break_or_continue] else {
387            return None;
388        };
389        Some((container, label?))
390    }
391
392    /// (AttrId, derive attribute call id, derive call ids)
393    pub(super) fn attr_to_derive_macro_call(
394        &mut self,
395        item: InFile<&ast::Adt>,
396        src: InFile<ast::Attr>,
397    ) -> Option<(AttrId, MacroCallId, &[Option<MacroCallId>])> {
398        let map = self.dyn_map(item)?;
399        map[keys::DERIVE_MACRO_CALL]
400            .get(&AstPtr::new(&src.value))
401            .map(|&(attr_id, call_id, ref ids)| (attr_id, call_id, &**ids))
402    }
403
404    // FIXME: Make this more fine grained! This should be a `adt_has_derives`!
405    pub(super) fn file_of_adt_has_derives(&mut self, adt: InFile<&ast::Adt>) -> bool {
406        self.dyn_map(adt).as_ref().is_some_and(|map| !map[keys::DERIVE_MACRO_CALL].is_empty())
407    }
408
409    pub(super) fn derive_macro_calls<'slf>(
410        &'slf mut self,
411        adt: InFile<&ast::Adt>,
412    ) -> Option<impl Iterator<Item = (AttrId, MacroCallId, &'slf [Option<MacroCallId>])> + use<'slf>>
413    {
414        self.dyn_map(adt).as_ref().map(|&map| {
415            let dyn_map = &map[keys::DERIVE_MACRO_CALL];
416            adt.value
417                .attrs()
418                .filter_map(move |attr| dyn_map.get(&AstPtr::new(&attr)))
419                .map(|&(attr_id, call_id, ref ids)| (attr_id, call_id, &**ids))
420        })
421    }
422
423    fn to_def<Ast: AstNode + 'static, ID: Copy + 'static>(
424        &mut self,
425        src: InFile<&Ast>,
426        key: Key<Ast, ID>,
427    ) -> Option<ID> {
428        self.dyn_map(src)?[key].get(&AstPtr::new(src.value)).copied()
429    }
430
431    fn dyn_map<Ast: AstNode + 'static>(&mut self, src: InFile<&Ast>) -> Option<&DynMap> {
432        let container = self.find_container(src.map(|it| it.syntax()))?;
433        Some(self.cache_for(container, src.file_id))
434    }
435
436    fn cache_for(&mut self, container: ChildContainer, file_id: HirFileId) -> &DynMap {
437        let db = self.db;
438        self.cache
439            .dynmap_cache
440            .entry((container, file_id))
441            .or_insert_with(|| container.child_by_source(db, file_id))
442    }
443
444    pub(super) fn item_to_macro_call(&mut self, src: InFile<&ast::Item>) -> Option<MacroCallId> {
445        self.to_def(src, keys::ATTR_MACRO_CALL)
446    }
447
448    pub(super) fn macro_call_to_macro_call(
449        &mut self,
450        src: InFile<&ast::MacroCall>,
451    ) -> Option<MacroCallId> {
452        self.to_def(src, keys::MACRO_CALL)
453    }
454
455    pub(super) fn type_param_to_def(
456        &mut self,
457        src: InFile<&ast::TypeParam>,
458    ) -> Option<TypeParamId> {
459        let container: ChildContainer = self.find_generic_param_container(src.syntax_ref())?.into();
460        let dyn_map = self.cache_for(container, src.file_id);
461        dyn_map[keys::TYPE_PARAM]
462            .get(&AstPtr::new(src.value))
463            .copied()
464            .map(TypeParamId::from_unchecked)
465    }
466
467    pub(super) fn lifetime_param_to_def(
468        &mut self,
469        src: InFile<&ast::LifetimeParam>,
470    ) -> Option<LifetimeParamId> {
471        let container: ChildContainer = self.find_generic_param_container(src.syntax_ref())?.into();
472        let dyn_map = self.cache_for(container, src.file_id);
473        dyn_map[keys::LIFETIME_PARAM].get(&AstPtr::new(src.value)).copied()
474    }
475
476    pub(super) fn const_param_to_def(
477        &mut self,
478        src: InFile<&ast::ConstParam>,
479    ) -> Option<ConstParamId> {
480        let container: ChildContainer = self.find_generic_param_container(src.syntax_ref())?.into();
481        let dyn_map = self.cache_for(container, src.file_id);
482        dyn_map[keys::CONST_PARAM]
483            .get(&AstPtr::new(src.value))
484            .copied()
485            .map(ConstParamId::from_unchecked)
486    }
487
488    pub(super) fn generic_param_to_def(
489        &mut self,
490        InFile { file_id, value }: InFile<&ast::GenericParam>,
491    ) -> Option<GenericParamId> {
492        match value {
493            ast::GenericParam::ConstParam(it) => {
494                self.const_param_to_def(InFile::new(file_id, it)).map(GenericParamId::ConstParamId)
495            }
496            ast::GenericParam::LifetimeParam(it) => self
497                .lifetime_param_to_def(InFile::new(file_id, it))
498                .map(GenericParamId::LifetimeParamId),
499            ast::GenericParam::TypeParam(it) => {
500                self.type_param_to_def(InFile::new(file_id, it)).map(GenericParamId::TypeParamId)
501            }
502        }
503    }
504
505    pub(super) fn macro_to_def(&mut self, src: InFile<&ast::Macro>) -> Option<MacroId> {
506        self.dyn_map(src).and_then(|it| match src.value {
507            ast::Macro::MacroRules(value) => {
508                it[keys::MACRO_RULES].get(&AstPtr::new(value)).copied().map(MacroId::from)
509            }
510            ast::Macro::MacroDef(value) => {
511                it[keys::MACRO2].get(&AstPtr::new(value)).copied().map(MacroId::from)
512            }
513        })
514    }
515
516    pub(super) fn proc_macro_to_def(&mut self, src: InFile<&ast::Fn>) -> Option<MacroId> {
517        self.dyn_map(src).and_then(|it| {
518            it[keys::PROC_MACRO].get(&AstPtr::new(src.value)).copied().map(MacroId::from)
519        })
520    }
521
522    pub(super) fn find_container(&mut self, src: InFile<&SyntaxNode>) -> Option<ChildContainer> {
523        let _p = tracing::info_span!("find_container").entered();
524        let def = self.parent_ancestors_with_macros(src, |this, container, child| {
525            this.container_to_def(container, child)
526        });
527        if let Some(def) = def {
528            return Some(def);
529        }
530
531        let def = self
532            .file_to_def(src.file_id.original_file(self.db).file_id(self.db))
533            .first()
534            .copied()?;
535        Some(def.into())
536    }
537
538    fn find_generic_param_container(&mut self, src: InFile<&SyntaxNode>) -> Option<GenericDefId> {
539        self.parent_ancestors_with_macros(src, |this, InFile { file_id, value }, _| {
540            let item = ast::Item::cast(value)?;
541            match &item {
542                ast::Item::Fn(it) => this.fn_to_def(InFile::new(file_id, it)).map(Into::into),
543                ast::Item::Struct(it) => {
544                    this.struct_to_def(InFile::new(file_id, it)).map(Into::into)
545                }
546                ast::Item::Enum(it) => this.enum_to_def(InFile::new(file_id, it)).map(Into::into),
547                ast::Item::Trait(it) => this.trait_to_def(InFile::new(file_id, it)).map(Into::into),
548                ast::Item::TypeAlias(it) => {
549                    this.type_alias_to_def(InFile::new(file_id, it)).map(Into::into)
550                }
551                ast::Item::Impl(it) => this.impl_to_def(InFile::new(file_id, it)).map(Into::into),
552                _ => None,
553            }
554        })
555    }
556
557    // FIXME: Remove this when we do inference in signatures
558    fn find_pat_or_label_container(&mut self, src: InFile<&SyntaxNode>) -> Option<DefWithBodyId> {
559        self.parent_ancestors_with_macros(src, |this, InFile { file_id, value }, _| {
560            let item = match ast::Item::cast(value.clone()) {
561                Some(it) => it,
562                None => {
563                    let variant = ast::Variant::cast(value)?;
564                    return this
565                        .enum_variant_to_def(InFile::new(file_id, &variant))
566                        .map(Into::into);
567                }
568            };
569            match &item {
570                ast::Item::Fn(it) => this.fn_to_def(InFile::new(file_id, it)).map(Into::into),
571                ast::Item::Const(it) => this.const_to_def(InFile::new(file_id, it)).map(Into::into),
572                ast::Item::Static(it) => {
573                    this.static_to_def(InFile::new(file_id, it)).map(Into::into)
574                }
575                _ => None,
576            }
577        })
578    }
579
580    /// Skips the attributed item that caused the macro invocation we are climbing up
581    fn parent_ancestors_with_macros<T>(
582        &mut self,
583        node: InFile<&SyntaxNode>,
584        mut cb: impl FnMut(
585            &mut Self,
586            /*parent: */ InFile<SyntaxNode>,
587            /*child: */ &SyntaxNode,
588        ) -> Option<T>,
589    ) -> Option<T> {
590        let parent = |this: &mut Self, node: InFile<&SyntaxNode>| match node.value.parent() {
591            Some(parent) => Some(node.with_value(parent)),
592            None => {
593                let macro_file = node.file_id.macro_file()?;
594                let expansion_info = this.cache.get_or_insert_expansion(this.db, macro_file);
595                expansion_info.arg().map(|node| node?.parent()).transpose()
596            }
597        };
598        let mut deepest_child_in_same_file = node.cloned();
599        let mut node = node.cloned();
600        while let Some(parent) = parent(self, node.as_ref()) {
601            if parent.file_id != node.file_id {
602                deepest_child_in_same_file = parent.clone();
603            }
604            if let Some(res) = cb(self, parent.clone(), &deepest_child_in_same_file.value) {
605                return Some(res);
606            }
607            node = parent;
608        }
609        None
610    }
611
612    fn container_to_def(
613        &mut self,
614        container: InFile<SyntaxNode>,
615        child: &SyntaxNode,
616    ) -> Option<ChildContainer> {
617        let cont = if let Some(item) = ast::Item::cast(container.value.clone()) {
618            match &item {
619                ast::Item::Module(it) => self.module_to_def(container.with_value(it))?.into(),
620                ast::Item::Trait(it) => self.trait_to_def(container.with_value(it))?.into(),
621                ast::Item::Impl(it) => self.impl_to_def(container.with_value(it))?.into(),
622                ast::Item::Enum(it) => self.enum_to_def(container.with_value(it))?.into(),
623                ast::Item::TypeAlias(it) => ChildContainer::GenericDefId(
624                    self.type_alias_to_def(container.with_value(it))?.into(),
625                ),
626                ast::Item::Struct(it) => {
627                    let def = self.struct_to_def(container.with_value(it))?;
628                    let is_in_body = it.field_list().is_some_and(|it| {
629                        it.syntax().text_range().contains(child.text_range().start())
630                    });
631                    if is_in_body {
632                        VariantId::from(def).into()
633                    } else {
634                        ChildContainer::GenericDefId(def.into())
635                    }
636                }
637                ast::Item::Union(it) => {
638                    let def = self.union_to_def(container.with_value(it))?;
639                    let is_in_body = it.record_field_list().is_some_and(|it| {
640                        it.syntax().text_range().contains(child.text_range().start())
641                    });
642                    if is_in_body {
643                        VariantId::from(def).into()
644                    } else {
645                        ChildContainer::GenericDefId(def.into())
646                    }
647                }
648                ast::Item::Fn(it) => {
649                    let def = self.fn_to_def(container.with_value(it))?;
650                    let child_offset = child.text_range().start();
651                    let is_in_body =
652                        it.body().is_some_and(|it| it.syntax().text_range().contains(child_offset));
653                    let in_param_pat = || {
654                        it.param_list().is_some_and(|it| {
655                            it.self_param()
656                                .and_then(|it| {
657                                    Some(TextRange::new(
658                                        it.syntax().text_range().start(),
659                                        it.name()?.syntax().text_range().end(),
660                                    ))
661                                })
662                                .is_some_and(|r| r.contains_inclusive(child_offset))
663                                || it
664                                    .params()
665                                    .filter_map(|it| it.pat())
666                                    .any(|it| it.syntax().text_range().contains(child_offset))
667                        })
668                    };
669                    if is_in_body || in_param_pat() {
670                        DefWithBodyId::from(def).into()
671                    } else {
672                        ChildContainer::GenericDefId(def.into())
673                    }
674                }
675                ast::Item::Static(it) => {
676                    let def = self.static_to_def(container.with_value(it))?;
677                    let is_in_body = it.body().is_some_and(|it| {
678                        it.syntax().text_range().contains(child.text_range().start())
679                    });
680                    if is_in_body {
681                        DefWithBodyId::from(def).into()
682                    } else {
683                        ChildContainer::GenericDefId(def.into())
684                    }
685                }
686                ast::Item::Const(it) => {
687                    let def = self.const_to_def(container.with_value(it))?;
688                    let is_in_body = it.body().is_some_and(|it| {
689                        it.syntax().text_range().contains(child.text_range().start())
690                    });
691                    if is_in_body {
692                        DefWithBodyId::from(def).into()
693                    } else {
694                        ChildContainer::GenericDefId(def.into())
695                    }
696                }
697                _ => return None,
698            }
699        } else if let Some(it) = ast::Variant::cast(container.value.clone()) {
700            let def = self.enum_variant_to_def(InFile::new(container.file_id, &it))?;
701            let is_in_body =
702                it.eq_token().is_some_and(|it| it.text_range().end() < child.text_range().start());
703            if is_in_body { DefWithBodyId::from(def).into() } else { VariantId::from(def).into() }
704        } else {
705            let it = match Either::<ast::Pat, ast::Name>::cast(container.value)? {
706                Either::Left(it) => ast::Param::cast(it.syntax().parent()?)?.syntax().parent(),
707                Either::Right(it) => ast::SelfParam::cast(it.syntax().parent()?)?.syntax().parent(),
708            }
709            .and_then(ast::ParamList::cast)?
710            .syntax()
711            .parent()
712            .and_then(ast::Fn::cast)?;
713            let def = self.fn_to_def(InFile::new(container.file_id, &it))?;
714            DefWithBodyId::from(def).into()
715        };
716        Some(cont)
717    }
718}
719
720#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
721pub(crate) enum ChildContainer {
722    DefWithBodyId(DefWithBodyId),
723    ModuleId(ModuleId),
724    TraitId(TraitId),
725    ImplId(ImplId),
726    EnumId(EnumId),
727    VariantId(VariantId),
728    /// XXX: this might be the same def as, for example an `EnumId`. However,
729    /// here the children are generic parameters, and not, eg enum variants.
730    GenericDefId(GenericDefId),
731}
732impl_from! {
733    DefWithBodyId,
734    ModuleId,
735    TraitId,
736    ImplId,
737    EnumId,
738    VariantId,
739    GenericDefId
740    for ChildContainer
741}
742
743impl ChildContainer {
744    fn child_by_source(self, db: &dyn HirDatabase, file_id: HirFileId) -> DynMap {
745        let _p = tracing::info_span!("ChildContainer::child_by_source").entered();
746        match self {
747            ChildContainer::DefWithBodyId(it) => it.child_by_source(db, file_id),
748            ChildContainer::ModuleId(it) => it.child_by_source(db, file_id),
749            ChildContainer::TraitId(it) => it.child_by_source(db, file_id),
750            ChildContainer::ImplId(it) => it.child_by_source(db, file_id),
751            ChildContainer::EnumId(it) => it.child_by_source(db, file_id),
752            ChildContainer::VariantId(it) => it.child_by_source(db, file_id),
753            ChildContainer::GenericDefId(it) => it.child_by_source(db, file_id),
754        }
755    }
756}