//! Maps *syntax* of various definitions to their semantic ids. //! //! This is a very interesting module, and, in some sense, can be considered the //! heart of the IDE parts of rust-analyzer. //! //! This module solves the following problem: //! //! > Given a piece of syntax, find the corresponding semantic definition (def). //! //! This problem is a part of more-or-less every IDE feature implemented. Every //! IDE functionality (like goto to definition), conceptually starts with a //! specific cursor position in a file. Starting with this text offset, we first //! figure out what syntactic construct are we at: is this a pattern, an //! expression, an item definition. //! //! Knowing only the syntax gives us relatively little info. For example, //! looking at the syntax of the function we can realize that it is a part of an //! `impl` block, but we won't be able to tell what trait function the current //! function overrides, and whether it does that correctly. For that, we need to //! go from [`ast::Fn`] to [`crate::Function`], and that's exactly what this //! module does. //! //! As syntax trees are values and don't know their place of origin/identity, //! this module also requires [`InFile`] wrappers to understand which specific //! real or macro-expanded file the tree comes from. //! //! The actual algorithm to resolve syntax to def is curious in two aspects: //! //! * It is recursive //! * It uses the inverse algorithm (what is the syntax for this def?) //! //! Specifically, the algorithm goes like this: //! //! 1. Find the syntactic container for the syntax. For example, field's //! container is the struct, and structs container is a module. //! 2. Recursively get the def corresponding to container. //! 3. Ask the container def for all child defs. These child defs contain //! the answer and answer's siblings. //! 4. For each child def, ask for it's source. //! 5. The child def whose source is the syntax node we've started with //! is the answer. //! //! It's interesting that both Roslyn and Kotlin contain very similar code //! shape. //! //! Let's take a look at Roslyn: //! //! //! //! //! The `GetDeclaredType` takes `Syntax` as input, and returns `Symbol` as //! output. First, it retrieves a `Symbol` for parent `Syntax`: //! //! * //! //! Then, it iterates parent symbol's children, looking for one which has the //! same text span as the original node: //! //! //! //! Now, let's look at Kotlin: //! //! //! //! This function starts with a syntax node (`KtExpression` is syntax, like all //! `Kt` nodes), and returns a def. It uses //! `getNonLocalContainingOrThisDeclaration` to get syntactic container for a //! current node. Then, `findSourceNonLocalFirDeclaration` gets `Fir` for this //! parent. Finally, `findElementIn` function traverses `Fir` children to find //! one with the same source we originally started with. //! //! One question is left though -- where does the recursion stops? This happens //! when we get to the file syntax node, which doesn't have a syntactic parent. //! In that case, we loop through all the crates that might contain this file //! and look for a module whose source is the given file. //! //! Note that the logic in this module is somewhat fundamentally imprecise -- //! due to conditional compilation and `#[path]` attributes, there's no //! injective mapping from syntax nodes to defs. This is not an edge case -- //! more or less every item in a `lib.rs` is a part of two distinct crates: a //! library with `--cfg test` and a library without. //! //! At the moment, we don't really handle this well and return the first answer //! that works. Ideally, we should first let the caller to pick a specific //! active crate for a given position, and then provide an API to resolve all //! syntax nodes against this specific crate. use either::Either; use hir_def::{ AdtId, BlockId, BuiltinDeriveImplId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExternBlockId, ExternCrateId, FieldId, FunctionId, GenericDefId, GenericParamId, ImplId, LifetimeParamId, Lookup, MacroId, ModuleId, StaticId, StructId, TraitId, TypeAliasId, TypeParamId, UnionId, UseId, VariantId, dyn_map::{ DynMap, keys::{self, Key}, }, hir::{BindingId, Expr, LabelId}, nameres::{block_def_map, crate_def_map}, }; use hir_expand::{ EditionedFileId, ExpansionInfo, HirFileId, InMacroFile, MacroCallId, attrs::AttrId, name::AsName, }; use rustc_hash::FxHashMap; use smallvec::SmallVec; use span::FileId; use stdx::impl_from; use syntax::{ AstNode, AstPtr, SyntaxNode, ast::{self, HasAttrs, HasName}, }; use tt::TextRange; use crate::{InFile, InlineAsmOperand, db::HirDatabase, semantics::child_by_source::ChildBySource}; #[derive(Default)] pub(super) struct SourceToDefCache { pub(super) dynmap_cache: FxHashMap<(ChildContainer, HirFileId), DynMap>, expansion_info_cache: FxHashMap, pub(super) file_to_def_cache: FxHashMap>, pub(super) included_file_cache: FxHashMap>, /// Rootnode to HirFileId cache pub(super) root_to_file_cache: FxHashMap, } impl SourceToDefCache { pub(super) fn cache( root_to_file_cache: &mut FxHashMap, root_node: SyntaxNode, file_id: HirFileId, ) { assert!(root_node.parent().is_none()); let prev = root_to_file_cache.insert(root_node, file_id); assert!(prev.is_none() || prev == Some(file_id)); } pub(super) fn get_or_insert_include_for( &mut self, db: &dyn HirDatabase, file: EditionedFileId, ) -> Option { if let Some(&m) = self.included_file_cache.get(&file) { return m; } self.included_file_cache.insert(file, None); for &crate_id in db.relevant_crates(file.file_id(db)).iter() { db.include_macro_invoc(crate_id).iter().for_each(|&(macro_call_id, file_id)| { self.included_file_cache.insert(file_id, Some(macro_call_id)); }); } self.included_file_cache.get(&file).copied().flatten() } pub(super) fn get_or_insert_expansion( &mut self, db: &dyn HirDatabase, macro_file: MacroCallId, ) -> &ExpansionInfo { self.expansion_info_cache.entry(macro_file).or_insert_with(|| { let exp_info = macro_file.expansion_info(db); let InMacroFile { file_id, value } = exp_info.expanded(); Self::cache(&mut self.root_to_file_cache, value, file_id.into()); exp_info }) } } pub(super) struct SourceToDefCtx<'db, 'cache> { pub(super) db: &'db dyn HirDatabase, pub(super) cache: &'cache mut SourceToDefCache, } impl SourceToDefCtx<'_, '_> { pub(super) fn file_to_def(&mut self, file: FileId) -> &SmallVec<[ModuleId; 1]> { let _p = tracing::info_span!("SourceToDefCtx::file_to_def").entered(); self.cache.file_to_def_cache.entry(file).or_insert_with(|| { let mut mods = SmallVec::new(); for &crate_id in self.db.relevant_crates(file).iter() { // Note: `mod` declarations in block modules cannot be supported here let crate_def_map = crate_def_map(self.db, crate_id); let n_mods = mods.len(); let modules = |file| crate_def_map.modules_for_file(self.db, file); mods.extend(modules(file)); if mods.len() == n_mods { mods.extend( self.db .include_macro_invoc(crate_id) .iter() .filter(|&&(_, file_id)| file_id.file_id(self.db) == file) .flat_map(|&(macro_call_id, file_id)| { self.cache.included_file_cache.insert(file_id, Some(macro_call_id)); modules( macro_call_id .lookup(self.db) .kind .file_id() .original_file(self.db) .file_id(self.db), ) }), ); } } if mods.is_empty() { // FIXME: detached file } mods }) } pub(super) fn module_to_def(&mut self, src: InFile<&ast::Module>) -> Option { let _p = tracing::info_span!("module_to_def").entered(); let parent_declaration = self .parent_ancestors_with_macros(src.syntax_ref(), |_, ancestor, _| { ancestor.map(Either::::cast).transpose() }) .map(|it| it.transpose()); let parent_module = match parent_declaration { Some(Either::Right(parent_block)) => self .block_to_def(parent_block.as_ref()) .map(|block| block_def_map(self.db, block).root_module_id()), Some(Either::Left(parent_declaration)) => { self.module_to_def(parent_declaration.as_ref()) } None => { let file_id = src.file_id.original_file(self.db); self.file_to_def(file_id.file_id(self.db)).first().copied() } }?; let child_name = src.value.name()?.as_name(); let def_map = parent_module.def_map(self.db); let &child_id = def_map[parent_module].children.get(&child_name)?; Some(child_id) } pub(super) fn source_file_to_def(&mut self, src: InFile<&ast::SourceFile>) -> Option { let _p = tracing::info_span!("source_file_to_def").entered(); let file_id = src.file_id.original_file(self.db); self.file_to_def(file_id.file_id(self.db)).first().copied() } pub(super) fn trait_to_def(&mut self, src: InFile<&ast::Trait>) -> Option { self.to_def(src, keys::TRAIT) } pub(super) fn impl_to_def(&mut self, src: InFile<&ast::Impl>) -> Option { self.to_def(src, keys::IMPL) } pub(super) fn fn_to_def(&mut self, src: InFile<&ast::Fn>) -> Option { self.to_def(src, keys::FUNCTION) } pub(super) fn struct_to_def(&mut self, src: InFile<&ast::Struct>) -> Option { self.to_def(src, keys::STRUCT) } pub(super) fn enum_to_def(&mut self, src: InFile<&ast::Enum>) -> Option { self.to_def(src, keys::ENUM) } pub(super) fn union_to_def(&mut self, src: InFile<&ast::Union>) -> Option { self.to_def(src, keys::UNION) } pub(super) fn static_to_def(&mut self, src: InFile<&ast::Static>) -> Option { self.to_def(src, keys::STATIC) } pub(super) fn const_to_def(&mut self, src: InFile<&ast::Const>) -> Option { self.to_def(src, keys::CONST) } pub(super) fn type_alias_to_def( &mut self, src: InFile<&ast::TypeAlias>, ) -> Option { self.to_def(src, keys::TYPE_ALIAS) } pub(super) fn record_field_to_def( &mut self, src: InFile<&ast::RecordField>, ) -> Option { self.to_def(src, keys::RECORD_FIELD) } pub(super) fn tuple_field_to_def(&mut self, src: InFile<&ast::TupleField>) -> Option { self.to_def(src, keys::TUPLE_FIELD) } pub(super) fn block_to_def(&mut self, src: InFile<&ast::BlockExpr>) -> Option { self.to_def(src, keys::BLOCK) } pub(super) fn enum_variant_to_def( &mut self, src: InFile<&ast::Variant>, ) -> Option { self.to_def(src, keys::ENUM_VARIANT) } pub(super) fn extern_crate_to_def( &mut self, src: InFile<&ast::ExternCrate>, ) -> Option { self.to_def(src, keys::EXTERN_CRATE) } pub(super) fn extern_block_to_def( &mut self, src: InFile<&ast::ExternBlock>, ) -> Option { self.to_def(src, keys::EXTERN_BLOCK) } #[allow(dead_code)] pub(super) fn use_to_def(&mut self, src: InFile<&ast::Use>) -> Option { self.to_def(src, keys::USE) } pub(super) fn adt_to_def( &mut self, InFile { file_id, value }: InFile<&ast::Adt>, ) -> Option { match value { ast::Adt::Enum(it) => self.enum_to_def(InFile::new(file_id, it)).map(AdtId::EnumId), ast::Adt::Struct(it) => { self.struct_to_def(InFile::new(file_id, it)).map(AdtId::StructId) } ast::Adt::Union(it) => self.union_to_def(InFile::new(file_id, it)).map(AdtId::UnionId), } } pub(super) fn asm_operand_to_def( &mut self, src: InFile<&ast::AsmOperandNamed>, ) -> Option { let asm = src.value.syntax().parent().and_then(ast::AsmExpr::cast)?; let index = asm .asm_pieces() .filter_map(|it| match it { ast::AsmPiece::AsmOperandNamed(it) => Some(it), _ => None, }) .position(|it| it == *src.value)?; let container = self.find_pat_or_label_container(src.syntax_ref())?; let source_map = self.db.body_with_source_map(container).1; let expr = source_map.node_expr(src.with_value(&ast::Expr::AsmExpr(asm)))?.as_expr()?; Some(InlineAsmOperand { owner: container, expr, index }) } pub(super) fn bind_pat_to_def( &mut self, src: InFile<&ast::IdentPat>, ) -> Option<(DefWithBodyId, BindingId)> { let container = self.find_pat_or_label_container(src.syntax_ref())?; let (body, source_map) = self.db.body_with_source_map(container); let src = src.cloned().map(ast::Pat::from); let pat_id = source_map.node_pat(src.as_ref())?; // the pattern could resolve to a constant, verify that this is not the case if let crate::Pat::Bind { id, .. } = body[pat_id.as_pat()?] { Some((container, id)) } else { None } } pub(super) fn self_param_to_def( &mut self, src: InFile<&ast::SelfParam>, ) -> Option<(DefWithBodyId, BindingId)> { let container = self.find_pat_or_label_container(src.syntax_ref())?; let body = self.db.body(container); Some((container, body.self_param?)) } pub(super) fn label_to_def( &mut self, src: InFile<&ast::Label>, ) -> Option<(DefWithBodyId, LabelId)> { let container = self.find_pat_or_label_container(src.syntax_ref())?; let source_map = self.db.body_with_source_map(container).1; let label_id = source_map.node_label(src)?; Some((container, label_id)) } pub(super) fn label_ref_to_def( &mut self, src: InFile<&ast::Lifetime>, ) -> Option<(DefWithBodyId, LabelId)> { let break_or_continue = ast::Expr::cast(src.value.syntax().parent()?)?; let container = self.find_pat_or_label_container(src.syntax_ref())?; let (body, source_map) = self.db.body_with_source_map(container); let break_or_continue = source_map.node_expr(src.with_value(&break_or_continue))?.as_expr()?; let (Expr::Break { label, .. } | Expr::Continue { label }) = body[break_or_continue] else { return None; }; Some((container, label?)) } /// (AttrId, derive attribute call id, derive call ids) pub(super) fn attr_to_derive_macro_call( &mut self, item: InFile<&ast::Adt>, src: InFile, ) -> Option<(AttrId, MacroCallId, &[Option>])> { let map = self.dyn_map(item)?; map[keys::DERIVE_MACRO_CALL] .get(&AstPtr::new(&src.value)) .map(|&(attr_id, call_id, ref ids)| (attr_id, call_id, &**ids)) } // FIXME: Make this more fine grained! This should be a `adt_has_derives`! pub(super) fn file_of_adt_has_derives(&mut self, adt: InFile<&ast::Adt>) -> bool { self.dyn_map(adt).as_ref().is_some_and(|map| !map[keys::DERIVE_MACRO_CALL].is_empty()) } pub(super) fn derive_macro_calls<'slf>( &'slf mut self, adt: InFile<&ast::Adt>, ) -> Option< impl Iterator< Item = (AttrId, MacroCallId, &'slf [Option>]), > + use<'slf>, > { self.dyn_map(adt).as_ref().map(|&map| { let dyn_map = &map[keys::DERIVE_MACRO_CALL]; adt.value .attrs() .filter_map(move |attr| dyn_map.get(&AstPtr::new(&attr))) .map(|&(attr_id, call_id, ref ids)| (attr_id, call_id, &**ids)) }) } fn to_def( &mut self, src: InFile<&Ast>, key: Key, ) -> Option { self.dyn_map(src)?[key].get(&AstPtr::new(src.value)).copied() } fn dyn_map(&mut self, src: InFile<&Ast>) -> Option<&DynMap> { let container = self.find_container(src.map(|it| it.syntax()))?; Some(self.cache_for(container, src.file_id)) } fn cache_for(&mut self, container: ChildContainer, file_id: HirFileId) -> &DynMap { let db = self.db; self.cache .dynmap_cache .entry((container, file_id)) .or_insert_with(|| container.child_by_source(db, file_id)) } pub(super) fn item_to_macro_call(&mut self, src: InFile<&ast::Item>) -> Option { self.to_def(src, keys::ATTR_MACRO_CALL) } pub(super) fn macro_call_to_macro_call( &mut self, src: InFile<&ast::MacroCall>, ) -> Option { self.to_def(src, keys::MACRO_CALL) } pub(super) fn type_param_to_def( &mut self, src: InFile<&ast::TypeParam>, ) -> Option { let container: ChildContainer = self.find_generic_param_container(src.syntax_ref())?.into(); let dyn_map = self.cache_for(container, src.file_id); dyn_map[keys::TYPE_PARAM] .get(&AstPtr::new(src.value)) .copied() .map(TypeParamId::from_unchecked) } pub(super) fn lifetime_param_to_def( &mut self, src: InFile<&ast::LifetimeParam>, ) -> Option { let container: ChildContainer = self.find_generic_param_container(src.syntax_ref())?.into(); let dyn_map = self.cache_for(container, src.file_id); dyn_map[keys::LIFETIME_PARAM].get(&AstPtr::new(src.value)).copied() } pub(super) fn const_param_to_def( &mut self, src: InFile<&ast::ConstParam>, ) -> Option { let container: ChildContainer = self.find_generic_param_container(src.syntax_ref())?.into(); let dyn_map = self.cache_for(container, src.file_id); dyn_map[keys::CONST_PARAM] .get(&AstPtr::new(src.value)) .copied() .map(ConstParamId::from_unchecked) } pub(super) fn generic_param_to_def( &mut self, InFile { file_id, value }: InFile<&ast::GenericParam>, ) -> Option { match value { ast::GenericParam::ConstParam(it) => { self.const_param_to_def(InFile::new(file_id, it)).map(GenericParamId::ConstParamId) } ast::GenericParam::LifetimeParam(it) => self .lifetime_param_to_def(InFile::new(file_id, it)) .map(GenericParamId::LifetimeParamId), ast::GenericParam::TypeParam(it) => { self.type_param_to_def(InFile::new(file_id, it)).map(GenericParamId::TypeParamId) } } } pub(super) fn macro_to_def(&mut self, src: InFile<&ast::Macro>) -> Option { self.dyn_map(src).and_then(|it| match src.value { ast::Macro::MacroRules(value) => { it[keys::MACRO_RULES].get(&AstPtr::new(value)).copied().map(MacroId::from) } ast::Macro::MacroDef(value) => { it[keys::MACRO2].get(&AstPtr::new(value)).copied().map(MacroId::from) } }) } pub(super) fn proc_macro_to_def(&mut self, src: InFile<&ast::Fn>) -> Option { self.dyn_map(src).and_then(|it| { it[keys::PROC_MACRO].get(&AstPtr::new(src.value)).copied().map(MacroId::from) }) } pub(super) fn find_container(&mut self, src: InFile<&SyntaxNode>) -> Option { let _p = tracing::info_span!("find_container").entered(); let def = self.parent_ancestors_with_macros(src, |this, container, child| { this.container_to_def(container, child) }); if let Some(def) = def { return Some(def); } let def = self .file_to_def(src.file_id.original_file(self.db).file_id(self.db)) .first() .copied()?; Some(def.into()) } fn find_generic_param_container(&mut self, src: InFile<&SyntaxNode>) -> Option { self.parent_ancestors_with_macros(src, |this, InFile { file_id, value }, _| { let item = ast::Item::cast(value)?; match &item { ast::Item::Fn(it) => this.fn_to_def(InFile::new(file_id, it)).map(Into::into), ast::Item::Struct(it) => { this.struct_to_def(InFile::new(file_id, it)).map(Into::into) } ast::Item::Enum(it) => this.enum_to_def(InFile::new(file_id, it)).map(Into::into), ast::Item::Trait(it) => this.trait_to_def(InFile::new(file_id, it)).map(Into::into), ast::Item::TypeAlias(it) => { this.type_alias_to_def(InFile::new(file_id, it)).map(Into::into) } ast::Item::Impl(it) => this.impl_to_def(InFile::new(file_id, it)).map(Into::into), _ => None, } }) } // FIXME: Remove this when we do inference in signatures fn find_pat_or_label_container(&mut self, src: InFile<&SyntaxNode>) -> Option { self.parent_ancestors_with_macros(src, |this, InFile { file_id, value }, _| { let item = match ast::Item::cast(value.clone()) { Some(it) => it, None => { let variant = ast::Variant::cast(value)?; return this .enum_variant_to_def(InFile::new(file_id, &variant)) .map(Into::into); } }; match &item { ast::Item::Fn(it) => this.fn_to_def(InFile::new(file_id, it)).map(Into::into), ast::Item::Const(it) => this.const_to_def(InFile::new(file_id, it)).map(Into::into), ast::Item::Static(it) => { this.static_to_def(InFile::new(file_id, it)).map(Into::into) } _ => None, } }) } /// Skips the attributed item that caused the macro invocation we are climbing up fn parent_ancestors_with_macros( &mut self, node: InFile<&SyntaxNode>, mut cb: impl FnMut( &mut Self, /*parent: */ InFile, /*child: */ &SyntaxNode, ) -> Option, ) -> Option { let parent = |this: &mut Self, node: InFile<&SyntaxNode>| match node.value.parent() { Some(parent) => Some(node.with_value(parent)), None => { let macro_file = node.file_id.macro_file()?; let expansion_info = this.cache.get_or_insert_expansion(this.db, macro_file); expansion_info.arg().map(|node| node?.parent()).transpose() } }; let mut deepest_child_in_same_file = node.cloned(); let mut node = node.cloned(); while let Some(parent) = parent(self, node.as_ref()) { if parent.file_id != node.file_id { deepest_child_in_same_file = parent.clone(); } if let Some(res) = cb(self, parent.clone(), &deepest_child_in_same_file.value) { return Some(res); } node = parent; } None } fn container_to_def( &mut self, container: InFile, child: &SyntaxNode, ) -> Option { let cont = if let Some(item) = ast::Item::cast(container.value.clone()) { match &item { ast::Item::Module(it) => self.module_to_def(container.with_value(it))?.into(), ast::Item::Trait(it) => self.trait_to_def(container.with_value(it))?.into(), ast::Item::Impl(it) => self.impl_to_def(container.with_value(it))?.into(), ast::Item::Enum(it) => self.enum_to_def(container.with_value(it))?.into(), ast::Item::TypeAlias(it) => ChildContainer::GenericDefId( self.type_alias_to_def(container.with_value(it))?.into(), ), ast::Item::Struct(it) => { let def = self.struct_to_def(container.with_value(it))?; let is_in_body = it.field_list().is_some_and(|it| { it.syntax().text_range().contains(child.text_range().start()) }); if is_in_body { VariantId::from(def).into() } else { ChildContainer::GenericDefId(def.into()) } } ast::Item::Union(it) => { let def = self.union_to_def(container.with_value(it))?; let is_in_body = it.record_field_list().is_some_and(|it| { it.syntax().text_range().contains(child.text_range().start()) }); if is_in_body { VariantId::from(def).into() } else { ChildContainer::GenericDefId(def.into()) } } ast::Item::Fn(it) => { let def = self.fn_to_def(container.with_value(it))?; let child_offset = child.text_range().start(); let is_in_body = it.body().is_some_and(|it| it.syntax().text_range().contains(child_offset)); let in_param_pat = || { it.param_list().is_some_and(|it| { it.self_param() .and_then(|it| { Some(TextRange::new( it.syntax().text_range().start(), it.name()?.syntax().text_range().end(), )) }) .is_some_and(|r| r.contains_inclusive(child_offset)) || it .params() .filter_map(|it| it.pat()) .any(|it| it.syntax().text_range().contains(child_offset)) }) }; if is_in_body || in_param_pat() { DefWithBodyId::from(def).into() } else { ChildContainer::GenericDefId(def.into()) } } ast::Item::Static(it) => { let def = self.static_to_def(container.with_value(it))?; let is_in_body = it.body().is_some_and(|it| { it.syntax().text_range().contains(child.text_range().start()) }); if is_in_body { DefWithBodyId::from(def).into() } else { ChildContainer::GenericDefId(def.into()) } } ast::Item::Const(it) => { let def = self.const_to_def(container.with_value(it))?; let is_in_body = it.body().is_some_and(|it| { it.syntax().text_range().contains(child.text_range().start()) }); if is_in_body { DefWithBodyId::from(def).into() } else { ChildContainer::GenericDefId(def.into()) } } _ => return None, } } else if let Some(it) = ast::Variant::cast(container.value.clone()) { let def = self.enum_variant_to_def(InFile::new(container.file_id, &it))?; let is_in_body = it.eq_token().is_some_and(|it| it.text_range().end() < child.text_range().start()); if is_in_body { DefWithBodyId::from(def).into() } else { VariantId::from(def).into() } } else { let it = match Either::::cast(container.value)? { Either::Left(it) => ast::Param::cast(it.syntax().parent()?)?.syntax().parent(), Either::Right(it) => ast::SelfParam::cast(it.syntax().parent()?)?.syntax().parent(), } .and_then(ast::ParamList::cast)? .syntax() .parent() .and_then(ast::Fn::cast)?; let def = self.fn_to_def(InFile::new(container.file_id, &it))?; DefWithBodyId::from(def).into() }; Some(cont) } } #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub(crate) enum ChildContainer { DefWithBodyId(DefWithBodyId), ModuleId(ModuleId), TraitId(TraitId), ImplId(ImplId), EnumId(EnumId), VariantId(VariantId), /// XXX: this might be the same def as, for example an `EnumId`. However, /// here the children are generic parameters, and not, eg enum variants. GenericDefId(GenericDefId), } impl_from! { DefWithBodyId, ModuleId, TraitId, ImplId, EnumId, VariantId, GenericDefId for ChildContainer } impl ChildContainer { fn child_by_source(self, db: &dyn HirDatabase, file_id: HirFileId) -> DynMap { let _p = tracing::info_span!("ChildContainer::child_by_source").entered(); match self { ChildContainer::DefWithBodyId(it) => it.child_by_source(db, file_id), ChildContainer::ModuleId(it) => it.child_by_source(db, file_id), ChildContainer::TraitId(it) => it.child_by_source(db, file_id), ChildContainer::ImplId(it) => it.child_by_source(db, file_id), ChildContainer::EnumId(it) => it.child_by_source(db, file_id), ChildContainer::VariantId(it) => it.child_by_source(db, file_id), ChildContainer::GenericDefId(it) => it.child_by_source(db, file_id), } } }