//! 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::{ child_by_source::ChildBySource, dyn_map::{ keys::{self, Key}, DynMap, }, hir::{BindingId, Expr, LabelId}, AdtId, BlockId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId, FieldId, FunctionId, GenericDefId, GenericParamId, ImplId, LifetimeParamId, Lookup, MacroId, ModuleId, StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, TypeParamId, UnionId, UseId, VariantId, }; use hir_expand::{ attrs::AttrId, name::AsName, ExpansionInfo, HirFileId, HirFileIdExt, InMacroFile, MacroCallId, MacroFileIdExt, }; use rustc_hash::FxHashMap; use smallvec::SmallVec; use span::{EditionedFileId, FileId, MacroFileId}; use stdx::impl_from; use syntax::{ ast::{self, HasName}, AstNode, AstPtr, SyntaxNode, }; use crate::{db::HirDatabase, InFile, InlineAsmOperand, SemanticsImpl}; #[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>, } impl SourceToDefCache { 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.into()).iter() { db.include_macro_invoc(crate_id).iter().for_each(|&(macro_call_id, file_id)| { self.included_file_cache.insert(file_id, Some(MacroFileId { macro_call_id })); }); } self.included_file_cache.get(&file).copied().flatten() } pub(super) fn get_or_insert_expansion( &mut self, sema: &SemanticsImpl<'_>, macro_file: MacroFileId, ) -> &ExpansionInfo { self.expansion_info_cache.entry(macro_file).or_insert_with(|| { let exp_info = macro_file.expansion_info(sema.db.upcast()); let InMacroFile { file_id, value } = exp_info.expanded(); sema.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 = self.db.crate_def_map(crate_id); let n_mods = mods.len(); let modules = |file| { crate_def_map .modules_for_file(file) .map(|local_id| crate_def_map.module_id(local_id)) }; 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) .flat_map(|&(macro_call_id, file_id)| { self.cache .included_file_cache .insert(file_id, Some(MacroFileId { macro_call_id })); modules( macro_call_id .lookup(self.db.upcast()) .kind .file_id() .original_file(self.db.upcast()) .file_id(), ) }), ); } } 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 .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| self.db.block_def_map(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.upcast()); self.file_to_def(file_id.file_id()).first().copied() } }?; let child_name = src.value.name()?.as_name(); let def_map = parent_module.def_map(self.db.upcast()); let &child_id = def_map[parent_module.local_id].children.get(&child_name)?; Some(def_map.module_id(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.upcast()); self.file_to_def(file_id.file_id()).first().copied() } pub(super) fn trait_to_def(&mut self, src: InFile<&ast::Trait>) -> Option { self.to_def(src, keys::TRAIT) } pub(super) fn trait_alias_to_def( &mut self, src: InFile<&ast::TraitAlias>, ) -> Option { self.to_def(src, keys::TRAIT_ALIAS) } 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) } #[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); let expr = source_map.node_expr(src.with_value(&ast::Expr::AsmExpr(asm)))?; 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] { 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 (_body, source_map) = self.db.body_with_source_map(container); 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))?; let (Expr::Break { label, .. } | Expr::Continue { label }) = body[break_or_continue] else { return None; }; Some((container, label?)) } pub(super) fn item_to_macro_call(&mut self, src: InFile<&ast::Item>) -> Option { let map = self.dyn_map(src)?; map[keys::ATTR_MACRO_CALL].get(&AstPtr::new(src.value)).copied() } pub(super) fn macro_call_to_macro_call( &mut self, src: InFile<&ast::MacroCall>, ) -> Option { let map = self.dyn_map(src)?; map[keys::MACRO_CALL].get(&AstPtr::new(src.value)).copied() } /// (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)) } pub(super) fn has_derives(&mut self, adt: InFile<&ast::Adt>) -> bool { self.dyn_map(adt).as_ref().map_or(false, |map| !map[keys::DERIVE_MACRO_CALL].is_empty()) } 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 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.ancestors_with_macros(src, |this, container| this.container_to_def(container)); if let Some(def) = def { return Some(def); } let def = self .file_to_def(src.file_id.original_file(self.db.upcast()).file_id()) .first() .copied()?; Some(def.into()) } /// Skips the attributed item that caused the macro invocation we are climbing up fn ancestors_with_macros( &mut self, node: InFile<&SyntaxNode>, mut cb: impl FnMut(&mut Self, InFile) -> Option, ) -> Option { use hir_expand::MacroFileIdExt; 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 .expansion_info_cache .entry(macro_file) .or_insert_with(|| macro_file.expansion_info(this.db.upcast())); expansion_info.arg().map(|node| node?.parent()).transpose() } }; let mut node = node.cloned(); while let Some(parent) = parent(self, node.as_ref()) { if let Some(res) = cb(self, parent.clone()) { return Some(res); } node = parent; } None } fn find_generic_param_container(&mut self, src: InFile<&SyntaxNode>) -> Option { self.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::TraitAlias(it) => { this.trait_alias_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, } }) } fn find_pat_or_label_container(&mut self, src: InFile<&SyntaxNode>) -> Option { self.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.clone())?; 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, } }) } fn container_to_def(&mut self, container: InFile) -> 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::TraitAlias(it) => { self.trait_alias_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) => { self.type_alias_to_def(container.with_value(it))?.into() } ast::Item::Struct(it) => { let def = self.struct_to_def(container.with_value(it))?; VariantId::from(def).into() } ast::Item::Union(it) => { let def = self.union_to_def(container.with_value(it))?; VariantId::from(def).into() } ast::Item::Fn(it) => { let def = self.fn_to_def(container.with_value(it))?; DefWithBodyId::from(def).into() } ast::Item::Static(it) => { let def = self.static_to_def(container.with_value(it))?; DefWithBodyId::from(def).into() } ast::Item::Const(it) => { let def = self.const_to_def(container.with_value(it))?; DefWithBodyId::from(def).into() } _ => return None, } } else { let it = ast::Variant::cast(container.value)?; let def = self.enum_variant_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), TraitAliasId(TraitAliasId), ImplId(ImplId), EnumId(EnumId), VariantId(VariantId), TypeAliasId(TypeAliasId), /// 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, TraitAliasId, ImplId, EnumId, VariantId, TypeAliasId, 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(); let db = db.upcast(); 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::TraitAliasId(_) => DynMap::default(), 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::TypeAliasId(_) => DynMap::default(), ChildContainer::GenericDefId(it) => it.child_by_source(db, file_id), } } }