Skip to main content

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 base_db::relevant_crates;
89use either::Either;
90use hir_def::{
91    AdtId, BlockId, BuiltinDeriveImplId, ConstId, ConstParamId, DefWithBodyId, EnumId,
92    EnumVariantId, ExpressionStoreOwnerId, ExternBlockId, ExternCrateId, FieldId, FunctionId,
93    GenericDefId, GenericParamId, ImplId, LifetimeParamId, Lookup, MacroId, ModuleId, StaticId,
94    StructId, TraitId, TypeAliasId, TypeParamId, UnionId, UseId, VariantId,
95    dyn_map::{
96        DynMap,
97        keys::{self, Key},
98    },
99    expr_store::{Body, ExpressionStore},
100    hir::{BindingId, Expr, LabelId},
101    nameres::{block_def_map, crate_def_map},
102};
103use hir_expand::{
104    EditionedFileId, ExpansionInfo, HirFileId, InMacroFile, MacroCallId, attrs::AttrId,
105    name::AsName,
106};
107use rustc_hash::FxHashMap;
108use smallvec::SmallVec;
109use span::FileId;
110use stdx::impl_from;
111use syntax::{
112    AstNode, AstPtr, SyntaxNode,
113    ast::{self, HasAttrs, HasName},
114};
115use tt::TextRange;
116
117use crate::{InFile, InlineAsmOperand, db::HirDatabase, semantics::child_by_source::ChildBySource};
118
119#[derive(Default)]
120pub(super) struct SourceToDefCache {
121    pub(super) dynmap_cache: FxHashMap<(ChildContainer, HirFileId), DynMap>,
122    expansion_info_cache: FxHashMap<MacroCallId, ExpansionInfo>,
123    pub(super) file_to_def_cache: FxHashMap<FileId, SmallVec<[ModuleId; 1]>>,
124    pub(super) included_file_cache: FxHashMap<EditionedFileId, Option<MacroCallId>>,
125    /// Rootnode to HirFileId cache
126    pub(super) root_to_file_cache: FxHashMap<SyntaxNode, HirFileId>,
127}
128
129impl SourceToDefCache {
130    pub(super) fn cache(
131        root_to_file_cache: &mut FxHashMap<SyntaxNode, HirFileId>,
132        root_node: SyntaxNode,
133        file_id: HirFileId,
134    ) {
135        assert!(root_node.parent().is_none());
136        let prev = root_to_file_cache.insert(root_node, file_id);
137        assert!(prev.is_none() || prev == Some(file_id));
138    }
139
140    pub(super) fn get_or_insert_include_for(
141        &mut self,
142        db: &dyn HirDatabase,
143        file: EditionedFileId,
144    ) -> Option<MacroCallId> {
145        if let Some(&m) = self.included_file_cache.get(&file) {
146            return m;
147        }
148        self.included_file_cache.insert(file, None);
149        for &crate_id in relevant_crates(db, file.file_id(db)).iter() {
150            db.include_macro_invoc(crate_id).iter().for_each(|&(macro_call_id, file_id)| {
151                self.included_file_cache.insert(file_id, Some(macro_call_id));
152            });
153        }
154        self.included_file_cache.get(&file).copied().flatten()
155    }
156
157    pub(super) fn get_or_insert_expansion(
158        &mut self,
159        db: &dyn HirDatabase,
160        macro_file: MacroCallId,
161    ) -> &ExpansionInfo {
162        self.expansion_info_cache.entry(macro_file).or_insert_with(|| {
163            let exp_info = macro_file.expansion_info(db);
164
165            let InMacroFile { file_id, value } = exp_info.expanded();
166            Self::cache(&mut self.root_to_file_cache, value, file_id.into());
167
168            exp_info
169        })
170    }
171}
172
173pub(super) struct SourceToDefCtx<'db, 'cache> {
174    pub(super) db: &'db dyn HirDatabase,
175    pub(super) cache: &'cache mut SourceToDefCache,
176}
177
178impl SourceToDefCtx<'_, '_> {
179    pub(super) fn file_to_def(&mut self, file: FileId) -> &SmallVec<[ModuleId; 1]> {
180        let _p = tracing::info_span!("SourceToDefCtx::file_to_def").entered();
181        self.cache.file_to_def_cache.entry(file).or_insert_with(|| {
182            let mut mods = SmallVec::new();
183
184            for &crate_id in relevant_crates(self.db, file).iter() {
185                // Note: `mod` declarations in block modules cannot be supported here
186                let crate_def_map = crate_def_map(self.db, crate_id);
187                let n_mods = mods.len();
188                let modules = |file| crate_def_map.modules_for_file(self.db, file);
189                mods.extend(modules(file));
190                if mods.len() == n_mods {
191                    mods.extend(
192                        self.db
193                            .include_macro_invoc(crate_id)
194                            .iter()
195                            .filter(|&&(_, file_id)| file_id.file_id(self.db) == file)
196                            .flat_map(|&(macro_call_id, file_id)| {
197                                self.cache.included_file_cache.insert(file_id, Some(macro_call_id));
198                                modules(
199                                    macro_call_id
200                                        .lookup(self.db)
201                                        .kind
202                                        .file_id()
203                                        .original_file(self.db)
204                                        .file_id(self.db),
205                                )
206                            }),
207                    );
208                }
209            }
210            if mods.is_empty() {
211                // FIXME: detached file
212            }
213            mods
214        })
215    }
216
217    pub(super) fn module_to_def(&mut self, src: InFile<&ast::Module>) -> Option<ModuleId> {
218        let _p = tracing::info_span!("module_to_def").entered();
219        let parent_declaration = self
220            .parent_ancestors_with_macros(src.syntax_ref(), |_, ancestor, _| {
221                ancestor.map(Either::<ast::Module, ast::BlockExpr>::cast).transpose()
222            })
223            .map(|it| it.transpose());
224
225        let parent_module = match parent_declaration {
226            Some(Either::Right(parent_block)) => self
227                .block_to_def(parent_block.as_ref())
228                .map(|block| block_def_map(self.db, block).root_module_id()),
229            Some(Either::Left(parent_declaration)) => {
230                self.module_to_def(parent_declaration.as_ref())
231            }
232            None => {
233                let file_id = src.file_id.original_file(self.db);
234                self.file_to_def(file_id.file_id(self.db)).first().copied()
235            }
236        }?;
237
238        let child_name = src.value.name()?.as_name();
239        let def_map = parent_module.def_map(self.db);
240        let &child_id = def_map[parent_module].children.get(&child_name)?;
241        Some(child_id)
242    }
243
244    pub(super) fn source_file_to_def(&mut self, src: InFile<&ast::SourceFile>) -> Option<ModuleId> {
245        let _p = tracing::info_span!("source_file_to_def").entered();
246        let file_id = src.file_id.original_file(self.db);
247        self.file_to_def(file_id.file_id(self.db)).first().copied()
248    }
249
250    pub(super) fn trait_to_def(&mut self, src: InFile<&ast::Trait>) -> Option<TraitId> {
251        self.to_def(src, keys::TRAIT)
252    }
253    pub(super) fn impl_to_def(&mut self, src: InFile<&ast::Impl>) -> Option<ImplId> {
254        self.to_def(src, keys::IMPL)
255    }
256    pub(super) fn fn_to_def(&mut self, src: InFile<&ast::Fn>) -> Option<FunctionId> {
257        self.to_def(src, keys::FUNCTION)
258    }
259    pub(super) fn struct_to_def(&mut self, src: InFile<&ast::Struct>) -> Option<StructId> {
260        self.to_def(src, keys::STRUCT)
261    }
262    pub(super) fn enum_to_def(&mut self, src: InFile<&ast::Enum>) -> Option<EnumId> {
263        self.to_def(src, keys::ENUM)
264    }
265    pub(super) fn union_to_def(&mut self, src: InFile<&ast::Union>) -> Option<UnionId> {
266        self.to_def(src, keys::UNION)
267    }
268    pub(super) fn static_to_def(&mut self, src: InFile<&ast::Static>) -> Option<StaticId> {
269        self.to_def(src, keys::STATIC)
270    }
271    pub(super) fn const_to_def(&mut self, src: InFile<&ast::Const>) -> Option<ConstId> {
272        self.to_def(src, keys::CONST)
273    }
274    pub(super) fn type_alias_to_def(
275        &mut self,
276        src: InFile<&ast::TypeAlias>,
277    ) -> Option<TypeAliasId> {
278        self.to_def(src, keys::TYPE_ALIAS)
279    }
280    pub(super) fn record_field_to_def(
281        &mut self,
282        src: InFile<&ast::RecordField>,
283    ) -> Option<FieldId> {
284        self.to_def(src, keys::RECORD_FIELD)
285    }
286    pub(super) fn tuple_field_to_def(&mut self, src: InFile<&ast::TupleField>) -> Option<FieldId> {
287        self.to_def(src, keys::TUPLE_FIELD)
288    }
289    pub(super) fn block_to_def(&mut self, src: InFile<&ast::BlockExpr>) -> Option<BlockId> {
290        self.to_def(src, keys::BLOCK)
291    }
292    pub(super) fn enum_variant_to_def(
293        &mut self,
294        src: InFile<&ast::Variant>,
295    ) -> Option<EnumVariantId> {
296        self.to_def(src, keys::ENUM_VARIANT)
297    }
298    pub(super) fn extern_crate_to_def(
299        &mut self,
300        src: InFile<&ast::ExternCrate>,
301    ) -> Option<ExternCrateId> {
302        self.to_def(src, keys::EXTERN_CRATE)
303    }
304    pub(super) fn extern_block_to_def(
305        &mut self,
306        src: InFile<&ast::ExternBlock>,
307    ) -> Option<ExternBlockId> {
308        self.to_def(src, keys::EXTERN_BLOCK)
309    }
310    #[allow(dead_code)]
311    pub(super) fn use_to_def(&mut self, src: InFile<&ast::Use>) -> Option<UseId> {
312        self.to_def(src, keys::USE)
313    }
314    pub(super) fn adt_to_def(
315        &mut self,
316        InFile { file_id, value }: InFile<&ast::Adt>,
317    ) -> Option<AdtId> {
318        match value {
319            ast::Adt::Enum(it) => self.enum_to_def(InFile::new(file_id, it)).map(AdtId::EnumId),
320            ast::Adt::Struct(it) => {
321                self.struct_to_def(InFile::new(file_id, it)).map(AdtId::StructId)
322            }
323            ast::Adt::Union(it) => self.union_to_def(InFile::new(file_id, it)).map(AdtId::UnionId),
324        }
325    }
326
327    pub(super) fn asm_operand_to_def(
328        &mut self,
329        src: InFile<&ast::AsmOperandNamed>,
330    ) -> Option<InlineAsmOperand> {
331        let asm = src.value.syntax().parent().and_then(ast::AsmExpr::cast)?;
332        let index = asm
333            .asm_pieces()
334            .filter_map(|it| match it {
335                ast::AsmPiece::AsmOperandNamed(it) => Some(it),
336                _ => None,
337            })
338            .position(|it| it == *src.value)?;
339        let container = self.find_container(src.syntax_ref())?.as_expression_store_owner()?;
340        let (_, source_map) = ExpressionStore::with_source_map(self.db, container);
341        let expr = source_map.node_expr(src.with_value(&ast::Expr::AsmExpr(asm)))?.as_expr()?;
342        Some(InlineAsmOperand { owner: container, expr, index })
343    }
344
345    pub(super) fn bind_pat_to_def(
346        &mut self,
347        src: InFile<&ast::IdentPat>,
348    ) -> Option<(ExpressionStoreOwnerId, BindingId)> {
349        let container = self.find_container(src.syntax_ref())?.as_expression_store_owner()?;
350        let (store, source_map) = ExpressionStore::with_source_map(self.db, container);
351        let src = src.cloned().map(ast::Pat::from);
352        let pat_id = source_map.node_pat(src.as_ref())?;
353        // the pattern could resolve to a constant, verify that this is not the case
354        if let crate::Pat::Bind { id, .. } = store[pat_id.as_pat()?] {
355            Some((container, id))
356        } else {
357            None
358        }
359    }
360    pub(super) fn self_param_to_def(
361        &mut self,
362        src: InFile<&ast::SelfParam>,
363    ) -> Option<(DefWithBodyId, BindingId)> {
364        let container = self
365            .find_container(src.syntax_ref())?
366            .as_expression_store_owner()?
367            .as_def_with_body()?;
368        let body = Body::of(self.db, container);
369        Some((container, body.self_param?))
370    }
371    pub(super) fn label_to_def(
372        &mut self,
373        src: InFile<&ast::Label>,
374    ) -> Option<(ExpressionStoreOwnerId, LabelId)> {
375        let container = self.find_container(src.syntax_ref())?.as_expression_store_owner()?;
376        let (_, source_map) = ExpressionStore::with_source_map(self.db, container);
377        let label_id = source_map.node_label(src)?;
378        Some((container, label_id))
379    }
380
381    pub(super) fn label_ref_to_def(
382        &mut self,
383        src: InFile<&ast::Lifetime>,
384    ) -> Option<(ExpressionStoreOwnerId, LabelId)> {
385        let break_or_continue = ast::Expr::cast(src.value.syntax().parent()?)?;
386        let container = self.find_container(src.syntax_ref())?.as_expression_store_owner()?;
387        let (store, source_map) = ExpressionStore::with_source_map(self.db, container);
388        let break_or_continue =
389            source_map.node_expr(src.with_value(&break_or_continue))?.as_expr()?;
390        let (Expr::Break { label, .. } | Expr::Continue { label }) = store[break_or_continue]
391        else {
392            return None;
393        };
394        Some((container, label?))
395    }
396
397    /// (AttrId, derive attribute call id, derive call ids)
398    pub(super) fn attr_to_derive_macro_call(
399        &mut self,
400        item: InFile<&ast::Adt>,
401        src: InFile<ast::Meta>,
402    ) -> Option<(AttrId, MacroCallId, &[Option<Either<MacroCallId, BuiltinDeriveImplId>>])> {
403        let map = self.dyn_map(item)?;
404        map[keys::DERIVE_MACRO_CALL]
405            .get(&AstPtr::new(&src.value))
406            .map(|&(attr_id, call_id, ref ids)| (attr_id, call_id, &**ids))
407    }
408
409    // FIXME: Make this more fine grained! This should be a `adt_has_derives`!
410    pub(super) fn file_of_adt_has_derives(&mut self, adt: InFile<&ast::Adt>) -> bool {
411        self.dyn_map(adt).as_ref().is_some_and(|map| !map[keys::DERIVE_MACRO_CALL].is_empty())
412    }
413
414    pub(super) fn derive_macro_calls<'slf>(
415        &'slf mut self,
416        adt: InFile<&ast::Adt>,
417    ) -> Option<
418        impl Iterator<
419            Item = (AttrId, MacroCallId, &'slf [Option<Either<MacroCallId, BuiltinDeriveImplId>>]),
420        > + use<'slf>,
421    > {
422        self.dyn_map(adt).as_ref().map(|&map| {
423            let dyn_map = &map[keys::DERIVE_MACRO_CALL];
424            adt.value
425                .attrs()
426                .flat_map(|attr| attr.skip_cfg_attrs())
427                .filter_map(move |attr| dyn_map.get(&AstPtr::new(&attr)))
428                .map(|&(attr_id, call_id, ref ids)| (attr_id, call_id, &**ids))
429        })
430    }
431
432    fn to_def<Ast: AstNode + 'static, ID: Copy + 'static>(
433        &mut self,
434        src: InFile<&Ast>,
435        key: Key<Ast, ID>,
436    ) -> Option<ID> {
437        self.dyn_map(src)?[key].get(&AstPtr::new(src.value)).copied()
438    }
439
440    fn dyn_map<Ast: AstNode + 'static>(&mut self, src: InFile<&Ast>) -> Option<&DynMap> {
441        let container = self.find_container(src.map(|it| it.syntax()))?;
442        Some(self.cache_for(container, src.file_id))
443    }
444
445    fn cache_for(&mut self, container: ChildContainer, file_id: HirFileId) -> &DynMap {
446        let db = self.db;
447        self.cache
448            .dynmap_cache
449            .entry((container, file_id))
450            .or_insert_with(|| container.child_by_source(db, file_id))
451    }
452
453    pub(super) fn item_to_macro_call(&mut self, src: InFile<&ast::Item>) -> Option<MacroCallId> {
454        self.to_def(src, keys::ATTR_MACRO_CALL)
455    }
456
457    pub(super) fn macro_call_to_macro_call(
458        &mut self,
459        src: InFile<&ast::MacroCall>,
460    ) -> Option<MacroCallId> {
461        self.to_def(src, keys::MACRO_CALL)
462    }
463
464    pub(super) fn type_param_to_def(
465        &mut self,
466        src: InFile<&ast::TypeParam>,
467    ) -> Option<TypeParamId> {
468        let container: ChildContainer = self.find_generic_param_container(src.syntax_ref())?.into();
469        let dyn_map = self.cache_for(container, src.file_id);
470        dyn_map[keys::TYPE_PARAM]
471            .get(&AstPtr::new(src.value))
472            .copied()
473            .map(TypeParamId::from_unchecked)
474    }
475
476    pub(super) fn lifetime_param_to_def(
477        &mut self,
478        src: InFile<&ast::LifetimeParam>,
479    ) -> Option<LifetimeParamId> {
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::LIFETIME_PARAM].get(&AstPtr::new(src.value)).copied()
483    }
484
485    pub(super) fn const_param_to_def(
486        &mut self,
487        src: InFile<&ast::ConstParam>,
488    ) -> Option<ConstParamId> {
489        let container: ChildContainer = self.find_generic_param_container(src.syntax_ref())?.into();
490        let dyn_map = self.cache_for(container, src.file_id);
491        dyn_map[keys::CONST_PARAM]
492            .get(&AstPtr::new(src.value))
493            .copied()
494            .map(ConstParamId::from_unchecked)
495    }
496
497    pub(super) fn generic_param_to_def(
498        &mut self,
499        InFile { file_id, value }: InFile<&ast::GenericParam>,
500    ) -> Option<GenericParamId> {
501        match value {
502            ast::GenericParam::ConstParam(it) => {
503                self.const_param_to_def(InFile::new(file_id, it)).map(GenericParamId::ConstParamId)
504            }
505            ast::GenericParam::LifetimeParam(it) => self
506                .lifetime_param_to_def(InFile::new(file_id, it))
507                .map(GenericParamId::LifetimeParamId),
508            ast::GenericParam::TypeParam(it) => {
509                self.type_param_to_def(InFile::new(file_id, it)).map(GenericParamId::TypeParamId)
510            }
511        }
512    }
513
514    pub(super) fn macro_to_def(&mut self, src: InFile<&ast::Macro>) -> Option<MacroId> {
515        self.dyn_map(src).and_then(|it| match src.value {
516            ast::Macro::MacroRules(value) => {
517                it[keys::MACRO_RULES].get(&AstPtr::new(value)).copied().map(MacroId::from)
518            }
519            ast::Macro::MacroDef(value) => {
520                it[keys::MACRO2].get(&AstPtr::new(value)).copied().map(MacroId::from)
521            }
522        })
523    }
524
525    pub(super) fn proc_macro_to_def(&mut self, src: InFile<&ast::Fn>) -> Option<MacroId> {
526        self.dyn_map(src).and_then(|it| {
527            it[keys::PROC_MACRO].get(&AstPtr::new(src.value)).copied().map(MacroId::from)
528        })
529    }
530
531    pub(super) fn find_container(&mut self, src: InFile<&SyntaxNode>) -> Option<ChildContainer> {
532        let _p = tracing::info_span!("find_container").entered();
533        let def = self.parent_ancestors_with_macros(src, |this, container, child| {
534            this.container_to_def(container, child)
535        });
536        if let Some(def) = def {
537            return Some(def);
538        }
539
540        let def = self
541            .file_to_def(src.file_id.original_file(self.db).file_id(self.db))
542            .first()
543            .copied()?;
544        Some(def.into())
545    }
546
547    fn find_generic_param_container(&mut self, src: InFile<&SyntaxNode>) -> Option<GenericDefId> {
548        self.parent_ancestors_with_macros(src, |this, InFile { file_id, value }, _| {
549            let item = ast::Item::cast(value)?;
550            match &item {
551                ast::Item::Fn(it) => this.fn_to_def(InFile::new(file_id, it)).map(Into::into),
552                ast::Item::Struct(it) => {
553                    this.struct_to_def(InFile::new(file_id, it)).map(Into::into)
554                }
555                ast::Item::Enum(it) => this.enum_to_def(InFile::new(file_id, it)).map(Into::into),
556                ast::Item::Trait(it) => this.trait_to_def(InFile::new(file_id, it)).map(Into::into),
557                ast::Item::TypeAlias(it) => {
558                    this.type_alias_to_def(InFile::new(file_id, it)).map(Into::into)
559                }
560                ast::Item::Impl(it) => this.impl_to_def(InFile::new(file_id, it)).map(Into::into),
561                _ => None,
562            }
563        })
564    }
565
566    /// Skips the attributed item that caused the macro invocation we are climbing up
567    fn parent_ancestors_with_macros<T>(
568        &mut self,
569        node: InFile<&SyntaxNode>,
570        mut cb: impl FnMut(
571            &mut Self,
572            /*parent: */ InFile<SyntaxNode>,
573            /*child: */ &SyntaxNode,
574        ) -> Option<T>,
575    ) -> Option<T> {
576        let parent = |this: &mut Self, node: InFile<&SyntaxNode>| match node.value.parent() {
577            Some(parent) => Some(node.with_value(parent)),
578            None => {
579                let macro_file = node.file_id.macro_file()?;
580                let expansion_info = this.cache.get_or_insert_expansion(this.db, macro_file);
581                expansion_info.arg().map(|node| node?.parent()).transpose()
582            }
583        };
584        let mut deepest_child_in_same_file = node.cloned();
585        let mut node = node.cloned();
586        while let Some(parent) = parent(self, node.as_ref()) {
587            if parent.file_id != node.file_id {
588                deepest_child_in_same_file = parent.clone();
589            }
590            if let Some(res) = cb(self, parent.clone(), &deepest_child_in_same_file.value) {
591                return Some(res);
592            }
593            node = parent;
594        }
595        None
596    }
597
598    fn container_to_def(
599        &mut self,
600        container: InFile<SyntaxNode>,
601        child: &SyntaxNode,
602    ) -> Option<ChildContainer> {
603        let cont = if let Some(item) = ast::Item::cast(container.value.clone()) {
604            match &item {
605                ast::Item::Module(it) => self.module_to_def(container.with_value(it))?.into(),
606                ast::Item::Trait(it) => self.trait_to_def(container.with_value(it))?.into(),
607                ast::Item::Impl(it) => self.impl_to_def(container.with_value(it))?.into(),
608                ast::Item::Enum(it) => self.enum_to_def(container.with_value(it))?.into(),
609                ast::Item::TypeAlias(it) => ChildContainer::GenericDefId(
610                    self.type_alias_to_def(container.with_value(it))?.into(),
611                ),
612                ast::Item::Struct(it) => {
613                    let def = self.struct_to_def(container.with_value(it))?;
614                    let is_in_body = it.field_list().is_some_and(|it| {
615                        it.syntax().text_range().contains(child.text_range().start())
616                    });
617                    if is_in_body {
618                        VariantId::from(def).into()
619                    } else {
620                        ChildContainer::GenericDefId(def.into())
621                    }
622                }
623                ast::Item::Union(it) => {
624                    let def = self.union_to_def(container.with_value(it))?;
625                    let is_in_body = it.record_field_list().is_some_and(|it| {
626                        it.syntax().text_range().contains(child.text_range().start())
627                    });
628                    if is_in_body {
629                        VariantId::from(def).into()
630                    } else {
631                        ChildContainer::GenericDefId(def.into())
632                    }
633                }
634                ast::Item::Fn(it) => {
635                    let def = self.fn_to_def(container.with_value(it))?;
636                    let child_offset = child.text_range().start();
637                    let is_in_body =
638                        it.body().is_some_and(|it| it.syntax().text_range().contains(child_offset));
639                    let in_param_pat = || {
640                        it.param_list().is_some_and(|it| {
641                            it.self_param()
642                                .and_then(|it| {
643                                    Some(TextRange::new(
644                                        it.syntax().text_range().start(),
645                                        it.name()?.syntax().text_range().end(),
646                                    ))
647                                })
648                                .is_some_and(|r| r.contains_inclusive(child_offset))
649                                || it
650                                    .params()
651                                    .filter_map(|it| it.pat())
652                                    .any(|it| it.syntax().text_range().contains(child_offset))
653                        })
654                    };
655                    if is_in_body || in_param_pat() {
656                        DefWithBodyId::from(def).into()
657                    } else {
658                        ChildContainer::GenericDefId(def.into())
659                    }
660                }
661                ast::Item::Static(it) => {
662                    let def = self.static_to_def(container.with_value(it))?;
663                    let is_in_body = it.body().is_some_and(|it| {
664                        it.syntax().text_range().contains(child.text_range().start())
665                    });
666                    if is_in_body {
667                        DefWithBodyId::from(def).into()
668                    } else {
669                        ChildContainer::GenericDefId(def.into())
670                    }
671                }
672                ast::Item::Const(it) => {
673                    let def = self.const_to_def(container.with_value(it))?;
674                    let is_in_body = it.body().is_some_and(|it| {
675                        it.syntax().text_range().contains(child.text_range().start())
676                    });
677                    if is_in_body {
678                        DefWithBodyId::from(def).into()
679                    } else {
680                        ChildContainer::GenericDefId(def.into())
681                    }
682                }
683                _ => return None,
684            }
685        } else if let Some(it) = ast::Variant::cast(container.value.clone()) {
686            let def = self.enum_variant_to_def(InFile::new(container.file_id, &it))?;
687            let is_in_body =
688                it.eq_token().is_some_and(|it| it.text_range().end() < child.text_range().start());
689            if is_in_body { DefWithBodyId::from(def).into() } else { VariantId::from(def).into() }
690        } else {
691            let it = match Either::<ast::Pat, ast::Name>::cast(container.value)? {
692                Either::Left(it) => ast::Param::cast(it.syntax().parent()?)?.syntax().parent(),
693                Either::Right(it) => ast::SelfParam::cast(it.syntax().parent()?)?.syntax().parent(),
694            }
695            .and_then(ast::ParamList::cast)?
696            .syntax()
697            .parent()
698            .and_then(ast::Fn::cast)?;
699            let def = self.fn_to_def(InFile::new(container.file_id, &it))?;
700            DefWithBodyId::from(def).into()
701        };
702        Some(cont)
703    }
704}
705
706#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
707pub(crate) enum ChildContainer {
708    DefWithBodyId(DefWithBodyId),
709    ModuleId(ModuleId),
710    TraitId(TraitId),
711    ImplId(ImplId),
712    EnumId(EnumId),
713    VariantId(VariantId),
714    /// XXX: this might be the same def as, for example an `EnumId`. However,
715    /// here the children are generic parameters, and not, eg enum variants.
716    GenericDefId(GenericDefId),
717}
718impl_from! {
719    DefWithBodyId,
720    ModuleId,
721    TraitId,
722    ImplId,
723    EnumId,
724    VariantId,
725    GenericDefId
726    for ChildContainer
727}
728
729impl ChildContainer {
730    fn child_by_source(self, db: &dyn HirDatabase, file_id: HirFileId) -> DynMap {
731        let _p = tracing::info_span!("ChildContainer::child_by_source").entered();
732        match self {
733            ChildContainer::DefWithBodyId(it) => it.child_by_source(db, file_id),
734            ChildContainer::ModuleId(it) => it.child_by_source(db, file_id),
735            ChildContainer::TraitId(it) => it.child_by_source(db, file_id),
736            ChildContainer::ImplId(it) => it.child_by_source(db, file_id),
737            ChildContainer::EnumId(it) => it.child_by_source(db, file_id),
738            ChildContainer::VariantId(it) => it.child_by_source(db, file_id),
739            ChildContainer::GenericDefId(it) => it.child_by_source(db, file_id),
740        }
741    }
742
743    pub(crate) fn as_expression_store_owner(self) -> Option<ExpressionStoreOwnerId> {
744        match self {
745            ChildContainer::DefWithBodyId(it) => Some(it.into()),
746            ChildContainer::ModuleId(_) => None,
747            ChildContainer::TraitId(it) => {
748                Some(ExpressionStoreOwnerId::Signature(GenericDefId::TraitId(it)))
749            }
750            ChildContainer::EnumId(it) => {
751                Some(ExpressionStoreOwnerId::Signature(GenericDefId::AdtId(it.into())))
752            }
753            ChildContainer::ImplId(it) => {
754                Some(ExpressionStoreOwnerId::Signature(GenericDefId::ImplId(it)))
755            }
756            ChildContainer::VariantId(_) => None,
757            ChildContainer::GenericDefId(it) => Some(it.into()),
758        }
759    }
760}