//! A simplified AST that only contains items. //! //! This is the primary IR used throughout `hir_def`. It is the input to the name resolution //! algorithm, as well as to the queries defined in `adt.rs`, `data.rs`, and most things in //! `attr.rs`. //! //! `ItemTree`s are built per `HirFileId`, from the syntax tree of the parsed file. This means that //! they are crate-independent: they don't know which `#[cfg]`s are active or which module they //! belong to, since those concepts don't exist at this level (a single `ItemTree` might be part of //! multiple crates, or might be included into the same crate twice via `#[path]`). //! //! One important purpose of this layer is to provide an "invalidation barrier" for incremental //! computations: when typing inside an item body, the `ItemTree` of the modified file is typically //! unaffected, so we don't have to recompute name resolution results or item data (see `data.rs`). //! //! The `ItemTree` for the currently open file can be displayed by using the VS Code command //! "rust-analyzer: Debug ItemTree". //! //! Compared to rustc's architecture, `ItemTree` has properties from both rustc's AST and HIR: many //! syntax-level Rust features are already desugared to simpler forms in the `ItemTree`, but name //! resolution has not yet been performed. `ItemTree`s are per-file, while rustc's AST and HIR are //! per-crate, because we are interested in incrementally computing it. //! //! The representation of items in the `ItemTree` should generally mirror the surface syntax: it is //! usually a bad idea to desugar a syntax-level construct to something that is structurally //! different here. Name resolution needs to be able to process attributes and expand macros //! (including attribute macros), and having a 1-to-1 mapping between syntax and the `ItemTree` //! avoids introducing subtle bugs. //! //! In general, any item in the `ItemTree` stores its `AstId`, which allows mapping it back to its //! surface syntax. mod attrs; mod lower; mod pretty; #[cfg(test)] mod tests; use std::{ fmt::{self, Debug}, hash::Hash, ops::Index, sync::OnceLock, }; use ast::{AstNode, StructKind}; use cfg::CfgOptions; use hir_expand::{ ExpandTo, HirFileId, mod_path::{ModPath, PathKind}, name::Name, }; use intern::Interned; use la_arena::{Idx, RawIdx}; use rustc_hash::FxHashMap; use span::{ AstIdNode, Edition, FileAstId, NO_DOWNMAP_ERASED_FILE_AST_ID_MARKER, Span, SpanAnchor, SyntaxContext, }; use stdx::never; use syntax::{SourceFile, SyntaxKind, ast, match_ast}; use thin_vec::ThinVec; use triomphe::Arc; use tt::TextRange; use crate::{BlockId, Lookup, attrs::parse_extra_crate_attrs, db::DefDatabase}; pub(crate) use crate::item_tree::{ attrs::*, lower::{lower_use_tree, visibility_from_ast}, }; #[derive(Copy, Clone, Eq, PartialEq)] pub(crate) struct RawVisibilityId(u32); impl RawVisibilityId { const PUB: Self = RawVisibilityId(u32::MAX); const PRIV_IMPLICIT: Self = RawVisibilityId(u32::MAX - 1); const PRIV_EXPLICIT: Self = RawVisibilityId(u32::MAX - 2); const PUB_CRATE: Self = RawVisibilityId(u32::MAX - 3); } impl fmt::Debug for RawVisibilityId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut f = f.debug_tuple("RawVisibilityId"); match *self { Self::PUB => f.field(&"pub"), Self::PRIV_IMPLICIT | Self::PRIV_EXPLICIT => f.field(&"pub(self)"), Self::PUB_CRATE => f.field(&"pub(crate)"), _ => f.field(&self.0), }; f.finish() } } fn lower_extra_crate_attrs<'a>( db: &dyn DefDatabase, crate_attrs_as_src: SourceFile, file_id: span::EditionedFileId, cfg_options: &dyn Fn() -> &'a CfgOptions, ) -> AttrsOrCfg { #[derive(Copy, Clone)] struct FakeSpanMap { file_id: span::EditionedFileId, } impl syntax_bridge::SpanMapper for FakeSpanMap { fn span_for(&self, range: TextRange) -> Span { Span { range, anchor: SpanAnchor { file_id: self.file_id, ast_id: NO_DOWNMAP_ERASED_FILE_AST_ID_MARKER, }, ctx: SyntaxContext::root(self.file_id.edition()), } } } let span_map = FakeSpanMap { file_id }; AttrsOrCfg::lower(db, &crate_attrs_as_src, cfg_options, span_map) } #[salsa_macros::tracked(returns(deref))] pub(crate) fn file_item_tree_query(db: &dyn DefDatabase, file_id: HirFileId) -> Arc { let _p = tracing::info_span!("file_item_tree_query", ?file_id).entered(); static EMPTY: OnceLock> = OnceLock::new(); let ctx = lower::Ctx::new(db, file_id); let syntax = db.parse_or_expand(file_id); let mut item_tree = match_ast! { match syntax { ast::SourceFile(file) => { let krate = file_id.krate(db); let root_file_id = krate.root_file_id(db); let extra_top_attrs = (file_id == root_file_id).then(|| { parse_extra_crate_attrs(db, krate).map(|crate_attrs| { let file_id = root_file_id.editioned_file_id(db); lower_extra_crate_attrs(db, crate_attrs, file_id, &|| ctx.cfg_options()) }) }).flatten(); let top_attrs = match extra_top_attrs { Some(attrs @ AttrsOrCfg::Enabled { .. }) => attrs.merge(ctx.lower_attrs(&file)), Some(attrs @ AttrsOrCfg::CfgDisabled(_)) => attrs, None => ctx.lower_attrs(&file) }; let mut item_tree = ctx.lower_module_items(&file); item_tree.top_attrs = top_attrs; item_tree }, ast::MacroItems(items) => { ctx.lower_module_items(&items) }, ast::MacroStmts(stmts) => { // The produced statements can include items, which should be added as top-level // items. ctx.lower_macro_stmts(stmts) }, _ => { if never!(syntax.kind() == SyntaxKind::ERROR, "{:?} from {:?} {}", file_id, syntax, syntax) { return Default::default(); } panic!("cannot create item tree for file {file_id:?} from {syntax:?} {syntax}"); }, } }; let ItemTree { top_level, top_attrs, attrs, vis, big_data, small_data } = &item_tree; if small_data.is_empty() && big_data.is_empty() && top_level.is_empty() && attrs.is_empty() && top_attrs.is_empty() && vis.arena.is_empty() { EMPTY .get_or_init(|| { Arc::new(ItemTree { top_level: Box::new([]), attrs: FxHashMap::default(), small_data: FxHashMap::default(), big_data: FxHashMap::default(), top_attrs: AttrsOrCfg::empty(), vis: ItemVisibilities { arena: ThinVec::new() }, }) }) .clone() } else { item_tree.shrink_to_fit(); Arc::new(item_tree) } } #[salsa_macros::tracked(returns(deref))] pub(crate) fn block_item_tree_query(db: &dyn DefDatabase, block: BlockId) -> Arc { let _p = tracing::info_span!("block_item_tree_query", ?block).entered(); static EMPTY: OnceLock> = OnceLock::new(); let loc = block.lookup(db); let block = loc.ast_id.to_node(db); let ctx = lower::Ctx::new(db, loc.ast_id.file_id); let mut item_tree = ctx.lower_block(&block); let ItemTree { top_level, top_attrs, attrs, vis, big_data, small_data } = &item_tree; if small_data.is_empty() && big_data.is_empty() && top_level.is_empty() && attrs.is_empty() && top_attrs.is_empty() && vis.arena.is_empty() { EMPTY .get_or_init(|| { Arc::new(ItemTree { top_level: Box::new([]), attrs: FxHashMap::default(), small_data: FxHashMap::default(), big_data: FxHashMap::default(), top_attrs: AttrsOrCfg::empty(), vis: ItemVisibilities { arena: ThinVec::new() }, }) }) .clone() } else { item_tree.shrink_to_fit(); Arc::new(item_tree) } } /// The item tree of a source file. #[derive(Debug, Default, Eq, PartialEq)] pub struct ItemTree { top_level: Box<[ModItemId]>, top_attrs: AttrsOrCfg, attrs: FxHashMap, AttrsOrCfg>, vis: ItemVisibilities, big_data: FxHashMap, BigModItem>, small_data: FxHashMap, SmallModItem>, } impl ItemTree { /// Returns an iterator over all items located at the top level of the `HirFileId` this /// `ItemTree` was created from. pub(crate) fn top_level_items(&self) -> &[ModItemId] { &self.top_level } /// Returns the inner attributes of the source file. pub(crate) fn top_level_attrs(&self) -> &AttrsOrCfg { &self.top_attrs } pub(crate) fn attrs(&self, of: FileAstId) -> Option<&AttrsOrCfg> { self.attrs.get(&of) } /// Returns a count of a few, expensive items. /// /// For more detail, see [`ItemTreeDataStats`]. pub fn item_tree_stats(&self) -> ItemTreeDataStats { let mut traits = 0; let mut impls = 0; let mut mods = 0; let mut macro_calls = 0; let mut macro_rules = 0; for item in self.small_data.values() { match item { SmallModItem::Trait(_) => traits += 1, SmallModItem::Impl(_) => impls += 1, SmallModItem::MacroRules(_) => macro_rules += 1, SmallModItem::MacroCall(_) => macro_calls += 1, _ => {} } } for item in self.big_data.values() { match item { BigModItem::Mod(_) => mods += 1, _ => {} } } ItemTreeDataStats { traits, impls, mods, macro_calls, macro_rules } } pub fn pretty_print(&self, db: &dyn DefDatabase, edition: Edition) -> String { pretty::print_item_tree(db, self, edition) } fn shrink_to_fit(&mut self) { let ItemTree { top_level: _, attrs, big_data, small_data, vis: _, top_attrs: _ } = self; attrs.shrink_to_fit(); big_data.shrink_to_fit(); small_data.shrink_to_fit(); } } #[derive(Default, Debug, Eq, PartialEq)] struct ItemVisibilities { arena: ThinVec, } #[derive(Debug, Clone, Eq, PartialEq)] enum SmallModItem { Const(Const), Enum(Enum), ExternBlock(ExternBlock), Function(Function), Impl(Impl), Macro2(Macro2), MacroCall(MacroCall), MacroRules(MacroRules), Static(Static), Struct(Struct), Trait(Trait), TypeAlias(TypeAlias), Union(Union), } #[derive(Debug, Clone, Eq, PartialEq)] enum BigModItem { ExternCrate(ExternCrate), Mod(Mod), Use(Use), } // `ModItem` is stored a bunch in `ItemTree`'s so we pay the max for each item. It should stay as // small as possible which is why we split them in two, most common ones are 3 usize but some rarer // ones are 5. #[cfg(target_pointer_width = "64")] const _: [(); std::mem::size_of::()] = [(); std::mem::size_of::<[usize; 5]>()]; #[cfg(target_pointer_width = "64")] const _: [(); std::mem::size_of::()] = [(); std::mem::size_of::<[usize; 3]>()]; #[derive(Default, Debug, Eq, PartialEq)] pub struct ItemTreeDataStats { pub traits: usize, pub impls: usize, pub mods: usize, pub macro_calls: usize, pub macro_rules: usize, } /// Trait implemented by all nodes in the item tree. pub(crate) trait ItemTreeNode: Clone { type Source: AstIdNode; } #[allow(type_alias_bounds)] pub(crate) type ItemTreeAstId = FileAstId; /// Identifies a particular [`ItemTree`]. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub struct TreeId { file: HirFileId, block: Option, } impl TreeId { pub(crate) fn new(file: HirFileId, block: Option) -> Self { Self { file, block } } pub(crate) fn item_tree<'db>(&self, db: &'db dyn DefDatabase) -> &'db ItemTree { match self.block { Some(block) => block_item_tree_query(db, block), None => file_item_tree_query(db, self.file), } } #[inline] pub fn file_id(self) -> HirFileId { self.file } pub(crate) fn is_block(self) -> bool { self.block.is_some() } } macro_rules! mod_items { ($mod_item:ident -> $( $typ:ident in $fld:ident -> $ast:ty ),+ $(,)? ) => { #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub(crate) enum $mod_item { $( $typ(FileAstId<$ast>), )+ } impl $mod_item { pub(crate) fn ast_id(self) -> FileAstId { match self { $($mod_item::$typ(it) => it.upcast()),+ } } } $( impl From> for $mod_item { fn from(id: FileAstId<$ast>) -> $mod_item { ModItemId::$typ(id) } } )+ $( impl ItemTreeNode for $typ { type Source = $ast; } impl Index> for ItemTree { type Output = $typ; #[allow(unused_imports)] fn index(&self, index: FileAstId<$ast>) -> &Self::Output { use BigModItem::*; use SmallModItem::*; match &self.$fld[&index.upcast()] { $typ(item) => item, _ => panic!("expected item of type `{}` at index `{:?}`", stringify!($typ), index), } } } )+ }; } mod_items! { ModItemId -> Const in small_data -> ast::Const, Enum in small_data -> ast::Enum, ExternBlock in small_data -> ast::ExternBlock, ExternCrate in big_data -> ast::ExternCrate, Function in small_data -> ast::Fn, Impl in small_data -> ast::Impl, Macro2 in small_data -> ast::MacroDef, MacroCall in small_data -> ast::MacroCall, MacroRules in small_data -> ast::MacroRules, Mod in big_data -> ast::Module, Static in small_data -> ast::Static, Struct in small_data -> ast::Struct, Trait in small_data -> ast::Trait, TypeAlias in small_data -> ast::TypeAlias, Union in small_data -> ast::Union, Use in big_data -> ast::Use, } impl Index for ItemTree { type Output = RawVisibility; fn index(&self, index: RawVisibilityId) -> &Self::Output { static VIS_PUB: RawVisibility = RawVisibility::Public; static VIS_PRIV_IMPLICIT: RawVisibility = RawVisibility::PubSelf(VisibilityExplicitness::Implicit); static VIS_PRIV_EXPLICIT: RawVisibility = RawVisibility::PubSelf(VisibilityExplicitness::Explicit); static VIS_PUB_CRATE: RawVisibility = RawVisibility::PubCrate; match index { RawVisibilityId::PRIV_IMPLICIT => &VIS_PRIV_IMPLICIT, RawVisibilityId::PRIV_EXPLICIT => &VIS_PRIV_EXPLICIT, RawVisibilityId::PUB => &VIS_PUB, RawVisibilityId::PUB_CRATE => &VIS_PUB_CRATE, _ => &self.vis.arena[index.0 as usize], } } } #[derive(Debug, Clone, Eq, PartialEq)] pub struct Use { pub(crate) visibility: RawVisibilityId, pub(crate) use_tree: UseTree, } #[derive(Debug, Clone, Eq, PartialEq)] pub struct UseTree { kind: UseTreeKind, } // FIXME: Would be nice to encode `None` into this // We could just use a `Name` where `_` well means `_` .. #[derive(Debug, Clone, PartialEq, Eq)] pub enum ImportAlias { /// Unnamed alias, as in `use Foo as _;` Underscore, /// Named alias Alias(Name), } impl ImportAlias { pub fn display(&self, edition: Edition) -> impl fmt::Display + '_ { ImportAliasDisplay { value: self, edition } } } struct ImportAliasDisplay<'a> { value: &'a ImportAlias, edition: Edition, } impl fmt::Display for ImportAliasDisplay<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.value { ImportAlias::Underscore => f.write_str("_"), ImportAlias::Alias(name) => fmt::Display::fmt(&name.display_no_db(self.edition), f), } } } #[derive(Debug, Clone, Eq, PartialEq)] pub enum UseTreeKind { /// ```ignore /// use path::to::Item; /// use path::to::Item as Renamed; /// use path::to::Trait as _; /// ``` Single { path: Interned, alias: Option }, /// ```ignore /// use *; // (invalid, but can occur in nested tree) /// use path::*; /// ``` Glob { path: Option> }, /// ```ignore /// use prefix::{self, Item, ...}; /// ``` Prefixed { prefix: Option>, list: Box<[UseTree]> }, } #[derive(Debug, Clone, Eq, PartialEq)] pub struct ExternCrate { pub name: Name, pub alias: Option, pub(crate) visibility: RawVisibilityId, } #[derive(Debug, Clone, Eq, PartialEq)] pub struct ExternBlock { pub(crate) children: Box<[ModItemId]>, } #[derive(Debug, Clone, Eq, PartialEq)] pub struct Function { pub name: Name, pub(crate) visibility: RawVisibilityId, } #[derive(Debug, Clone, Eq, PartialEq)] pub struct Struct { pub name: Name, pub(crate) visibility: RawVisibilityId, pub shape: FieldsShape, } #[derive(Debug, Clone, Eq, PartialEq)] pub struct Union { pub name: Name, pub(crate) visibility: RawVisibilityId, } #[derive(Debug, Clone, Eq, PartialEq)] pub struct Enum { pub name: Name, pub(crate) visibility: RawVisibilityId, } #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum FieldsShape { Record, Tuple, Unit, } /// Visibility of an item, not yet resolved. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum RawVisibility { /// `pub(in module)`, `pub(crate)` or `pub(super)`. Also private, which is /// equivalent to `pub(self)`. Module(Interned, VisibilityExplicitness), /// `pub(self)`. PubSelf(VisibilityExplicitness), /// `pub(crate)`. PubCrate, /// `pub`. Public, } /// Whether the item was imported through an explicit `pub(crate) use` or just a `use` without /// visibility. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum VisibilityExplicitness { Explicit, Implicit, } impl VisibilityExplicitness { pub fn is_explicit(&self) -> bool { matches!(self, Self::Explicit) } } #[derive(Debug, Clone, Eq, PartialEq)] pub struct Const { /// `None` for `const _: () = ();` pub name: Option, pub(crate) visibility: RawVisibilityId, } #[derive(Debug, Clone, Eq, PartialEq)] pub struct Static { pub name: Name, pub(crate) visibility: RawVisibilityId, } #[derive(Debug, Clone, Eq, PartialEq)] pub struct Trait { pub name: Name, pub(crate) visibility: RawVisibilityId, } #[derive(Debug, Clone, Eq, PartialEq)] pub struct Impl { pub is_trait_impl: bool, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct TypeAlias { pub name: Name, pub(crate) visibility: RawVisibilityId, } #[derive(Debug, Clone, Eq, PartialEq)] pub struct Mod { pub name: Name, pub(crate) visibility: RawVisibilityId, pub(crate) kind: ModKind, } #[derive(Debug, Clone, Eq, PartialEq)] pub(crate) enum ModKind { /// `mod m { ... }` Inline { items: Box<[ModItemId]> }, /// `mod m;` Outline, } #[derive(Debug, Clone, Eq, PartialEq)] pub struct MacroCall { /// Path to the called macro. pub path: Interned, pub expand_to: ExpandTo, pub ctxt: SyntaxContext, } #[derive(Debug, Clone, Eq, PartialEq)] pub struct MacroRules { /// The name of the declared macro. pub name: Name, } /// "Macros 2.0" macro definition. #[derive(Debug, Clone, Eq, PartialEq)] pub struct Macro2 { pub name: Name, pub(crate) visibility: RawVisibilityId, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ImportKind { /// The `ModPath` is imported normally. Plain, /// This is a glob-import of all names in the `ModPath`. Glob, /// This is a `some::path::self` import, which imports `some::path` only in type namespace. TypeOnly, } impl Use { /// Expands the `UseTree` into individually imported `ModPath`s. pub fn expand( &self, mut cb: impl FnMut(Idx, ModPath, ImportKind, Option), ) { self.use_tree.expand_impl(None, &mut 0, &mut cb) } } impl UseTree { /// The [`UseTreeKind`] of this `UseTree`. pub fn kind(&self) -> &UseTreeKind { &self.kind } fn expand_impl( &self, prefix: Option, counting_index: &mut u32, cb: &mut impl FnMut(Idx, ModPath, ImportKind, Option), ) { fn concat_mod_paths( prefix: Option, path: &ModPath, ) -> Option<(ModPath, ImportKind)> { match (prefix, path.kind) { (None, _) => Some((path.clone(), ImportKind::Plain)), (Some(mut prefix), PathKind::Plain) => { prefix.extend(path.segments().iter().cloned()); Some((prefix, ImportKind::Plain)) } (Some(mut prefix), PathKind::Super(n)) if n > 0 && prefix.segments().is_empty() => { // `super::super` + `super::rest` match &mut prefix.kind { PathKind::Super(m) => { cov_mark::hit!(concat_super_mod_paths); *m += n; prefix.extend(path.segments().iter().cloned()); Some((prefix, ImportKind::Plain)) } _ => None, } } (Some(prefix), PathKind::SELF) if path.segments().is_empty() => { // `some::path::self` == `some::path` Some((prefix, ImportKind::TypeOnly)) } (Some(_), _) => None, } } match &self.kind { UseTreeKind::Single { path, alias } => { if let Some((path, kind)) = concat_mod_paths(prefix, path) { cb(Idx::from_raw(RawIdx::from_u32(*counting_index)), path, kind, alias.clone()); } } UseTreeKind::Glob { path: Some(path) } => { if let Some((path, _)) = concat_mod_paths(prefix, path) { cb( Idx::from_raw(RawIdx::from_u32(*counting_index)), path, ImportKind::Glob, None, ); } } UseTreeKind::Glob { path: None } => { if let Some(prefix) = prefix { cb( Idx::from_raw(RawIdx::from_u32(*counting_index)), prefix, ImportKind::Glob, None, ); } } UseTreeKind::Prefixed { prefix: additional_prefix, list } => { let prefix = match additional_prefix { Some(path) => match concat_mod_paths(prefix, path) { Some((path, ImportKind::Plain)) => Some(path), _ => return, }, None => prefix, }; for tree in &**list { *counting_index += 1; tree.expand_impl(prefix.clone(), counting_index, cb); } } } } }