2025-06-15 10:25:45 +02:00

741 lines
23 KiB
Rust

//! 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 lower;
mod pretty;
#[cfg(test)]
mod tests;
use std::{
fmt::{self, Debug},
hash::Hash,
ops::Index,
sync::OnceLock,
};
use ast::{AstNode, StructKind};
use base_db::Crate;
use hir_expand::{
ExpandTo, HirFileId,
attrs::RawAttrs,
mod_path::{ModPath, PathKind},
name::Name,
};
use intern::Interned;
use la_arena::{Idx, RawIdx};
use rustc_hash::FxHashMap;
use span::{AstIdNode, Edition, FileAstId, SyntaxContext};
use stdx::never;
use syntax::{SyntaxKind, ast, match_ast};
use thin_vec::ThinVec;
use triomphe::Arc;
use crate::{BlockId, Lookup, attr::Attrs, db::DefDatabase};
pub(crate) use crate::item_tree::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()
}
}
#[salsa_macros::tracked(returns(deref))]
pub(crate) fn file_item_tree_query(db: &dyn DefDatabase, file_id: HirFileId) -> Arc<ItemTree> {
let _p = tracing::info_span!("file_item_tree_query", ?file_id).entered();
static EMPTY: OnceLock<Arc<ItemTree>> = 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 top_attrs = RawAttrs::new(db, &file, ctx.span_map());
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: RawAttrs::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<ItemTree> {
let _p = tracing::info_span!("block_item_tree_query", ?block).entered();
static EMPTY: OnceLock<Arc<ItemTree>> = 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: RawAttrs::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: RawAttrs,
attrs: FxHashMap<FileAstId<ast::Item>, RawAttrs>,
vis: ItemVisibilities,
big_data: FxHashMap<FileAstId<ast::Item>, BigModItem>,
small_data: FxHashMap<FileAstId<ast::Item>, 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_raw_attrs(&self) -> &RawAttrs {
&self.top_attrs
}
/// Returns the inner attributes of the source file.
pub(crate) fn top_level_attrs(&self, db: &dyn DefDatabase, krate: Crate) -> Attrs {
Attrs::expand_cfg_attr(db, krate, self.top_attrs.clone())
}
pub(crate) fn raw_attrs(&self, of: FileAstId<ast::Item>) -> &RawAttrs {
self.attrs.get(&of).unwrap_or(&RawAttrs::EMPTY)
}
pub(crate) fn attrs(
&self,
db: &dyn DefDatabase,
krate: Crate,
of: FileAstId<ast::Item>,
) -> Attrs {
Attrs::expand_cfg_attr(db, krate, self.raw_attrs(of).clone())
}
/// 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<RawVisibility>,
}
#[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),
TraitAlias(TraitAlias),
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::<BigModItem>()] = [(); std::mem::size_of::<[usize; 5]>()];
#[cfg(target_pointer_width = "64")]
const _: [(); std::mem::size_of::<SmallModItem>()] = [(); 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<T: ItemTreeNode> = FileAstId<T::Source>;
/// Identifies a particular [`ItemTree`].
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub struct TreeId {
file: HirFileId,
block: Option<BlockId>,
}
impl TreeId {
pub(crate) fn new(file: HirFileId, block: Option<BlockId>) -> 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<ast::Item> {
match self {
$($mod_item::$typ(it) => it.upcast()),+
}
}
}
$(
impl From<FileAstId<$ast>> for $mod_item {
fn from(id: FileAstId<$ast>) -> $mod_item {
ModItemId::$typ(id)
}
}
)+
$(
impl ItemTreeNode for $typ {
type Source = $ast;
}
impl Index<FileAstId<$ast>> 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,
TraitAlias in small_data -> ast::TraitAlias,
TypeAlias in small_data -> ast::TypeAlias,
Union in small_data -> ast::Union,
Use in big_data -> ast::Use,
}
impl Index<RawVisibilityId> 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<ModPath>, alias: Option<ImportAlias> },
/// ```ignore
/// use *; // (invalid, but can occur in nested tree)
/// use path::*;
/// ```
Glob { path: Option<Interned<ModPath>> },
/// ```ignore
/// use prefix::{self, Item, ...};
/// ```
Prefixed { prefix: Option<Interned<ModPath>>, list: Box<[UseTree]> },
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ExternCrate {
pub name: Name,
pub alias: Option<ImportAlias>,
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<ModPath>, 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<Name>,
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 TraitAlias {
pub name: Name,
pub(crate) visibility: RawVisibilityId,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Impl {}
#[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<ModPath>,
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<ast::UseTree>, ModPath, ImportKind, Option<ImportAlias>),
) {
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<ModPath>,
counting_index: &mut u32,
cb: &mut impl FnMut(Idx<ast::UseTree>, ModPath, ImportKind, Option<ImportAlias>),
) {
fn concat_mod_paths(
prefix: Option<ModPath>,
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);
}
}
}
}
}