diff options
Diffstat (limited to 'src/tools/rust-analyzer/crates/hir-def')
58 files changed, 27719 insertions, 0 deletions
diff --git a/src/tools/rust-analyzer/crates/hir-def/Cargo.toml b/src/tools/rust-analyzer/crates/hir-def/Cargo.toml new file mode 100644 index 000000000..e8cff2f3e --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "hir-def" +version = "0.0.0" +description = "TBD" +license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.57" + +[lib] +doctest = false + +[dependencies] +anymap = "1.0.0-beta.2" +arrayvec = "0.7.2" +bitflags = "1.3.2" +cov-mark = "2.0.0-pre.1" +# We need to freeze the version of the crate, as the raw-api feature is considered unstable +dashmap = { version = "=5.3.4", features = ["raw-api"] } +drop_bomb = "0.1.5" +either = "1.7.0" +fst = { version = "0.4.7", default-features = false } +hashbrown = { version = "0.12.1", default-features = false } +indexmap = "1.9.1" +itertools = "0.10.3" +la-arena = { version = "0.3.0", path = "../../lib/la-arena" } +once_cell = "1.12.0" +rustc-hash = "1.1.0" +smallvec = "1.9.0" +tracing = "0.1.35" + +stdx = { path = "../stdx", version = "0.0.0" } +base-db = { path = "../base-db", version = "0.0.0" } +syntax = { path = "../syntax", version = "0.0.0" } +profile = { path = "../profile", version = "0.0.0" } +hir-expand = { path = "../hir-expand", version = "0.0.0" } +mbe = { path = "../mbe", version = "0.0.0" } +cfg = { path = "../cfg", version = "0.0.0" } +tt = { path = "../tt", version = "0.0.0" } +limit = { path = "../limit", version = "0.0.0" } + +[dev-dependencies] +test-utils = { path = "../test-utils" } +expect-test = "1.4.0" diff --git a/src/tools/rust-analyzer/crates/hir-def/src/adt.rs b/src/tools/rust-analyzer/crates/hir-def/src/adt.rs new file mode 100644 index 000000000..277135d6d --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/adt.rs @@ -0,0 +1,365 @@ +//! Defines hir-level representation of structs, enums and unions + +use std::sync::Arc; + +use base_db::CrateId; +use either::Either; +use hir_expand::{ + name::{AsName, Name}, + InFile, +}; +use la_arena::{Arena, ArenaMap}; +use syntax::ast::{self, HasName, HasVisibility}; +use tt::{Delimiter, DelimiterKind, Leaf, Subtree, TokenTree}; + +use crate::{ + body::{CfgExpander, LowerCtx}, + db::DefDatabase, + intern::Interned, + item_tree::{AttrOwner, Field, Fields, ItemTree, ModItem, RawVisibilityId}, + src::HasChildSource, + src::HasSource, + trace::Trace, + type_ref::TypeRef, + visibility::RawVisibility, + EnumId, LocalEnumVariantId, LocalFieldId, Lookup, ModuleId, StructId, UnionId, VariantId, +}; +use cfg::CfgOptions; + +/// Note that we use `StructData` for unions as well! +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StructData { + pub name: Name, + pub variant_data: Arc<VariantData>, + pub repr: Option<ReprKind>, + pub visibility: RawVisibility, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EnumData { + pub name: Name, + pub variants: Arena<EnumVariantData>, + pub visibility: RawVisibility, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EnumVariantData { + pub name: Name, + pub variant_data: Arc<VariantData>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum VariantData { + Record(Arena<FieldData>), + Tuple(Arena<FieldData>), + Unit, +} + +/// A single field of an enum variant or struct +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FieldData { + pub name: Name, + pub type_ref: Interned<TypeRef>, + pub visibility: RawVisibility, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ReprKind { + Packed, + Other, +} + +fn repr_from_value( + db: &dyn DefDatabase, + krate: CrateId, + item_tree: &ItemTree, + of: AttrOwner, +) -> Option<ReprKind> { + item_tree.attrs(db, krate, of).by_key("repr").tt_values().find_map(parse_repr_tt) +} + +fn parse_repr_tt(tt: &Subtree) -> Option<ReprKind> { + match tt.delimiter { + Some(Delimiter { kind: DelimiterKind::Parenthesis, .. }) => {} + _ => return None, + } + + let mut it = tt.token_trees.iter(); + match it.next()? { + TokenTree::Leaf(Leaf::Ident(ident)) if ident.text == "packed" => Some(ReprKind::Packed), + _ => Some(ReprKind::Other), + } +} + +impl StructData { + pub(crate) fn struct_data_query(db: &dyn DefDatabase, id: StructId) -> Arc<StructData> { + let loc = id.lookup(db); + let krate = loc.container.krate; + let item_tree = loc.id.item_tree(db); + let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into()); + let cfg_options = db.crate_graph()[loc.container.krate].cfg_options.clone(); + + let strukt = &item_tree[loc.id.value]; + let variant_data = lower_fields(db, krate, &item_tree, &cfg_options, &strukt.fields, None); + Arc::new(StructData { + name: strukt.name.clone(), + variant_data: Arc::new(variant_data), + repr, + visibility: item_tree[strukt.visibility].clone(), + }) + } + pub(crate) fn union_data_query(db: &dyn DefDatabase, id: UnionId) -> Arc<StructData> { + let loc = id.lookup(db); + let krate = loc.container.krate; + let item_tree = loc.id.item_tree(db); + let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into()); + let cfg_options = db.crate_graph()[loc.container.krate].cfg_options.clone(); + + let union = &item_tree[loc.id.value]; + let variant_data = lower_fields(db, krate, &item_tree, &cfg_options, &union.fields, None); + + Arc::new(StructData { + name: union.name.clone(), + variant_data: Arc::new(variant_data), + repr, + visibility: item_tree[union.visibility].clone(), + }) + } +} + +impl EnumData { + pub(crate) fn enum_data_query(db: &dyn DefDatabase, e: EnumId) -> Arc<EnumData> { + let loc = e.lookup(db); + let krate = loc.container.krate; + let item_tree = loc.id.item_tree(db); + let cfg_options = db.crate_graph()[krate].cfg_options.clone(); + + let enum_ = &item_tree[loc.id.value]; + let mut variants = Arena::new(); + for tree_id in enum_.variants.clone() { + if item_tree.attrs(db, krate, tree_id.into()).is_cfg_enabled(&cfg_options) { + let var = &item_tree[tree_id]; + let var_data = lower_fields( + db, + krate, + &item_tree, + &cfg_options, + &var.fields, + Some(enum_.visibility), + ); + + variants.alloc(EnumVariantData { + name: var.name.clone(), + variant_data: Arc::new(var_data), + }); + } + } + + Arc::new(EnumData { + name: enum_.name.clone(), + variants, + visibility: item_tree[enum_.visibility].clone(), + }) + } + + pub fn variant(&self, name: &Name) -> Option<LocalEnumVariantId> { + let (id, _) = self.variants.iter().find(|(_id, data)| &data.name == name)?; + Some(id) + } +} + +impl HasChildSource<LocalEnumVariantId> for EnumId { + type Value = ast::Variant; + fn child_source( + &self, + db: &dyn DefDatabase, + ) -> InFile<ArenaMap<LocalEnumVariantId, Self::Value>> { + let src = self.lookup(db).source(db); + let mut trace = Trace::new_for_map(); + lower_enum(db, &mut trace, &src, self.lookup(db).container); + src.with_value(trace.into_map()) + } +} + +fn lower_enum( + db: &dyn DefDatabase, + trace: &mut Trace<EnumVariantData, ast::Variant>, + ast: &InFile<ast::Enum>, + module_id: ModuleId, +) { + let expander = CfgExpander::new(db, ast.file_id, module_id.krate); + let variants = ast + .value + .variant_list() + .into_iter() + .flat_map(|it| it.variants()) + .filter(|var| expander.is_cfg_enabled(db, var)); + for var in variants { + trace.alloc( + || var.clone(), + || EnumVariantData { + name: var.name().map_or_else(Name::missing, |it| it.as_name()), + variant_data: Arc::new(VariantData::new(db, ast.with_value(var.kind()), module_id)), + }, + ); + } +} + +impl VariantData { + fn new(db: &dyn DefDatabase, flavor: InFile<ast::StructKind>, module_id: ModuleId) -> Self { + let mut expander = CfgExpander::new(db, flavor.file_id, module_id.krate); + let mut trace = Trace::new_for_arena(); + match lower_struct(db, &mut expander, &mut trace, &flavor) { + StructKind::Tuple => VariantData::Tuple(trace.into_arena()), + StructKind::Record => VariantData::Record(trace.into_arena()), + StructKind::Unit => VariantData::Unit, + } + } + + pub fn fields(&self) -> &Arena<FieldData> { + const EMPTY: &Arena<FieldData> = &Arena::new(); + match &self { + VariantData::Record(fields) | VariantData::Tuple(fields) => fields, + _ => EMPTY, + } + } + + pub fn field(&self, name: &Name) -> Option<LocalFieldId> { + self.fields().iter().find_map(|(id, data)| if &data.name == name { Some(id) } else { None }) + } + + pub fn kind(&self) -> StructKind { + match self { + VariantData::Record(_) => StructKind::Record, + VariantData::Tuple(_) => StructKind::Tuple, + VariantData::Unit => StructKind::Unit, + } + } +} + +impl HasChildSource<LocalFieldId> for VariantId { + type Value = Either<ast::TupleField, ast::RecordField>; + + fn child_source(&self, db: &dyn DefDatabase) -> InFile<ArenaMap<LocalFieldId, Self::Value>> { + let (src, module_id) = match self { + VariantId::EnumVariantId(it) => { + // I don't really like the fact that we call into parent source + // here, this might add to more queries then necessary. + let src = it.parent.child_source(db); + (src.map(|map| map[it.local_id].kind()), it.parent.lookup(db).container) + } + VariantId::StructId(it) => { + (it.lookup(db).source(db).map(|it| it.kind()), it.lookup(db).container) + } + VariantId::UnionId(it) => ( + it.lookup(db).source(db).map(|it| { + it.record_field_list() + .map(ast::StructKind::Record) + .unwrap_or(ast::StructKind::Unit) + }), + it.lookup(db).container, + ), + }; + let mut expander = CfgExpander::new(db, src.file_id, module_id.krate); + let mut trace = Trace::new_for_map(); + lower_struct(db, &mut expander, &mut trace, &src); + src.with_value(trace.into_map()) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum StructKind { + Tuple, + Record, + Unit, +} + +fn lower_struct( + db: &dyn DefDatabase, + expander: &mut CfgExpander, + trace: &mut Trace<FieldData, Either<ast::TupleField, ast::RecordField>>, + ast: &InFile<ast::StructKind>, +) -> StructKind { + let ctx = LowerCtx::new(db, ast.file_id); + + match &ast.value { + ast::StructKind::Tuple(fl) => { + for (i, fd) in fl.fields().enumerate() { + if !expander.is_cfg_enabled(db, &fd) { + continue; + } + + trace.alloc( + || Either::Left(fd.clone()), + || FieldData { + name: Name::new_tuple_field(i), + type_ref: Interned::new(TypeRef::from_ast_opt(&ctx, fd.ty())), + visibility: RawVisibility::from_ast(db, ast.with_value(fd.visibility())), + }, + ); + } + StructKind::Tuple + } + ast::StructKind::Record(fl) => { + for fd in fl.fields() { + if !expander.is_cfg_enabled(db, &fd) { + continue; + } + + trace.alloc( + || Either::Right(fd.clone()), + || FieldData { + name: fd.name().map(|n| n.as_name()).unwrap_or_else(Name::missing), + type_ref: Interned::new(TypeRef::from_ast_opt(&ctx, fd.ty())), + visibility: RawVisibility::from_ast(db, ast.with_value(fd.visibility())), + }, + ); + } + StructKind::Record + } + ast::StructKind::Unit => StructKind::Unit, + } +} + +fn lower_fields( + db: &dyn DefDatabase, + krate: CrateId, + item_tree: &ItemTree, + cfg_options: &CfgOptions, + fields: &Fields, + override_visibility: Option<RawVisibilityId>, +) -> VariantData { + match fields { + Fields::Record(flds) => { + let mut arena = Arena::new(); + for field_id in flds.clone() { + if item_tree.attrs(db, krate, field_id.into()).is_cfg_enabled(cfg_options) { + arena.alloc(lower_field(item_tree, &item_tree[field_id], override_visibility)); + } + } + VariantData::Record(arena) + } + Fields::Tuple(flds) => { + let mut arena = Arena::new(); + for field_id in flds.clone() { + if item_tree.attrs(db, krate, field_id.into()).is_cfg_enabled(cfg_options) { + arena.alloc(lower_field(item_tree, &item_tree[field_id], override_visibility)); + } + } + VariantData::Tuple(arena) + } + Fields::Unit => VariantData::Unit, + } +} + +fn lower_field( + item_tree: &ItemTree, + field: &Field, + override_visibility: Option<RawVisibilityId>, +) -> FieldData { + FieldData { + name: field.name.clone(), + type_ref: field.type_ref.clone(), + visibility: item_tree[override_visibility.unwrap_or(field.visibility)].clone(), + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attr.rs b/src/tools/rust-analyzer/crates/hir-def/src/attr.rs new file mode 100644 index 000000000..2b39c6f8d --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/attr.rs @@ -0,0 +1,1002 @@ +//! A higher level attributes based on TokenTree, with also some shortcuts. + +use std::{fmt, hash::Hash, ops, sync::Arc}; + +use base_db::CrateId; +use cfg::{CfgExpr, CfgOptions}; +use either::Either; +use hir_expand::{hygiene::Hygiene, name::AsName, HirFileId, InFile}; +use itertools::Itertools; +use la_arena::{ArenaMap, Idx, RawIdx}; +use mbe::{syntax_node_to_token_tree, DelimiterKind, Punct}; +use smallvec::{smallvec, SmallVec}; +use syntax::{ + ast::{self, AstNode, HasAttrs, IsString}, + match_ast, AstPtr, AstToken, SmolStr, SyntaxNode, TextRange, TextSize, +}; +use tt::Subtree; + +use crate::{ + db::DefDatabase, + intern::Interned, + item_tree::{AttrOwner, Fields, ItemTreeId, ItemTreeNode}, + nameres::{ModuleOrigin, ModuleSource}, + path::{ModPath, PathKind}, + src::{HasChildSource, HasSource}, + AdtId, AttrDefId, EnumId, GenericParamId, LocalEnumVariantId, LocalFieldId, Lookup, MacroId, + VariantId, +}; + +/// Holds documentation +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Documentation(String); + +impl Documentation { + pub fn new(s: String) -> Self { + Documentation(s) + } + + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl From<Documentation> for String { + fn from(Documentation(string): Documentation) -> Self { + string + } +} + +/// Syntactical attributes, without filtering of `cfg_attr`s. +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub(crate) struct RawAttrs { + entries: Option<Arc<[Attr]>>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct Attrs(RawAttrs); + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AttrsWithOwner { + attrs: Attrs, + owner: AttrDefId, +} + +impl ops::Deref for RawAttrs { + type Target = [Attr]; + + fn deref(&self) -> &[Attr] { + match &self.entries { + Some(it) => &*it, + None => &[], + } + } +} +impl Attrs { + pub fn get(&self, id: AttrId) -> Option<&Attr> { + (**self).iter().find(|attr| attr.id == id) + } +} + +impl ops::Deref for Attrs { + type Target = [Attr]; + + fn deref(&self) -> &[Attr] { + match &self.0.entries { + Some(it) => &*it, + None => &[], + } + } +} + +impl ops::Deref for AttrsWithOwner { + type Target = Attrs; + + fn deref(&self) -> &Attrs { + &self.attrs + } +} + +impl RawAttrs { + pub(crate) const EMPTY: Self = Self { entries: None }; + + pub(crate) fn new(db: &dyn DefDatabase, owner: &dyn ast::HasAttrs, hygiene: &Hygiene) -> Self { + let entries = collect_attrs(owner) + .filter_map(|(id, attr)| match attr { + Either::Left(attr) => { + attr.meta().and_then(|meta| Attr::from_src(db, meta, hygiene, id)) + } + Either::Right(comment) => comment.doc_comment().map(|doc| Attr { + id, + input: Some(Interned::new(AttrInput::Literal(SmolStr::new(doc)))), + path: Interned::new(ModPath::from(hir_expand::name!(doc))), + }), + }) + .collect::<Arc<_>>(); + + Self { entries: if entries.is_empty() { None } else { Some(entries) } } + } + + fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn ast::HasAttrs>) -> Self { + let hygiene = Hygiene::new(db.upcast(), owner.file_id); + Self::new(db, owner.value, &hygiene) + } + + pub(crate) fn merge(&self, other: Self) -> Self { + // FIXME: This needs to fixup `AttrId`s + match (&self.entries, other.entries) { + (None, None) => Self::EMPTY, + (None, entries @ Some(_)) => Self { entries }, + (Some(entries), None) => Self { entries: Some(entries.clone()) }, + (Some(a), Some(b)) => { + let last_ast_index = a.last().map_or(0, |it| it.id.ast_index + 1); + Self { + entries: Some( + a.iter() + .cloned() + .chain(b.iter().map(|it| { + let mut it = it.clone(); + it.id.ast_index += last_ast_index; + it + })) + .collect(), + ), + } + } + } + } + + /// Processes `cfg_attr`s, returning the resulting semantic `Attrs`. + pub(crate) fn filter(self, db: &dyn DefDatabase, krate: CrateId) -> Attrs { + let has_cfg_attrs = self.iter().any(|attr| { + attr.path.as_ident().map_or(false, |name| *name == hir_expand::name![cfg_attr]) + }); + if !has_cfg_attrs { + return Attrs(self); + } + + let crate_graph = db.crate_graph(); + let new_attrs = self + .iter() + .flat_map(|attr| -> SmallVec<[_; 1]> { + let is_cfg_attr = + attr.path.as_ident().map_or(false, |name| *name == hir_expand::name![cfg_attr]); + if !is_cfg_attr { + return smallvec![attr.clone()]; + } + + let subtree = match attr.token_tree_value() { + Some(it) => it, + _ => return smallvec![attr.clone()], + }; + + // Input subtree is: `(cfg, $(attr),+)` + // Split it up into a `cfg` subtree and the `attr` subtrees. + // FIXME: There should be a common API for this. + let mut parts = subtree.token_trees.split(|tt| { + matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Punct(Punct { char: ',', .. }))) + }); + let cfg = match parts.next() { + Some(it) => it, + None => return smallvec![], + }; + let cfg = Subtree { delimiter: subtree.delimiter, token_trees: cfg.to_vec() }; + let cfg = CfgExpr::parse(&cfg); + let index = attr.id; + let attrs = parts.filter(|a| !a.is_empty()).filter_map(|attr| { + let tree = Subtree { delimiter: None, token_trees: attr.to_vec() }; + // FIXME hygiene + let hygiene = Hygiene::new_unhygienic(); + Attr::from_tt(db, &tree, &hygiene, index) + }); + + let cfg_options = &crate_graph[krate].cfg_options; + if cfg_options.check(&cfg) == Some(false) { + smallvec![] + } else { + cov_mark::hit!(cfg_attr_active); + + attrs.collect() + } + }) + .collect(); + + Attrs(RawAttrs { entries: Some(new_attrs) }) + } +} + +impl Attrs { + pub const EMPTY: Self = Self(RawAttrs::EMPTY); + + pub(crate) fn variants_attrs_query( + db: &dyn DefDatabase, + e: EnumId, + ) -> Arc<ArenaMap<LocalEnumVariantId, Attrs>> { + // FIXME: There should be some proper form of mapping between item tree enum variant ids and hir enum variant ids + let mut res = ArenaMap::default(); + + let loc = e.lookup(db); + let krate = loc.container.krate; + let item_tree = loc.id.item_tree(db); + let enum_ = &item_tree[loc.id.value]; + let crate_graph = db.crate_graph(); + let cfg_options = &crate_graph[krate].cfg_options; + + let mut idx = 0; + for variant in enum_.variants.clone() { + let attrs = item_tree.attrs(db, krate, variant.into()); + if attrs.is_cfg_enabled(cfg_options) { + res.insert(Idx::from_raw(RawIdx::from(idx)), attrs); + idx += 1; + } + } + + Arc::new(res) + } + + pub(crate) fn fields_attrs_query( + db: &dyn DefDatabase, + v: VariantId, + ) -> Arc<ArenaMap<LocalFieldId, Attrs>> { + // FIXME: There should be some proper form of mapping between item tree field ids and hir field ids + let mut res = ArenaMap::default(); + + let crate_graph = db.crate_graph(); + let (fields, item_tree, krate) = match v { + VariantId::EnumVariantId(it) => { + let e = it.parent; + let loc = e.lookup(db); + let krate = loc.container.krate; + let item_tree = loc.id.item_tree(db); + let enum_ = &item_tree[loc.id.value]; + + let cfg_options = &crate_graph[krate].cfg_options; + let variant = 'tri: loop { + let mut idx = 0; + for variant in enum_.variants.clone() { + let attrs = item_tree.attrs(db, krate, variant.into()); + if attrs.is_cfg_enabled(cfg_options) { + if it.local_id == Idx::from_raw(RawIdx::from(idx)) { + break 'tri variant; + } + idx += 1; + } + } + return Arc::new(res); + }; + (item_tree[variant].fields.clone(), item_tree, krate) + } + VariantId::StructId(it) => { + let loc = it.lookup(db); + let krate = loc.container.krate; + let item_tree = loc.id.item_tree(db); + let struct_ = &item_tree[loc.id.value]; + (struct_.fields.clone(), item_tree, krate) + } + VariantId::UnionId(it) => { + let loc = it.lookup(db); + let krate = loc.container.krate; + let item_tree = loc.id.item_tree(db); + let union_ = &item_tree[loc.id.value]; + (union_.fields.clone(), item_tree, krate) + } + }; + + let fields = match fields { + Fields::Record(fields) | Fields::Tuple(fields) => fields, + Fields::Unit => return Arc::new(res), + }; + + let cfg_options = &crate_graph[krate].cfg_options; + + let mut idx = 0; + for field in fields { + let attrs = item_tree.attrs(db, krate, field.into()); + if attrs.is_cfg_enabled(cfg_options) { + res.insert(Idx::from_raw(RawIdx::from(idx)), attrs); + idx += 1; + } + } + + Arc::new(res) + } + + pub fn by_key(&self, key: &'static str) -> AttrQuery<'_> { + AttrQuery { attrs: self, key } + } +} + +impl Attrs { + pub fn cfg(&self) -> Option<CfgExpr> { + let mut cfgs = self.by_key("cfg").tt_values().map(CfgExpr::parse); + let first = cfgs.next()?; + match cfgs.next() { + Some(second) => { + let cfgs = [first, second].into_iter().chain(cfgs); + Some(CfgExpr::All(cfgs.collect())) + } + None => Some(first), + } + } + pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool { + match self.cfg() { + None => true, + Some(cfg) => cfg_options.check(&cfg) != Some(false), + } + } + + pub fn lang(&self) -> Option<&SmolStr> { + self.by_key("lang").string_value() + } + + pub fn docs(&self) -> Option<Documentation> { + let docs = self.by_key("doc").attrs().filter_map(|attr| attr.string_value()); + let indent = doc_indent(self); + let mut buf = String::new(); + for doc in docs { + // str::lines doesn't yield anything for the empty string + if !doc.is_empty() { + buf.extend(Itertools::intersperse( + doc.lines().map(|line| { + line.char_indices() + .nth(indent) + .map_or(line, |(offset, _)| &line[offset..]) + .trim_end() + }), + "\n", + )); + } + buf.push('\n'); + } + buf.pop(); + if buf.is_empty() { + None + } else { + Some(Documentation(buf)) + } + } + + pub fn has_doc_hidden(&self) -> bool { + self.by_key("doc").tt_values().any(|tt| { + tt.delimiter_kind() == Some(DelimiterKind::Parenthesis) && + matches!(&*tt.token_trees, [tt::TokenTree::Leaf(tt::Leaf::Ident(ident))] if ident.text == "hidden") + }) + } + + pub fn is_proc_macro(&self) -> bool { + self.by_key("proc_macro").exists() + } + + pub fn is_proc_macro_attribute(&self) -> bool { + self.by_key("proc_macro_attribute").exists() + } + + pub fn is_proc_macro_derive(&self) -> bool { + self.by_key("proc_macro_derive").exists() + } +} + +impl AttrsWithOwner { + pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Self { + // FIXME: this should use `Trace` to avoid duplication in `source_map` below + let raw_attrs = match def { + AttrDefId::ModuleId(module) => { + let def_map = module.def_map(db); + let mod_data = &def_map[module.local_id]; + + match mod_data.origin { + ModuleOrigin::File { definition, declaration_tree_id, .. } => { + let decl_attrs = declaration_tree_id + .item_tree(db) + .raw_attrs(AttrOwner::ModItem(declaration_tree_id.value.into())) + .clone(); + let tree = db.file_item_tree(definition.into()); + let def_attrs = tree.raw_attrs(AttrOwner::TopLevel).clone(); + decl_attrs.merge(def_attrs) + } + ModuleOrigin::CrateRoot { definition } => { + let tree = db.file_item_tree(definition.into()); + tree.raw_attrs(AttrOwner::TopLevel).clone() + } + ModuleOrigin::Inline { definition_tree_id, .. } => definition_tree_id + .item_tree(db) + .raw_attrs(AttrOwner::ModItem(definition_tree_id.value.into())) + .clone(), + ModuleOrigin::BlockExpr { block } => RawAttrs::from_attrs_owner( + db, + InFile::new(block.file_id, block.to_node(db.upcast())) + .as_ref() + .map(|it| it as &dyn ast::HasAttrs), + ), + } + } + AttrDefId::FieldId(it) => { + return Self { attrs: db.fields_attrs(it.parent)[it.local_id].clone(), owner: def }; + } + AttrDefId::EnumVariantId(it) => { + return Self { + attrs: db.variants_attrs(it.parent)[it.local_id].clone(), + owner: def, + }; + } + AttrDefId::AdtId(it) => match it { + AdtId::StructId(it) => attrs_from_item_tree(it.lookup(db).id, db), + AdtId::EnumId(it) => attrs_from_item_tree(it.lookup(db).id, db), + AdtId::UnionId(it) => attrs_from_item_tree(it.lookup(db).id, db), + }, + AttrDefId::TraitId(it) => attrs_from_item_tree(it.lookup(db).id, db), + AttrDefId::MacroId(it) => match it { + MacroId::Macro2Id(it) => attrs_from_item_tree(it.lookup(db).id, db), + MacroId::MacroRulesId(it) => attrs_from_item_tree(it.lookup(db).id, db), + MacroId::ProcMacroId(it) => attrs_from_item_tree(it.lookup(db).id, db), + }, + AttrDefId::ImplId(it) => attrs_from_item_tree(it.lookup(db).id, db), + AttrDefId::ConstId(it) => attrs_from_item_tree(it.lookup(db).id, db), + AttrDefId::StaticId(it) => attrs_from_item_tree(it.lookup(db).id, db), + AttrDefId::FunctionId(it) => attrs_from_item_tree(it.lookup(db).id, db), + AttrDefId::TypeAliasId(it) => attrs_from_item_tree(it.lookup(db).id, db), + AttrDefId::GenericParamId(it) => match it { + GenericParamId::ConstParamId(it) => { + let src = it.parent().child_source(db); + RawAttrs::from_attrs_owner( + db, + src.with_value(src.value[it.local_id()].as_ref().either( + |it| match it { + ast::TypeOrConstParam::Type(it) => it as _, + ast::TypeOrConstParam::Const(it) => it as _, + }, + |it| it as _, + )), + ) + } + GenericParamId::TypeParamId(it) => { + let src = it.parent().child_source(db); + RawAttrs::from_attrs_owner( + db, + src.with_value(src.value[it.local_id()].as_ref().either( + |it| match it { + ast::TypeOrConstParam::Type(it) => it as _, + ast::TypeOrConstParam::Const(it) => it as _, + }, + |it| it as _, + )), + ) + } + GenericParamId::LifetimeParamId(it) => { + let src = it.parent.child_source(db); + RawAttrs::from_attrs_owner(db, src.with_value(&src.value[it.local_id])) + } + }, + AttrDefId::ExternBlockId(it) => attrs_from_item_tree(it.lookup(db).id, db), + }; + + let attrs = raw_attrs.filter(db, def.krate(db)); + Self { attrs, owner: def } + } + + pub fn source_map(&self, db: &dyn DefDatabase) -> AttrSourceMap { + let owner = match self.owner { + AttrDefId::ModuleId(module) => { + // Modules can have 2 attribute owners (the `mod x;` item, and the module file itself). + + let def_map = module.def_map(db); + let mod_data = &def_map[module.local_id]; + match mod_data.declaration_source(db) { + Some(it) => { + let mut map = AttrSourceMap::new(InFile::new(it.file_id, &it.value)); + if let InFile { file_id, value: ModuleSource::SourceFile(file) } = + mod_data.definition_source(db) + { + map.append_module_inline_attrs(AttrSourceMap::new(InFile::new( + file_id, &file, + ))); + } + return map; + } + None => { + let InFile { file_id, value } = mod_data.definition_source(db); + let attrs_owner = match &value { + ModuleSource::SourceFile(file) => file as &dyn ast::HasAttrs, + ModuleSource::Module(module) => module as &dyn ast::HasAttrs, + ModuleSource::BlockExpr(block) => block as &dyn ast::HasAttrs, + }; + return AttrSourceMap::new(InFile::new(file_id, attrs_owner)); + } + } + } + AttrDefId::FieldId(id) => { + let map = db.fields_attrs_source_map(id.parent); + let file_id = id.parent.file_id(db); + let root = db.parse_or_expand(file_id).unwrap(); + let owner = match &map[id.local_id] { + Either::Left(it) => ast::AnyHasAttrs::new(it.to_node(&root)), + Either::Right(it) => ast::AnyHasAttrs::new(it.to_node(&root)), + }; + InFile::new(file_id, owner) + } + AttrDefId::AdtId(adt) => match adt { + AdtId::StructId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + AdtId::UnionId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + AdtId::EnumId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + }, + AttrDefId::FunctionId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + AttrDefId::EnumVariantId(id) => { + let map = db.variants_attrs_source_map(id.parent); + let file_id = id.parent.lookup(db).id.file_id(); + let root = db.parse_or_expand(file_id).unwrap(); + InFile::new(file_id, ast::AnyHasAttrs::new(map[id.local_id].to_node(&root))) + } + AttrDefId::StaticId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + AttrDefId::ConstId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + AttrDefId::TraitId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + AttrDefId::TypeAliasId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + AttrDefId::MacroId(id) => match id { + MacroId::Macro2Id(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + MacroId::MacroRulesId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + MacroId::ProcMacroId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + }, + AttrDefId::ImplId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + AttrDefId::GenericParamId(id) => match id { + GenericParamId::ConstParamId(id) => { + id.parent().child_source(db).map(|source| match &source[id.local_id()] { + Either::Left(ast::TypeOrConstParam::Type(id)) => { + ast::AnyHasAttrs::new(id.clone()) + } + Either::Left(ast::TypeOrConstParam::Const(id)) => { + ast::AnyHasAttrs::new(id.clone()) + } + Either::Right(id) => ast::AnyHasAttrs::new(id.clone()), + }) + } + GenericParamId::TypeParamId(id) => { + id.parent().child_source(db).map(|source| match &source[id.local_id()] { + Either::Left(ast::TypeOrConstParam::Type(id)) => { + ast::AnyHasAttrs::new(id.clone()) + } + Either::Left(ast::TypeOrConstParam::Const(id)) => { + ast::AnyHasAttrs::new(id.clone()) + } + Either::Right(id) => ast::AnyHasAttrs::new(id.clone()), + }) + } + GenericParamId::LifetimeParamId(id) => id + .parent + .child_source(db) + .map(|source| ast::AnyHasAttrs::new(source[id.local_id].clone())), + }, + AttrDefId::ExternBlockId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + }; + + AttrSourceMap::new(owner.as_ref().map(|node| node as &dyn HasAttrs)) + } + + pub fn docs_with_rangemap( + &self, + db: &dyn DefDatabase, + ) -> Option<(Documentation, DocsRangeMap)> { + let docs = + self.by_key("doc").attrs().filter_map(|attr| attr.string_value().map(|s| (s, attr.id))); + let indent = doc_indent(self); + let mut buf = String::new(); + let mut mapping = Vec::new(); + for (doc, idx) in docs { + if !doc.is_empty() { + let mut base_offset = 0; + for raw_line in doc.split('\n') { + let line = raw_line.trim_end(); + let line_len = line.len(); + let (offset, line) = match line.char_indices().nth(indent) { + Some((offset, _)) => (offset, &line[offset..]), + None => (0, line), + }; + let buf_offset = buf.len(); + buf.push_str(line); + mapping.push(( + TextRange::new(buf_offset.try_into().ok()?, buf.len().try_into().ok()?), + idx, + TextRange::at( + (base_offset + offset).try_into().ok()?, + line_len.try_into().ok()?, + ), + )); + buf.push('\n'); + base_offset += raw_line.len() + 1; + } + } else { + buf.push('\n'); + } + } + buf.pop(); + if buf.is_empty() { + None + } else { + Some((Documentation(buf), DocsRangeMap { mapping, source_map: self.source_map(db) })) + } + } +} + +fn doc_indent(attrs: &Attrs) -> usize { + attrs + .by_key("doc") + .attrs() + .filter_map(|attr| attr.string_value()) + .flat_map(|s| s.lines()) + .filter(|line| !line.chars().all(|c| c.is_whitespace())) + .map(|line| line.chars().take_while(|c| c.is_whitespace()).count()) + .min() + .unwrap_or(0) +} + +fn inner_attributes( + syntax: &SyntaxNode, +) -> Option<impl Iterator<Item = Either<ast::Attr, ast::Comment>>> { + let node = match_ast! { + match syntax { + ast::SourceFile(_) => syntax.clone(), + ast::ExternBlock(it) => it.extern_item_list()?.syntax().clone(), + ast::Fn(it) => it.body()?.stmt_list()?.syntax().clone(), + ast::Impl(it) => it.assoc_item_list()?.syntax().clone(), + ast::Module(it) => it.item_list()?.syntax().clone(), + ast::BlockExpr(it) => { + use syntax::SyntaxKind::{BLOCK_EXPR , EXPR_STMT}; + // Block expressions accept outer and inner attributes, but only when they are the outer + // expression of an expression statement or the final expression of another block expression. + let may_carry_attributes = matches!( + it.syntax().parent().map(|it| it.kind()), + Some(BLOCK_EXPR | EXPR_STMT) + ); + if !may_carry_attributes { + return None + } + syntax.clone() + }, + _ => return None, + } + }; + + let attrs = ast::AttrDocCommentIter::from_syntax_node(&node).filter(|el| match el { + Either::Left(attr) => attr.kind().is_inner(), + Either::Right(comment) => comment.is_inner(), + }); + Some(attrs) +} + +#[derive(Debug)] +pub struct AttrSourceMap { + source: Vec<Either<ast::Attr, ast::Comment>>, + file_id: HirFileId, + /// If this map is for a module, this will be the [`HirFileId`] of the module's definition site, + /// while `file_id` will be the one of the module declaration site. + /// The usize is the index into `source` from which point on the entries reside in the def site + /// file. + mod_def_site_file_id: Option<(HirFileId, usize)>, +} + +impl AttrSourceMap { + fn new(owner: InFile<&dyn ast::HasAttrs>) -> Self { + Self { + source: collect_attrs(owner.value).map(|(_, it)| it).collect(), + file_id: owner.file_id, + mod_def_site_file_id: None, + } + } + + /// Append a second source map to this one, this is required for modules, whose outline and inline + /// attributes can reside in different files + fn append_module_inline_attrs(&mut self, other: Self) { + assert!(self.mod_def_site_file_id.is_none() && other.mod_def_site_file_id.is_none()); + let len = self.source.len(); + self.source.extend(other.source); + if other.file_id != self.file_id { + self.mod_def_site_file_id = Some((other.file_id, len)); + } + } + + /// Maps the lowered `Attr` back to its original syntax node. + /// + /// `attr` must come from the `owner` used for AttrSourceMap + /// + /// Note that the returned syntax node might be a `#[cfg_attr]`, or a doc comment, instead of + /// the attribute represented by `Attr`. + pub fn source_of(&self, attr: &Attr) -> InFile<&Either<ast::Attr, ast::Comment>> { + self.source_of_id(attr.id) + } + + fn source_of_id(&self, id: AttrId) -> InFile<&Either<ast::Attr, ast::Comment>> { + let ast_idx = id.ast_index as usize; + let file_id = match self.mod_def_site_file_id { + Some((file_id, def_site_cut)) if def_site_cut <= ast_idx => file_id, + _ => self.file_id, + }; + + self.source + .get(ast_idx) + .map(|it| InFile::new(file_id, it)) + .unwrap_or_else(|| panic!("cannot find attr at index {:?}", id)) + } +} + +/// A struct to map text ranges from [`Documentation`] back to TextRanges in the syntax tree. +#[derive(Debug)] +pub struct DocsRangeMap { + source_map: AttrSourceMap, + // (docstring-line-range, attr_index, attr-string-range) + // a mapping from the text range of a line of the [`Documentation`] to the attribute index and + // the original (untrimmed) syntax doc line + mapping: Vec<(TextRange, AttrId, TextRange)>, +} + +impl DocsRangeMap { + /// Maps a [`TextRange`] relative to the documentation string back to its AST range + pub fn map(&self, range: TextRange) -> Option<InFile<TextRange>> { + let found = self.mapping.binary_search_by(|(probe, ..)| probe.ordering(range)).ok()?; + let (line_docs_range, idx, original_line_src_range) = self.mapping[found]; + if !line_docs_range.contains_range(range) { + return None; + } + + let relative_range = range - line_docs_range.start(); + + let InFile { file_id, value: source } = self.source_map.source_of_id(idx); + match source { + Either::Left(attr) => { + let string = get_doc_string_in_attr(attr)?; + let text_range = string.open_quote_text_range()?; + let range = TextRange::at( + text_range.end() + original_line_src_range.start() + relative_range.start(), + string.syntax().text_range().len().min(range.len()), + ); + Some(InFile { file_id, value: range }) + } + Either::Right(comment) => { + let text_range = comment.syntax().text_range(); + let range = TextRange::at( + text_range.start() + + TextSize::try_from(comment.prefix().len()).ok()? + + original_line_src_range.start() + + relative_range.start(), + text_range.len().min(range.len()), + ); + Some(InFile { file_id, value: range }) + } + } + } +} + +fn get_doc_string_in_attr(it: &ast::Attr) -> Option<ast::String> { + match it.expr() { + // #[doc = lit] + Some(ast::Expr::Literal(lit)) => match lit.kind() { + ast::LiteralKind::String(it) => Some(it), + _ => None, + }, + // #[cfg_attr(..., doc = "", ...)] + None => { + // FIXME: See highlight injection for what to do here + None + } + _ => None, + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct AttrId { + pub(crate) ast_index: u32, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Attr { + pub(crate) id: AttrId, + pub(crate) path: Interned<ModPath>, + pub(crate) input: Option<Interned<AttrInput>>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum AttrInput { + /// `#[attr = "string"]` + Literal(SmolStr), + /// `#[attr(subtree)]` + TokenTree(tt::Subtree, mbe::TokenMap), +} + +impl fmt::Display for AttrInput { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AttrInput::Literal(lit) => write!(f, " = \"{}\"", lit.escape_debug()), + AttrInput::TokenTree(subtree, _) => subtree.fmt(f), + } + } +} + +impl Attr { + fn from_src( + db: &dyn DefDatabase, + ast: ast::Meta, + hygiene: &Hygiene, + id: AttrId, + ) -> Option<Attr> { + let path = Interned::new(ModPath::from_src(db.upcast(), ast.path()?, hygiene)?); + let input = if let Some(ast::Expr::Literal(lit)) = ast.expr() { + let value = match lit.kind() { + ast::LiteralKind::String(string) => string.value()?.into(), + _ => lit.syntax().first_token()?.text().trim_matches('"').into(), + }; + Some(Interned::new(AttrInput::Literal(value))) + } else if let Some(tt) = ast.token_tree() { + let (tree, map) = syntax_node_to_token_tree(tt.syntax()); + Some(Interned::new(AttrInput::TokenTree(tree, map))) + } else { + None + }; + Some(Attr { id, path, input }) + } + + fn from_tt( + db: &dyn DefDatabase, + tt: &tt::Subtree, + hygiene: &Hygiene, + id: AttrId, + ) -> Option<Attr> { + let (parse, _) = mbe::token_tree_to_syntax_node(tt, mbe::TopEntryPoint::MetaItem); + let ast = ast::Meta::cast(parse.syntax_node())?; + + Self::from_src(db, ast, hygiene, id) + } + + pub fn path(&self) -> &ModPath { + &self.path + } +} + +impl Attr { + /// #[path = "string"] + pub fn string_value(&self) -> Option<&SmolStr> { + match self.input.as_deref()? { + AttrInput::Literal(it) => Some(it), + _ => None, + } + } + + /// #[path(ident)] + pub fn single_ident_value(&self) -> Option<&tt::Ident> { + match self.input.as_deref()? { + AttrInput::TokenTree(subtree, _) => match &*subtree.token_trees { + [tt::TokenTree::Leaf(tt::Leaf::Ident(ident))] => Some(ident), + _ => None, + }, + _ => None, + } + } + + /// #[path TokenTree] + pub fn token_tree_value(&self) -> Option<&Subtree> { + match self.input.as_deref()? { + AttrInput::TokenTree(subtree, _) => Some(subtree), + _ => None, + } + } + + /// Parses this attribute as a token tree consisting of comma separated paths. + pub fn parse_path_comma_token_tree(&self) -> Option<impl Iterator<Item = ModPath> + '_> { + let args = self.token_tree_value()?; + + if args.delimiter_kind() != Some(DelimiterKind::Parenthesis) { + return None; + } + let paths = args + .token_trees + .split(|tt| matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Punct(Punct { char: ',', .. })))) + .filter_map(|tts| { + if tts.is_empty() { + return None; + } + let segments = tts.iter().filter_map(|tt| match tt { + tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => Some(id.as_name()), + _ => None, + }); + Some(ModPath::from_segments(PathKind::Plain, segments)) + }); + + Some(paths) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct AttrQuery<'attr> { + attrs: &'attr Attrs, + key: &'static str, +} + +impl<'attr> AttrQuery<'attr> { + pub fn tt_values(self) -> impl Iterator<Item = &'attr Subtree> { + self.attrs().filter_map(|attr| attr.token_tree_value()) + } + + pub fn string_value(self) -> Option<&'attr SmolStr> { + self.attrs().find_map(|attr| attr.string_value()) + } + + pub fn exists(self) -> bool { + self.attrs().next().is_some() + } + + pub fn attrs(self) -> impl Iterator<Item = &'attr Attr> + Clone { + let key = self.key; + self.attrs + .iter() + .filter(move |attr| attr.path.as_ident().map_or(false, |s| s.to_smol_str() == key)) + } + + /// Find string value for a specific key inside token tree + /// + /// ```ignore + /// #[doc(html_root_url = "url")] + /// ^^^^^^^^^^^^^ key + /// ``` + pub fn find_string_value_in_tt(self, key: &'attr str) -> Option<&SmolStr> { + self.tt_values().find_map(|tt| { + let name = tt.token_trees.iter() + .skip_while(|tt| !matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { text, ..} )) if text == key)) + .nth(2); + + match name { + Some(tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal{ref text, ..}))) => Some(text), + _ => None + } + }) + } +} + +fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase) -> RawAttrs { + let tree = id.item_tree(db); + let mod_item = N::id_to_mod_item(id.value); + tree.raw_attrs(mod_item.into()).clone() +} + +fn collect_attrs( + owner: &dyn ast::HasAttrs, +) -> impl Iterator<Item = (AttrId, Either<ast::Attr, ast::Comment>)> { + let inner_attrs = inner_attributes(owner.syntax()).into_iter().flatten(); + let outer_attrs = + ast::AttrDocCommentIter::from_syntax_node(owner.syntax()).filter(|el| match el { + Either::Left(attr) => attr.kind().is_outer(), + Either::Right(comment) => comment.is_outer(), + }); + outer_attrs + .chain(inner_attrs) + .enumerate() + .map(|(id, attr)| (AttrId { ast_index: id as u32 }, attr)) +} + +pub(crate) fn variants_attrs_source_map( + db: &dyn DefDatabase, + def: EnumId, +) -> Arc<ArenaMap<LocalEnumVariantId, AstPtr<ast::Variant>>> { + let mut res = ArenaMap::default(); + let child_source = def.child_source(db); + + for (idx, variant) in child_source.value.iter() { + res.insert(idx, AstPtr::new(variant)); + } + + Arc::new(res) +} + +pub(crate) fn fields_attrs_source_map( + db: &dyn DefDatabase, + def: VariantId, +) -> Arc<ArenaMap<LocalFieldId, Either<AstPtr<ast::TupleField>, AstPtr<ast::RecordField>>>> { + let mut res = ArenaMap::default(); + let child_source = def.child_source(db); + + for (idx, variant) in child_source.value.iter() { + res.insert( + idx, + variant + .as_ref() + .either(|l| Either::Left(AstPtr::new(l)), |r| Either::Right(AstPtr::new(r))), + ); + } + + Arc::new(res) +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body.rs b/src/tools/rust-analyzer/crates/hir-def/src/body.rs new file mode 100644 index 000000000..080a307b1 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/body.rs @@ -0,0 +1,471 @@ +//! Defines `Body`: a lowered representation of bodies of functions, statics and +//! consts. +mod lower; +#[cfg(test)] +mod tests; +pub mod scope; + +use std::{ops::Index, sync::Arc}; + +use base_db::CrateId; +use cfg::{CfgExpr, CfgOptions}; +use drop_bomb::DropBomb; +use either::Either; +use hir_expand::{hygiene::Hygiene, ExpandError, ExpandResult, HirFileId, InFile, MacroCallId}; +use la_arena::{Arena, ArenaMap}; +use limit::Limit; +use profile::Count; +use rustc_hash::FxHashMap; +use syntax::{ast, AstPtr, SyntaxNodePtr}; + +use crate::{ + attr::{Attrs, RawAttrs}, + db::DefDatabase, + expr::{dummy_expr_id, Expr, ExprId, Label, LabelId, Pat, PatId}, + item_scope::BuiltinShadowMode, + macro_id_to_def_id, + nameres::DefMap, + path::{ModPath, Path}, + src::HasSource, + AsMacroCall, BlockId, DefWithBodyId, HasModule, LocalModuleId, Lookup, MacroId, ModuleId, + UnresolvedMacro, +}; + +pub use lower::LowerCtx; + +/// A subset of Expander that only deals with cfg attributes. We only need it to +/// avoid cyclic queries in crate def map during enum processing. +#[derive(Debug)] +pub(crate) struct CfgExpander { + cfg_options: CfgOptions, + hygiene: Hygiene, + krate: CrateId, +} + +#[derive(Debug)] +pub struct Expander { + cfg_expander: CfgExpander, + def_map: Arc<DefMap>, + current_file_id: HirFileId, + module: LocalModuleId, + recursion_limit: usize, +} + +impl CfgExpander { + pub(crate) fn new( + db: &dyn DefDatabase, + current_file_id: HirFileId, + krate: CrateId, + ) -> CfgExpander { + let hygiene = Hygiene::new(db.upcast(), current_file_id); + let cfg_options = db.crate_graph()[krate].cfg_options.clone(); + CfgExpander { cfg_options, hygiene, krate } + } + + pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs { + RawAttrs::new(db, owner, &self.hygiene).filter(db, self.krate) + } + + pub(crate) fn is_cfg_enabled(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> bool { + let attrs = self.parse_attrs(db, owner); + attrs.is_cfg_enabled(&self.cfg_options) + } +} + +impl Expander { + pub fn new(db: &dyn DefDatabase, current_file_id: HirFileId, module: ModuleId) -> Expander { + let cfg_expander = CfgExpander::new(db, current_file_id, module.krate); + let def_map = module.def_map(db); + Expander { + cfg_expander, + def_map, + current_file_id, + module: module.local_id, + recursion_limit: 0, + } + } + + pub fn enter_expand<T: ast::AstNode>( + &mut self, + db: &dyn DefDatabase, + macro_call: ast::MacroCall, + ) -> Result<ExpandResult<Option<(Mark, T)>>, UnresolvedMacro> { + if self.recursion_limit(db).check(self.recursion_limit + 1).is_err() { + cov_mark::hit!(your_stack_belongs_to_me); + return Ok(ExpandResult::only_err(ExpandError::Other( + "reached recursion limit during macro expansion".into(), + ))); + } + + let macro_call = InFile::new(self.current_file_id, ¯o_call); + + let resolver = + |path| self.resolve_path_as_macro(db, &path).map(|it| macro_id_to_def_id(db, it)); + + let mut err = None; + let call_id = + macro_call.as_call_id_with_errors(db, self.def_map.krate(), resolver, &mut |e| { + err.get_or_insert(e); + })?; + let call_id = match call_id { + Ok(it) => it, + Err(_) => { + return Ok(ExpandResult { value: None, err }); + } + }; + + Ok(self.enter_expand_inner(db, call_id, err)) + } + + pub fn enter_expand_id<T: ast::AstNode>( + &mut self, + db: &dyn DefDatabase, + call_id: MacroCallId, + ) -> ExpandResult<Option<(Mark, T)>> { + self.enter_expand_inner(db, call_id, None) + } + + fn enter_expand_inner<T: ast::AstNode>( + &mut self, + db: &dyn DefDatabase, + call_id: MacroCallId, + mut err: Option<ExpandError>, + ) -> ExpandResult<Option<(Mark, T)>> { + if err.is_none() { + err = db.macro_expand_error(call_id); + } + + let file_id = call_id.as_file(); + + let raw_node = match db.parse_or_expand(file_id) { + Some(it) => it, + None => { + // Only `None` if the macro expansion produced no usable AST. + if err.is_none() { + tracing::warn!("no error despite `parse_or_expand` failing"); + } + + return ExpandResult::only_err(err.unwrap_or_else(|| { + ExpandError::Other("failed to parse macro invocation".into()) + })); + } + }; + + let node = match T::cast(raw_node) { + Some(it) => it, + None => { + // This can happen without being an error, so only forward previous errors. + return ExpandResult { value: None, err }; + } + }; + + tracing::debug!("macro expansion {:#?}", node.syntax()); + + self.recursion_limit += 1; + let mark = + Mark { file_id: self.current_file_id, bomb: DropBomb::new("expansion mark dropped") }; + self.cfg_expander.hygiene = Hygiene::new(db.upcast(), file_id); + self.current_file_id = file_id; + + ExpandResult { value: Some((mark, node)), err } + } + + pub fn exit(&mut self, db: &dyn DefDatabase, mut mark: Mark) { + self.cfg_expander.hygiene = Hygiene::new(db.upcast(), mark.file_id); + self.current_file_id = mark.file_id; + self.recursion_limit -= 1; + mark.bomb.defuse(); + } + + pub(crate) fn to_source<T>(&self, value: T) -> InFile<T> { + InFile { file_id: self.current_file_id, value } + } + + pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs { + self.cfg_expander.parse_attrs(db, owner) + } + + pub(crate) fn cfg_options(&self) -> &CfgOptions { + &self.cfg_expander.cfg_options + } + + pub fn current_file_id(&self) -> HirFileId { + self.current_file_id + } + + fn parse_path(&mut self, db: &dyn DefDatabase, path: ast::Path) -> Option<Path> { + let ctx = LowerCtx::with_hygiene(db, &self.cfg_expander.hygiene); + Path::from_src(path, &ctx) + } + + fn resolve_path_as_macro(&self, db: &dyn DefDatabase, path: &ModPath) -> Option<MacroId> { + self.def_map.resolve_path(db, self.module, path, BuiltinShadowMode::Other).0.take_macros() + } + + fn recursion_limit(&self, db: &dyn DefDatabase) -> Limit { + let limit = db.crate_limits(self.cfg_expander.krate).recursion_limit as _; + + #[cfg(not(test))] + return Limit::new(limit); + + // Without this, `body::tests::your_stack_belongs_to_me` stack-overflows in debug + #[cfg(test)] + return Limit::new(std::cmp::min(32, limit)); + } +} + +#[derive(Debug)] +pub struct Mark { + file_id: HirFileId, + bomb: DropBomb, +} + +/// The body of an item (function, const etc.). +#[derive(Debug, Eq, PartialEq)] +pub struct Body { + pub exprs: Arena<Expr>, + pub pats: Arena<Pat>, + pub or_pats: FxHashMap<PatId, Arc<[PatId]>>, + pub labels: Arena<Label>, + /// The patterns for the function's parameters. While the parameter types are + /// part of the function signature, the patterns are not (they don't change + /// the external type of the function). + /// + /// If this `Body` is for the body of a constant, this will just be + /// empty. + pub params: Vec<PatId>, + /// The `ExprId` of the actual body expression. + pub body_expr: ExprId, + /// Block expressions in this body that may contain inner items. + block_scopes: Vec<BlockId>, + _c: Count<Self>, +} + +pub type ExprPtr = AstPtr<ast::Expr>; +pub type ExprSource = InFile<ExprPtr>; + +pub type PatPtr = Either<AstPtr<ast::Pat>, AstPtr<ast::SelfParam>>; +pub type PatSource = InFile<PatPtr>; + +pub type LabelPtr = AstPtr<ast::Label>; +pub type LabelSource = InFile<LabelPtr>; +/// An item body together with the mapping from syntax nodes to HIR expression +/// IDs. This is needed to go from e.g. a position in a file to the HIR +/// expression containing it; but for type inference etc., we want to operate on +/// a structure that is agnostic to the actual positions of expressions in the +/// file, so that we don't recompute types whenever some whitespace is typed. +/// +/// One complication here is that, due to macro expansion, a single `Body` might +/// be spread across several files. So, for each ExprId and PatId, we record +/// both the HirFileId and the position inside the file. However, we only store +/// AST -> ExprId mapping for non-macro files, as it is not clear how to handle +/// this properly for macros. +#[derive(Default, Debug, Eq, PartialEq)] +pub struct BodySourceMap { + expr_map: FxHashMap<ExprSource, ExprId>, + expr_map_back: ArenaMap<ExprId, Result<ExprSource, SyntheticSyntax>>, + + pat_map: FxHashMap<PatSource, PatId>, + pat_map_back: ArenaMap<PatId, Result<PatSource, SyntheticSyntax>>, + + label_map: FxHashMap<LabelSource, LabelId>, + label_map_back: ArenaMap<LabelId, LabelSource>, + + /// We don't create explicit nodes for record fields (`S { record_field: 92 }`). + /// Instead, we use id of expression (`92`) to identify the field. + field_map: FxHashMap<InFile<AstPtr<ast::RecordExprField>>, ExprId>, + field_map_back: FxHashMap<ExprId, InFile<AstPtr<ast::RecordExprField>>>, + + expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>, + + /// Diagnostics accumulated during body lowering. These contain `AstPtr`s and so are stored in + /// the source map (since they're just as volatile). + diagnostics: Vec<BodyDiagnostic>, +} + +#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)] +pub struct SyntheticSyntax; + +#[derive(Debug, Eq, PartialEq)] +pub enum BodyDiagnostic { + InactiveCode { node: InFile<SyntaxNodePtr>, cfg: CfgExpr, opts: CfgOptions }, + MacroError { node: InFile<AstPtr<ast::MacroCall>>, message: String }, + UnresolvedProcMacro { node: InFile<AstPtr<ast::MacroCall>>, krate: CrateId }, + UnresolvedMacroCall { node: InFile<AstPtr<ast::MacroCall>>, path: ModPath }, +} + +impl Body { + pub(crate) fn body_with_source_map_query( + db: &dyn DefDatabase, + def: DefWithBodyId, + ) -> (Arc<Body>, Arc<BodySourceMap>) { + let _p = profile::span("body_with_source_map_query"); + let mut params = None; + + let (file_id, module, body) = match def { + DefWithBodyId::FunctionId(f) => { + let f = f.lookup(db); + let src = f.source(db); + params = src.value.param_list(); + (src.file_id, f.module(db), src.value.body().map(ast::Expr::from)) + } + DefWithBodyId::ConstId(c) => { + let c = c.lookup(db); + let src = c.source(db); + (src.file_id, c.module(db), src.value.body()) + } + DefWithBodyId::StaticId(s) => { + let s = s.lookup(db); + let src = s.source(db); + (src.file_id, s.module(db), src.value.body()) + } + }; + let expander = Expander::new(db, file_id, module); + let (mut body, source_map) = Body::new(db, expander, params, body); + body.shrink_to_fit(); + (Arc::new(body), Arc::new(source_map)) + } + + pub(crate) fn body_query(db: &dyn DefDatabase, def: DefWithBodyId) -> Arc<Body> { + db.body_with_source_map(def).0 + } + + /// Returns an iterator over all block expressions in this body that define inner items. + pub fn blocks<'a>( + &'a self, + db: &'a dyn DefDatabase, + ) -> impl Iterator<Item = (BlockId, Arc<DefMap>)> + '_ { + self.block_scopes + .iter() + .map(move |&block| (block, db.block_def_map(block).expect("block ID without DefMap"))) + } + + pub fn pattern_representative(&self, pat: PatId) -> PatId { + self.or_pats.get(&pat).and_then(|pats| pats.first().copied()).unwrap_or(pat) + } + + /// Retrieves all ident patterns this pattern shares the ident with. + pub fn ident_patterns_for<'slf>(&'slf self, pat: &'slf PatId) -> &'slf [PatId] { + match self.or_pats.get(pat) { + Some(pats) => &**pats, + None => std::slice::from_ref(pat), + } + } + + fn new( + db: &dyn DefDatabase, + expander: Expander, + params: Option<ast::ParamList>, + body: Option<ast::Expr>, + ) -> (Body, BodySourceMap) { + lower::lower(db, expander, params, body) + } + + fn shrink_to_fit(&mut self) { + let Self { _c: _, body_expr: _, block_scopes, or_pats, exprs, labels, params, pats } = self; + block_scopes.shrink_to_fit(); + or_pats.shrink_to_fit(); + exprs.shrink_to_fit(); + labels.shrink_to_fit(); + params.shrink_to_fit(); + pats.shrink_to_fit(); + } +} + +impl Default for Body { + fn default() -> Self { + Self { + body_expr: dummy_expr_id(), + exprs: Default::default(), + pats: Default::default(), + or_pats: Default::default(), + labels: Default::default(), + params: Default::default(), + block_scopes: Default::default(), + _c: Default::default(), + } + } +} + +impl Index<ExprId> for Body { + type Output = Expr; + + fn index(&self, expr: ExprId) -> &Expr { + &self.exprs[expr] + } +} + +impl Index<PatId> for Body { + type Output = Pat; + + fn index(&self, pat: PatId) -> &Pat { + &self.pats[pat] + } +} + +impl Index<LabelId> for Body { + type Output = Label; + + fn index(&self, label: LabelId) -> &Label { + &self.labels[label] + } +} + +// FIXME: Change `node_` prefix to something more reasonable. +// Perhaps `expr_syntax` and `expr_id`? +impl BodySourceMap { + pub fn expr_syntax(&self, expr: ExprId) -> Result<ExprSource, SyntheticSyntax> { + self.expr_map_back[expr].clone() + } + + pub fn node_expr(&self, node: InFile<&ast::Expr>) -> Option<ExprId> { + let src = node.map(AstPtr::new); + self.expr_map.get(&src).cloned() + } + + pub fn node_macro_file(&self, node: InFile<&ast::MacroCall>) -> Option<HirFileId> { + let src = node.map(AstPtr::new); + self.expansions.get(&src).cloned() + } + + pub fn pat_syntax(&self, pat: PatId) -> Result<PatSource, SyntheticSyntax> { + self.pat_map_back[pat].clone() + } + + pub fn node_pat(&self, node: InFile<&ast::Pat>) -> Option<PatId> { + let src = node.map(|it| Either::Left(AstPtr::new(it))); + self.pat_map.get(&src).cloned() + } + + pub fn node_self_param(&self, node: InFile<&ast::SelfParam>) -> Option<PatId> { + let src = node.map(|it| Either::Right(AstPtr::new(it))); + self.pat_map.get(&src).cloned() + } + + pub fn label_syntax(&self, label: LabelId) -> LabelSource { + self.label_map_back[label].clone() + } + + pub fn node_label(&self, node: InFile<&ast::Label>) -> Option<LabelId> { + let src = node.map(AstPtr::new); + self.label_map.get(&src).cloned() + } + + pub fn field_syntax(&self, expr: ExprId) -> InFile<AstPtr<ast::RecordExprField>> { + self.field_map_back[&expr].clone() + } + pub fn node_field(&self, node: InFile<&ast::RecordExprField>) -> Option<ExprId> { + let src = node.map(AstPtr::new); + self.field_map.get(&src).cloned() + } + + pub fn macro_expansion_expr(&self, node: InFile<&ast::MacroExpr>) -> Option<ExprId> { + let src = node.map(AstPtr::new).map(AstPtr::upcast::<ast::MacroExpr>).map(AstPtr::upcast); + self.expr_map.get(&src).copied() + } + + /// Get a reference to the body source map's diagnostics. + pub fn diagnostics(&self) -> &[BodyDiagnostic] { + &self.diagnostics + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs new file mode 100644 index 000000000..66f9c24e8 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs @@ -0,0 +1,1023 @@ +//! Transforms `ast::Expr` into an equivalent `hir_def::expr::Expr` +//! representation. + +use std::{mem, sync::Arc}; + +use either::Either; +use hir_expand::{ + ast_id_map::AstIdMap, + hygiene::Hygiene, + name::{name, AsName, Name}, + AstId, ExpandError, HirFileId, InFile, +}; +use la_arena::Arena; +use once_cell::unsync::OnceCell; +use profile::Count; +use rustc_hash::FxHashMap; +use syntax::{ + ast::{ + self, ArrayExprKind, AstChildren, HasArgList, HasLoopBody, HasName, LiteralKind, + SlicePatComponents, + }, + AstNode, AstPtr, SyntaxNodePtr, +}; + +use crate::{ + adt::StructKind, + body::{Body, BodySourceMap, Expander, LabelSource, PatPtr, SyntheticSyntax}, + body::{BodyDiagnostic, ExprSource, PatSource}, + builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint}, + db::DefDatabase, + expr::{ + dummy_expr_id, Array, BindingAnnotation, Expr, ExprId, FloatTypeWrapper, Label, LabelId, + Literal, MatchArm, Pat, PatId, RecordFieldPat, RecordLitField, Statement, + }, + intern::Interned, + item_scope::BuiltinShadowMode, + path::{GenericArgs, Path}, + type_ref::{Mutability, Rawness, TypeRef}, + AdtId, BlockLoc, ModuleDefId, UnresolvedMacro, +}; + +pub struct LowerCtx<'a> { + pub db: &'a dyn DefDatabase, + hygiene: Hygiene, + ast_id_map: Option<(HirFileId, OnceCell<Arc<AstIdMap>>)>, +} + +impl<'a> LowerCtx<'a> { + pub fn new(db: &'a dyn DefDatabase, file_id: HirFileId) -> Self { + LowerCtx { + db, + hygiene: Hygiene::new(db.upcast(), file_id), + ast_id_map: Some((file_id, OnceCell::new())), + } + } + + pub fn with_hygiene(db: &'a dyn DefDatabase, hygiene: &Hygiene) -> Self { + LowerCtx { db, hygiene: hygiene.clone(), ast_id_map: None } + } + + pub(crate) fn hygiene(&self) -> &Hygiene { + &self.hygiene + } + + pub(crate) fn lower_path(&self, ast: ast::Path) -> Option<Path> { + Path::from_src(ast, self) + } + + pub(crate) fn ast_id<N: AstNode>(&self, db: &dyn DefDatabase, item: &N) -> Option<AstId<N>> { + let &(file_id, ref ast_id_map) = self.ast_id_map.as_ref()?; + let ast_id_map = ast_id_map.get_or_init(|| db.ast_id_map(file_id)); + Some(InFile::new(file_id, ast_id_map.ast_id(item))) + } +} + +pub(super) fn lower( + db: &dyn DefDatabase, + expander: Expander, + params: Option<ast::ParamList>, + body: Option<ast::Expr>, +) -> (Body, BodySourceMap) { + ExprCollector { + db, + source_map: BodySourceMap::default(), + ast_id_map: db.ast_id_map(expander.current_file_id), + body: Body { + exprs: Arena::default(), + pats: Arena::default(), + labels: Arena::default(), + params: Vec::new(), + body_expr: dummy_expr_id(), + block_scopes: Vec::new(), + _c: Count::new(), + or_pats: Default::default(), + }, + expander, + name_to_pat_grouping: Default::default(), + is_lowering_inside_or_pat: false, + is_lowering_assignee_expr: false, + } + .collect(params, body) +} + +struct ExprCollector<'a> { + db: &'a dyn DefDatabase, + expander: Expander, + ast_id_map: Arc<AstIdMap>, + body: Body, + source_map: BodySourceMap, + // a poor-mans union-find? + name_to_pat_grouping: FxHashMap<Name, Vec<PatId>>, + is_lowering_inside_or_pat: bool, + is_lowering_assignee_expr: bool, +} + +impl ExprCollector<'_> { + fn collect( + mut self, + param_list: Option<ast::ParamList>, + body: Option<ast::Expr>, + ) -> (Body, BodySourceMap) { + if let Some(param_list) = param_list { + if let Some(self_param) = param_list.self_param() { + let ptr = AstPtr::new(&self_param); + let param_pat = self.alloc_pat( + Pat::Bind { + name: name![self], + mode: BindingAnnotation::new( + self_param.mut_token().is_some() && self_param.amp_token().is_none(), + false, + ), + subpat: None, + }, + Either::Right(ptr), + ); + self.body.params.push(param_pat); + } + + for pat in param_list.params().filter_map(|param| param.pat()) { + let param_pat = self.collect_pat(pat); + self.body.params.push(param_pat); + } + }; + + self.body.body_expr = self.collect_expr_opt(body); + (self.body, self.source_map) + } + + fn ctx(&self) -> LowerCtx<'_> { + LowerCtx::new(self.db, self.expander.current_file_id) + } + + fn alloc_expr(&mut self, expr: Expr, ptr: AstPtr<ast::Expr>) -> ExprId { + let src = self.expander.to_source(ptr); + let id = self.make_expr(expr, Ok(src.clone())); + self.source_map.expr_map.insert(src, id); + id + } + // desugared exprs don't have ptr, that's wrong and should be fixed + // somehow. + fn alloc_expr_desugared(&mut self, expr: Expr) -> ExprId { + self.make_expr(expr, Err(SyntheticSyntax)) + } + fn missing_expr(&mut self) -> ExprId { + self.alloc_expr_desugared(Expr::Missing) + } + fn make_expr(&mut self, expr: Expr, src: Result<ExprSource, SyntheticSyntax>) -> ExprId { + let id = self.body.exprs.alloc(expr); + self.source_map.expr_map_back.insert(id, src); + id + } + + fn alloc_pat(&mut self, pat: Pat, ptr: PatPtr) -> PatId { + let src = self.expander.to_source(ptr); + let id = self.make_pat(pat, Ok(src.clone())); + self.source_map.pat_map.insert(src, id); + id + } + fn missing_pat(&mut self) -> PatId { + self.make_pat(Pat::Missing, Err(SyntheticSyntax)) + } + fn make_pat(&mut self, pat: Pat, src: Result<PatSource, SyntheticSyntax>) -> PatId { + let id = self.body.pats.alloc(pat); + self.source_map.pat_map_back.insert(id, src); + id + } + + fn alloc_label(&mut self, label: Label, ptr: AstPtr<ast::Label>) -> LabelId { + let src = self.expander.to_source(ptr); + let id = self.make_label(label, src.clone()); + self.source_map.label_map.insert(src, id); + id + } + fn make_label(&mut self, label: Label, src: LabelSource) -> LabelId { + let id = self.body.labels.alloc(label); + self.source_map.label_map_back.insert(id, src); + id + } + + fn collect_expr(&mut self, expr: ast::Expr) -> ExprId { + self.maybe_collect_expr(expr).unwrap_or_else(|| self.missing_expr()) + } + + /// Returns `None` if and only if the expression is `#[cfg]`d out. + fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option<ExprId> { + let syntax_ptr = AstPtr::new(&expr); + self.check_cfg(&expr)?; + + Some(match expr { + ast::Expr::IfExpr(e) => { + let then_branch = self.collect_block_opt(e.then_branch()); + + let else_branch = e.else_branch().map(|b| match b { + ast::ElseBranch::Block(it) => self.collect_block(it), + ast::ElseBranch::IfExpr(elif) => { + let expr: ast::Expr = ast::Expr::cast(elif.syntax().clone()).unwrap(); + self.collect_expr(expr) + } + }); + + let condition = self.collect_expr_opt(e.condition()); + + self.alloc_expr(Expr::If { condition, then_branch, else_branch }, syntax_ptr) + } + ast::Expr::LetExpr(e) => { + let pat = self.collect_pat_opt(e.pat()); + let expr = self.collect_expr_opt(e.expr()); + self.alloc_expr(Expr::Let { pat, expr }, syntax_ptr) + } + ast::Expr::BlockExpr(e) => match e.modifier() { + Some(ast::BlockModifier::Try(_)) => { + let body = self.collect_block(e); + self.alloc_expr(Expr::TryBlock { body }, syntax_ptr) + } + Some(ast::BlockModifier::Unsafe(_)) => { + let body = self.collect_block(e); + self.alloc_expr(Expr::Unsafe { body }, syntax_ptr) + } + // FIXME: we need to record these effects somewhere... + Some(ast::BlockModifier::Label(label)) => { + let label = self.collect_label(label); + let res = self.collect_block(e); + match &mut self.body.exprs[res] { + Expr::Block { label: block_label, .. } => { + *block_label = Some(label); + } + _ => unreachable!(), + } + res + } + Some(ast::BlockModifier::Async(_)) => { + let body = self.collect_block(e); + self.alloc_expr(Expr::Async { body }, syntax_ptr) + } + Some(ast::BlockModifier::Const(_)) => { + let body = self.collect_block(e); + self.alloc_expr(Expr::Const { body }, syntax_ptr) + } + None => self.collect_block(e), + }, + ast::Expr::LoopExpr(e) => { + let label = e.label().map(|label| self.collect_label(label)); + let body = self.collect_block_opt(e.loop_body()); + self.alloc_expr(Expr::Loop { body, label }, syntax_ptr) + } + ast::Expr::WhileExpr(e) => { + let label = e.label().map(|label| self.collect_label(label)); + let body = self.collect_block_opt(e.loop_body()); + + let condition = self.collect_expr_opt(e.condition()); + + self.alloc_expr(Expr::While { condition, body, label }, syntax_ptr) + } + ast::Expr::ForExpr(e) => { + let label = e.label().map(|label| self.collect_label(label)); + let iterable = self.collect_expr_opt(e.iterable()); + let pat = self.collect_pat_opt(e.pat()); + let body = self.collect_block_opt(e.loop_body()); + self.alloc_expr(Expr::For { iterable, pat, body, label }, syntax_ptr) + } + ast::Expr::CallExpr(e) => { + let callee = self.collect_expr_opt(e.expr()); + let args = if let Some(arg_list) = e.arg_list() { + arg_list.args().filter_map(|e| self.maybe_collect_expr(e)).collect() + } else { + Box::default() + }; + self.alloc_expr( + Expr::Call { callee, args, is_assignee_expr: self.is_lowering_assignee_expr }, + syntax_ptr, + ) + } + ast::Expr::MethodCallExpr(e) => { + let receiver = self.collect_expr_opt(e.receiver()); + let args = if let Some(arg_list) = e.arg_list() { + arg_list.args().filter_map(|e| self.maybe_collect_expr(e)).collect() + } else { + Box::default() + }; + let method_name = e.name_ref().map(|nr| nr.as_name()).unwrap_or_else(Name::missing); + let generic_args = e + .generic_arg_list() + .and_then(|it| GenericArgs::from_ast(&self.ctx(), it)) + .map(Box::new); + self.alloc_expr( + Expr::MethodCall { receiver, method_name, args, generic_args }, + syntax_ptr, + ) + } + ast::Expr::MatchExpr(e) => { + let expr = self.collect_expr_opt(e.expr()); + let arms = if let Some(match_arm_list) = e.match_arm_list() { + match_arm_list + .arms() + .filter_map(|arm| { + self.check_cfg(&arm).map(|()| MatchArm { + pat: self.collect_pat_opt(arm.pat()), + expr: self.collect_expr_opt(arm.expr()), + guard: arm + .guard() + .map(|guard| self.collect_expr_opt(guard.condition())), + }) + }) + .collect() + } else { + Box::default() + }; + self.alloc_expr(Expr::Match { expr, arms }, syntax_ptr) + } + ast::Expr::PathExpr(e) => { + let path = e + .path() + .and_then(|path| self.expander.parse_path(self.db, path)) + .map(Expr::Path) + .unwrap_or(Expr::Missing); + self.alloc_expr(path, syntax_ptr) + } + ast::Expr::ContinueExpr(e) => self.alloc_expr( + Expr::Continue { label: e.lifetime().map(|l| Name::new_lifetime(&l)) }, + syntax_ptr, + ), + ast::Expr::BreakExpr(e) => { + let expr = e.expr().map(|e| self.collect_expr(e)); + self.alloc_expr( + Expr::Break { expr, label: e.lifetime().map(|l| Name::new_lifetime(&l)) }, + syntax_ptr, + ) + } + ast::Expr::ParenExpr(e) => { + let inner = self.collect_expr_opt(e.expr()); + // make the paren expr point to the inner expression as well + let src = self.expander.to_source(syntax_ptr); + self.source_map.expr_map.insert(src, inner); + inner + } + ast::Expr::ReturnExpr(e) => { + let expr = e.expr().map(|e| self.collect_expr(e)); + self.alloc_expr(Expr::Return { expr }, syntax_ptr) + } + ast::Expr::YieldExpr(e) => { + let expr = e.expr().map(|e| self.collect_expr(e)); + self.alloc_expr(Expr::Yield { expr }, syntax_ptr) + } + ast::Expr::RecordExpr(e) => { + let path = + e.path().and_then(|path| self.expander.parse_path(self.db, path)).map(Box::new); + let is_assignee_expr = self.is_lowering_assignee_expr; + let record_lit = if let Some(nfl) = e.record_expr_field_list() { + let fields = nfl + .fields() + .filter_map(|field| { + self.check_cfg(&field)?; + + let name = field.field_name()?.as_name(); + + let expr = match field.expr() { + Some(e) => self.collect_expr(e), + None => self.missing_expr(), + }; + let src = self.expander.to_source(AstPtr::new(&field)); + self.source_map.field_map.insert(src.clone(), expr); + self.source_map.field_map_back.insert(expr, src); + Some(RecordLitField { name, expr }) + }) + .collect(); + let spread = nfl.spread().map(|s| self.collect_expr(s)); + let ellipsis = nfl.dotdot_token().is_some(); + Expr::RecordLit { path, fields, spread, ellipsis, is_assignee_expr } + } else { + Expr::RecordLit { + path, + fields: Box::default(), + spread: None, + ellipsis: false, + is_assignee_expr, + } + }; + + self.alloc_expr(record_lit, syntax_ptr) + } + ast::Expr::FieldExpr(e) => { + let expr = self.collect_expr_opt(e.expr()); + let name = match e.field_access() { + Some(kind) => kind.as_name(), + _ => Name::missing(), + }; + self.alloc_expr(Expr::Field { expr, name }, syntax_ptr) + } + ast::Expr::AwaitExpr(e) => { + let expr = self.collect_expr_opt(e.expr()); + self.alloc_expr(Expr::Await { expr }, syntax_ptr) + } + ast::Expr::TryExpr(e) => { + let expr = self.collect_expr_opt(e.expr()); + self.alloc_expr(Expr::Try { expr }, syntax_ptr) + } + ast::Expr::CastExpr(e) => { + let expr = self.collect_expr_opt(e.expr()); + let type_ref = Interned::new(TypeRef::from_ast_opt(&self.ctx(), e.ty())); + self.alloc_expr(Expr::Cast { expr, type_ref }, syntax_ptr) + } + ast::Expr::RefExpr(e) => { + let expr = self.collect_expr_opt(e.expr()); + let raw_tok = e.raw_token().is_some(); + let mutability = if raw_tok { + if e.mut_token().is_some() { + Mutability::Mut + } else if e.const_token().is_some() { + Mutability::Shared + } else { + unreachable!("parser only remaps to raw_token() if matching mutability token follows") + } + } else { + Mutability::from_mutable(e.mut_token().is_some()) + }; + let rawness = Rawness::from_raw(raw_tok); + self.alloc_expr(Expr::Ref { expr, rawness, mutability }, syntax_ptr) + } + ast::Expr::PrefixExpr(e) => { + let expr = self.collect_expr_opt(e.expr()); + match e.op_kind() { + Some(op) => self.alloc_expr(Expr::UnaryOp { expr, op }, syntax_ptr), + None => self.alloc_expr(Expr::Missing, syntax_ptr), + } + } + ast::Expr::ClosureExpr(e) => { + let mut args = Vec::new(); + let mut arg_types = Vec::new(); + if let Some(pl) = e.param_list() { + for param in pl.params() { + let pat = self.collect_pat_opt(param.pat()); + let type_ref = + param.ty().map(|it| Interned::new(TypeRef::from_ast(&self.ctx(), it))); + args.push(pat); + arg_types.push(type_ref); + } + } + let ret_type = e + .ret_type() + .and_then(|r| r.ty()) + .map(|it| Interned::new(TypeRef::from_ast(&self.ctx(), it))); + let body = self.collect_expr_opt(e.body()); + self.alloc_expr( + Expr::Closure { + args: args.into(), + arg_types: arg_types.into(), + ret_type, + body, + }, + syntax_ptr, + ) + } + ast::Expr::BinExpr(e) => { + let op = e.op_kind(); + if let Some(ast::BinaryOp::Assignment { op: None }) = op { + self.is_lowering_assignee_expr = true; + } + let lhs = self.collect_expr_opt(e.lhs()); + self.is_lowering_assignee_expr = false; + let rhs = self.collect_expr_opt(e.rhs()); + self.alloc_expr(Expr::BinaryOp { lhs, rhs, op }, syntax_ptr) + } + ast::Expr::TupleExpr(e) => { + let exprs = e.fields().map(|expr| self.collect_expr(expr)).collect(); + self.alloc_expr( + Expr::Tuple { exprs, is_assignee_expr: self.is_lowering_assignee_expr }, + syntax_ptr, + ) + } + ast::Expr::BoxExpr(e) => { + let expr = self.collect_expr_opt(e.expr()); + self.alloc_expr(Expr::Box { expr }, syntax_ptr) + } + + ast::Expr::ArrayExpr(e) => { + let kind = e.kind(); + + match kind { + ArrayExprKind::ElementList(e) => { + let elements = e.map(|expr| self.collect_expr(expr)).collect(); + self.alloc_expr( + Expr::Array(Array::ElementList { + elements, + is_assignee_expr: self.is_lowering_assignee_expr, + }), + syntax_ptr, + ) + } + ArrayExprKind::Repeat { initializer, repeat } => { + let initializer = self.collect_expr_opt(initializer); + let repeat = self.collect_expr_opt(repeat); + self.alloc_expr( + Expr::Array(Array::Repeat { initializer, repeat }), + syntax_ptr, + ) + } + } + } + + ast::Expr::Literal(e) => self.alloc_expr(Expr::Literal(e.kind().into()), syntax_ptr), + ast::Expr::IndexExpr(e) => { + let base = self.collect_expr_opt(e.base()); + let index = self.collect_expr_opt(e.index()); + self.alloc_expr(Expr::Index { base, index }, syntax_ptr) + } + ast::Expr::RangeExpr(e) => { + let lhs = e.start().map(|lhs| self.collect_expr(lhs)); + let rhs = e.end().map(|rhs| self.collect_expr(rhs)); + match e.op_kind() { + Some(range_type) => { + self.alloc_expr(Expr::Range { lhs, rhs, range_type }, syntax_ptr) + } + None => self.alloc_expr(Expr::Missing, syntax_ptr), + } + } + ast::Expr::MacroExpr(e) => { + let e = e.macro_call()?; + let macro_ptr = AstPtr::new(&e); + let id = self.collect_macro_call(e, macro_ptr, true, |this, expansion| { + expansion.map(|it| this.collect_expr(it)) + }); + match id { + Some(id) => { + // Make the macro-call point to its expanded expression so we can query + // semantics on syntax pointers to the macro + let src = self.expander.to_source(syntax_ptr); + self.source_map.expr_map.insert(src, id); + id + } + None => self.alloc_expr(Expr::Missing, syntax_ptr), + } + } + ast::Expr::MacroStmts(e) => { + let statements = e.statements().filter_map(|s| self.collect_stmt(s)).collect(); + let tail = e.expr().map(|e| self.collect_expr(e)); + + self.alloc_expr(Expr::MacroStmts { tail, statements }, syntax_ptr) + } + ast::Expr::UnderscoreExpr(_) => self.alloc_expr(Expr::Underscore, syntax_ptr), + }) + } + + fn collect_macro_call<F, T, U>( + &mut self, + mcall: ast::MacroCall, + syntax_ptr: AstPtr<ast::MacroCall>, + record_diagnostics: bool, + collector: F, + ) -> U + where + F: FnOnce(&mut Self, Option<T>) -> U, + T: ast::AstNode, + { + // File containing the macro call. Expansion errors will be attached here. + let outer_file = self.expander.current_file_id; + + let macro_call_ptr = self.expander.to_source(AstPtr::new(&mcall)); + let res = self.expander.enter_expand(self.db, mcall); + + let res = match res { + Ok(res) => res, + Err(UnresolvedMacro { path }) => { + if record_diagnostics { + self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedMacroCall { + node: InFile::new(outer_file, syntax_ptr), + path, + }); + } + return collector(self, None); + } + }; + + if record_diagnostics { + match &res.err { + Some(ExpandError::UnresolvedProcMacro(krate)) => { + self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedProcMacro { + node: InFile::new(outer_file, syntax_ptr), + krate: *krate, + }); + } + Some(err) => { + self.source_map.diagnostics.push(BodyDiagnostic::MacroError { + node: InFile::new(outer_file, syntax_ptr), + message: err.to_string(), + }); + } + None => {} + } + } + + match res.value { + Some((mark, expansion)) => { + self.source_map.expansions.insert(macro_call_ptr, self.expander.current_file_id); + let prev_ast_id_map = mem::replace( + &mut self.ast_id_map, + self.db.ast_id_map(self.expander.current_file_id), + ); + + let id = collector(self, Some(expansion)); + self.ast_id_map = prev_ast_id_map; + self.expander.exit(self.db, mark); + id + } + None => collector(self, None), + } + } + + fn collect_expr_opt(&mut self, expr: Option<ast::Expr>) -> ExprId { + match expr { + Some(expr) => self.collect_expr(expr), + None => self.missing_expr(), + } + } + + fn collect_stmt(&mut self, s: ast::Stmt) -> Option<Statement> { + match s { + ast::Stmt::LetStmt(stmt) => { + if self.check_cfg(&stmt).is_none() { + return None; + } + let pat = self.collect_pat_opt(stmt.pat()); + let type_ref = + stmt.ty().map(|it| Interned::new(TypeRef::from_ast(&self.ctx(), it))); + let initializer = stmt.initializer().map(|e| self.collect_expr(e)); + let else_branch = stmt + .let_else() + .and_then(|let_else| let_else.block_expr()) + .map(|block| self.collect_block(block)); + Some(Statement::Let { pat, type_ref, initializer, else_branch }) + } + ast::Stmt::ExprStmt(stmt) => { + let expr = stmt.expr(); + if let Some(expr) = &expr { + if self.check_cfg(expr).is_none() { + return None; + } + } + let has_semi = stmt.semicolon_token().is_some(); + // Note that macro could be expanded to multiple statements + if let Some(expr @ ast::Expr::MacroExpr(mac)) = &expr { + let mac_call = mac.macro_call()?; + let syntax_ptr = AstPtr::new(expr); + let macro_ptr = AstPtr::new(&mac_call); + let stmt = self.collect_macro_call( + mac_call, + macro_ptr, + false, + |this, expansion: Option<ast::MacroStmts>| match expansion { + Some(expansion) => { + let statements = expansion + .statements() + .filter_map(|stmt| this.collect_stmt(stmt)) + .collect(); + let tail = expansion.expr().map(|expr| this.collect_expr(expr)); + + let mac_stmts = this.alloc_expr( + Expr::MacroStmts { tail, statements }, + AstPtr::new(&ast::Expr::MacroStmts(expansion)), + ); + + Some(mac_stmts) + } + None => None, + }, + ); + + let expr = match stmt { + Some(expr) => { + // Make the macro-call point to its expanded expression so we can query + // semantics on syntax pointers to the macro + let src = self.expander.to_source(syntax_ptr); + self.source_map.expr_map.insert(src, expr); + expr + } + None => self.alloc_expr(Expr::Missing, syntax_ptr), + }; + Some(Statement::Expr { expr, has_semi }) + } else { + let expr = self.collect_expr_opt(expr); + Some(Statement::Expr { expr, has_semi }) + } + } + ast::Stmt::Item(_item) => None, + } + } + + fn collect_block(&mut self, block: ast::BlockExpr) -> ExprId { + let file_local_id = self.ast_id_map.ast_id(&block); + let ast_id = AstId::new(self.expander.current_file_id, file_local_id); + let block_loc = + BlockLoc { ast_id, module: self.expander.def_map.module_id(self.expander.module) }; + let block_id = self.db.intern_block(block_loc); + + let (module, def_map) = match self.db.block_def_map(block_id) { + Some(def_map) => { + self.body.block_scopes.push(block_id); + (def_map.root(), def_map) + } + None => (self.expander.module, self.expander.def_map.clone()), + }; + let prev_def_map = mem::replace(&mut self.expander.def_map, def_map); + let prev_local_module = mem::replace(&mut self.expander.module, module); + + let mut statements: Vec<_> = + block.statements().filter_map(|s| self.collect_stmt(s)).collect(); + let tail = block.tail_expr().and_then(|e| self.maybe_collect_expr(e)); + let tail = tail.or_else(|| { + let stmt = statements.pop()?; + if let Statement::Expr { expr, has_semi: false } = stmt { + return Some(expr); + } + statements.push(stmt); + None + }); + + let syntax_node_ptr = AstPtr::new(&block.into()); + let expr_id = self.alloc_expr( + Expr::Block { + id: block_id, + statements: statements.into_boxed_slice(), + tail, + label: None, + }, + syntax_node_ptr, + ); + + self.expander.def_map = prev_def_map; + self.expander.module = prev_local_module; + expr_id + } + + fn collect_block_opt(&mut self, expr: Option<ast::BlockExpr>) -> ExprId { + match expr { + Some(block) => self.collect_block(block), + None => self.missing_expr(), + } + } + + fn collect_label(&mut self, ast_label: ast::Label) -> LabelId { + let label = Label { + name: ast_label.lifetime().as_ref().map_or_else(Name::missing, Name::new_lifetime), + }; + self.alloc_label(label, AstPtr::new(&ast_label)) + } + + fn collect_pat(&mut self, pat: ast::Pat) -> PatId { + let pat_id = self.collect_pat_(pat); + for (_, pats) in self.name_to_pat_grouping.drain() { + let pats = Arc::<[_]>::from(pats); + self.body.or_pats.extend(pats.iter().map(|&pat| (pat, pats.clone()))); + } + self.is_lowering_inside_or_pat = false; + pat_id + } + + fn collect_pat_opt(&mut self, pat: Option<ast::Pat>) -> PatId { + match pat { + Some(pat) => self.collect_pat(pat), + None => self.missing_pat(), + } + } + + fn collect_pat_(&mut self, pat: ast::Pat) -> PatId { + let pattern = match &pat { + ast::Pat::IdentPat(bp) => { + let name = bp.name().map(|nr| nr.as_name()).unwrap_or_else(Name::missing); + + let key = self.is_lowering_inside_or_pat.then(|| name.clone()); + let annotation = + BindingAnnotation::new(bp.mut_token().is_some(), bp.ref_token().is_some()); + let subpat = bp.pat().map(|subpat| self.collect_pat_(subpat)); + let pattern = if annotation == BindingAnnotation::Unannotated && subpat.is_none() { + // This could also be a single-segment path pattern. To + // decide that, we need to try resolving the name. + let (resolved, _) = self.expander.def_map.resolve_path( + self.db, + self.expander.module, + &name.clone().into(), + BuiltinShadowMode::Other, + ); + match resolved.take_values() { + Some(ModuleDefId::ConstId(_)) => Pat::Path(name.into()), + Some(ModuleDefId::EnumVariantId(_)) => { + // this is only really valid for unit variants, but + // shadowing other enum variants with a pattern is + // an error anyway + Pat::Path(name.into()) + } + Some(ModuleDefId::AdtId(AdtId::StructId(s))) + if self.db.struct_data(s).variant_data.kind() != StructKind::Record => + { + // Funnily enough, record structs *can* be shadowed + // by pattern bindings (but unit or tuple structs + // can't). + Pat::Path(name.into()) + } + // shadowing statics is an error as well, so we just ignore that case here + _ => Pat::Bind { name, mode: annotation, subpat }, + } + } else { + Pat::Bind { name, mode: annotation, subpat } + }; + + let ptr = AstPtr::new(&pat); + let pat = self.alloc_pat(pattern, Either::Left(ptr)); + if let Some(key) = key { + self.name_to_pat_grouping.entry(key).or_default().push(pat); + } + return pat; + } + ast::Pat::TupleStructPat(p) => { + let path = + p.path().and_then(|path| self.expander.parse_path(self.db, path)).map(Box::new); + let (args, ellipsis) = self.collect_tuple_pat(p.fields()); + Pat::TupleStruct { path, args, ellipsis } + } + ast::Pat::RefPat(p) => { + let pat = self.collect_pat_opt(p.pat()); + let mutability = Mutability::from_mutable(p.mut_token().is_some()); + Pat::Ref { pat, mutability } + } + ast::Pat::PathPat(p) => { + let path = + p.path().and_then(|path| self.expander.parse_path(self.db, path)).map(Box::new); + path.map(Pat::Path).unwrap_or(Pat::Missing) + } + ast::Pat::OrPat(p) => { + self.is_lowering_inside_or_pat = true; + let pats = p.pats().map(|p| self.collect_pat_(p)).collect(); + Pat::Or(pats) + } + ast::Pat::ParenPat(p) => return self.collect_pat_opt_(p.pat()), + ast::Pat::TuplePat(p) => { + let (args, ellipsis) = self.collect_tuple_pat(p.fields()); + Pat::Tuple { args, ellipsis } + } + ast::Pat::WildcardPat(_) => Pat::Wild, + ast::Pat::RecordPat(p) => { + let path = + p.path().and_then(|path| self.expander.parse_path(self.db, path)).map(Box::new); + let args = p + .record_pat_field_list() + .expect("every struct should have a field list") + .fields() + .filter_map(|f| { + let ast_pat = f.pat()?; + let pat = self.collect_pat_(ast_pat); + let name = f.field_name()?.as_name(); + Some(RecordFieldPat { name, pat }) + }) + .collect(); + + let ellipsis = p + .record_pat_field_list() + .expect("every struct should have a field list") + .rest_pat() + .is_some(); + + Pat::Record { path, args, ellipsis } + } + ast::Pat::SlicePat(p) => { + let SlicePatComponents { prefix, slice, suffix } = p.components(); + + // FIXME properly handle `RestPat` + Pat::Slice { + prefix: prefix.into_iter().map(|p| self.collect_pat_(p)).collect(), + slice: slice.map(|p| self.collect_pat_(p)), + suffix: suffix.into_iter().map(|p| self.collect_pat_(p)).collect(), + } + } + ast::Pat::LiteralPat(lit) => { + if let Some(ast_lit) = lit.literal() { + let expr = Expr::Literal(ast_lit.kind().into()); + let expr_ptr = AstPtr::new(&ast::Expr::Literal(ast_lit)); + let expr_id = self.alloc_expr(expr, expr_ptr); + Pat::Lit(expr_id) + } else { + Pat::Missing + } + } + ast::Pat::RestPat(_) => { + // `RestPat` requires special handling and should not be mapped + // to a Pat. Here we are using `Pat::Missing` as a fallback for + // when `RestPat` is mapped to `Pat`, which can easily happen + // when the source code being analyzed has a malformed pattern + // which includes `..` in a place where it isn't valid. + + Pat::Missing + } + ast::Pat::BoxPat(boxpat) => { + let inner = self.collect_pat_opt_(boxpat.pat()); + Pat::Box { inner } + } + ast::Pat::ConstBlockPat(const_block_pat) => { + if let Some(expr) = const_block_pat.block_expr() { + let expr_id = self.collect_block(expr); + Pat::ConstBlock(expr_id) + } else { + Pat::Missing + } + } + ast::Pat::MacroPat(mac) => match mac.macro_call() { + Some(call) => { + let macro_ptr = AstPtr::new(&call); + let src = self.expander.to_source(Either::Left(AstPtr::new(&pat))); + let pat = + self.collect_macro_call(call, macro_ptr, true, |this, expanded_pat| { + this.collect_pat_opt_(expanded_pat) + }); + self.source_map.pat_map.insert(src, pat); + return pat; + } + None => Pat::Missing, + }, + // FIXME: implement + ast::Pat::RangePat(_) => Pat::Missing, + }; + let ptr = AstPtr::new(&pat); + self.alloc_pat(pattern, Either::Left(ptr)) + } + + fn collect_pat_opt_(&mut self, pat: Option<ast::Pat>) -> PatId { + match pat { + Some(pat) => self.collect_pat_(pat), + None => self.missing_pat(), + } + } + + fn collect_tuple_pat(&mut self, args: AstChildren<ast::Pat>) -> (Box<[PatId]>, Option<usize>) { + // Find the location of the `..`, if there is one. Note that we do not + // consider the possibility of there being multiple `..` here. + let ellipsis = args.clone().position(|p| matches!(p, ast::Pat::RestPat(_))); + // We want to skip the `..` pattern here, since we account for it above. + let args = args + .filter(|p| !matches!(p, ast::Pat::RestPat(_))) + .map(|p| self.collect_pat_(p)) + .collect(); + + (args, ellipsis) + } + + /// Returns `None` (and emits diagnostics) when `owner` if `#[cfg]`d out, and `Some(())` when + /// not. + fn check_cfg(&mut self, owner: &dyn ast::HasAttrs) -> Option<()> { + match self.expander.parse_attrs(self.db, owner).cfg() { + Some(cfg) => { + if self.expander.cfg_options().check(&cfg) != Some(false) { + return Some(()); + } + + self.source_map.diagnostics.push(BodyDiagnostic::InactiveCode { + node: InFile::new( + self.expander.current_file_id, + SyntaxNodePtr::new(owner.syntax()), + ), + cfg, + opts: self.expander.cfg_options().clone(), + }); + + None + } + None => Some(()), + } + } +} + +impl From<ast::LiteralKind> for Literal { + fn from(ast_lit_kind: ast::LiteralKind) -> Self { + match ast_lit_kind { + // FIXME: these should have actual values filled in, but unsure on perf impact + LiteralKind::IntNumber(lit) => { + if let builtin @ Some(_) = lit.suffix().and_then(BuiltinFloat::from_suffix) { + Literal::Float( + FloatTypeWrapper::new(lit.float_value().unwrap_or(Default::default())), + builtin, + ) + } else if let builtin @ Some(_) = lit.suffix().and_then(BuiltinInt::from_suffix) { + Literal::Int(lit.value().unwrap_or(0) as i128, builtin) + } else { + let builtin = lit.suffix().and_then(BuiltinUint::from_suffix); + Literal::Uint(lit.value().unwrap_or(0), builtin) + } + } + LiteralKind::FloatNumber(lit) => { + let ty = lit.suffix().and_then(BuiltinFloat::from_suffix); + Literal::Float(FloatTypeWrapper::new(lit.value().unwrap_or(Default::default())), ty) + } + LiteralKind::ByteString(bs) => { + let text = bs.value().map(Box::from).unwrap_or_else(Default::default); + Literal::ByteString(text) + } + LiteralKind::String(s) => { + let text = s.value().map(Box::from).unwrap_or_else(Default::default); + Literal::String(text) + } + LiteralKind::Byte(b) => { + Literal::Uint(b.value().unwrap_or_default() as u128, Some(BuiltinUint::U8)) + } + LiteralKind::Char(c) => Literal::Char(c.value().unwrap_or_default()), + LiteralKind::Bool(val) => Literal::Bool(val), + } + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/scope.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/scope.rs new file mode 100644 index 000000000..f4c390dce --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/body/scope.rs @@ -0,0 +1,571 @@ +//! Name resolution for expressions. +use std::sync::Arc; + +use hir_expand::name::Name; +use la_arena::{Arena, Idx}; +use rustc_hash::FxHashMap; + +use crate::{ + body::Body, + db::DefDatabase, + expr::{Expr, ExprId, LabelId, Pat, PatId, Statement}, + BlockId, DefWithBodyId, +}; + +pub type ScopeId = Idx<ScopeData>; + +#[derive(Debug, PartialEq, Eq)] +pub struct ExprScopes { + scopes: Arena<ScopeData>, + scope_by_expr: FxHashMap<ExprId, ScopeId>, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct ScopeEntry { + name: Name, + pat: PatId, +} + +impl ScopeEntry { + pub fn name(&self) -> &Name { + &self.name + } + + pub fn pat(&self) -> PatId { + self.pat + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct ScopeData { + parent: Option<ScopeId>, + block: Option<BlockId>, + label: Option<(LabelId, Name)>, + entries: Vec<ScopeEntry>, +} + +impl ExprScopes { + pub(crate) fn expr_scopes_query(db: &dyn DefDatabase, def: DefWithBodyId) -> Arc<ExprScopes> { + let body = db.body(def); + Arc::new(ExprScopes::new(&*body)) + } + + fn new(body: &Body) -> ExprScopes { + let mut scopes = + ExprScopes { scopes: Arena::default(), scope_by_expr: FxHashMap::default() }; + let mut root = scopes.root_scope(); + scopes.add_params_bindings(body, root, &body.params); + compute_expr_scopes(body.body_expr, body, &mut scopes, &mut root); + scopes + } + + pub fn entries(&self, scope: ScopeId) -> &[ScopeEntry] { + &self.scopes[scope].entries + } + + /// If `scope` refers to a block expression scope, returns the corresponding `BlockId`. + pub fn block(&self, scope: ScopeId) -> Option<BlockId> { + self.scopes[scope].block + } + + /// If `scope` refers to a labeled expression scope, returns the corresponding `Label`. + pub fn label(&self, scope: ScopeId) -> Option<(LabelId, Name)> { + self.scopes[scope].label.clone() + } + + pub fn scope_chain(&self, scope: Option<ScopeId>) -> impl Iterator<Item = ScopeId> + '_ { + std::iter::successors(scope, move |&scope| self.scopes[scope].parent) + } + + pub fn resolve_name_in_scope(&self, scope: ScopeId, name: &Name) -> Option<&ScopeEntry> { + self.scope_chain(Some(scope)) + .find_map(|scope| self.entries(scope).iter().find(|it| it.name == *name)) + } + + pub fn scope_for(&self, expr: ExprId) -> Option<ScopeId> { + self.scope_by_expr.get(&expr).copied() + } + + pub fn scope_by_expr(&self) -> &FxHashMap<ExprId, ScopeId> { + &self.scope_by_expr + } + + fn root_scope(&mut self) -> ScopeId { + self.scopes.alloc(ScopeData { parent: None, block: None, label: None, entries: vec![] }) + } + + fn new_scope(&mut self, parent: ScopeId) -> ScopeId { + self.scopes.alloc(ScopeData { + parent: Some(parent), + block: None, + label: None, + entries: vec![], + }) + } + + fn new_labeled_scope(&mut self, parent: ScopeId, label: Option<(LabelId, Name)>) -> ScopeId { + self.scopes.alloc(ScopeData { parent: Some(parent), block: None, label, entries: vec![] }) + } + + fn new_block_scope( + &mut self, + parent: ScopeId, + block: BlockId, + label: Option<(LabelId, Name)>, + ) -> ScopeId { + self.scopes.alloc(ScopeData { + parent: Some(parent), + block: Some(block), + label, + entries: vec![], + }) + } + + fn add_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) { + let pattern = &body[pat]; + if let Pat::Bind { name, .. } = pattern { + let entry = ScopeEntry { name: name.clone(), pat }; + self.scopes[scope].entries.push(entry); + } + + pattern.walk_child_pats(|pat| self.add_bindings(body, scope, pat)); + } + + fn add_params_bindings(&mut self, body: &Body, scope: ScopeId, params: &[PatId]) { + params.iter().for_each(|pat| self.add_bindings(body, scope, *pat)); + } + + fn set_scope(&mut self, node: ExprId, scope: ScopeId) { + self.scope_by_expr.insert(node, scope); + } +} + +fn compute_block_scopes( + statements: &[Statement], + tail: Option<ExprId>, + body: &Body, + scopes: &mut ExprScopes, + scope: &mut ScopeId, +) { + for stmt in statements { + match stmt { + Statement::Let { pat, initializer, else_branch, .. } => { + if let Some(expr) = initializer { + compute_expr_scopes(*expr, body, scopes, scope); + } + if let Some(expr) = else_branch { + compute_expr_scopes(*expr, body, scopes, scope); + } + + *scope = scopes.new_scope(*scope); + scopes.add_bindings(body, *scope, *pat); + } + Statement::Expr { expr, .. } => { + compute_expr_scopes(*expr, body, scopes, scope); + } + } + } + if let Some(expr) = tail { + compute_expr_scopes(expr, body, scopes, scope); + } +} + +fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: &mut ScopeId) { + let make_label = + |label: &Option<LabelId>| label.map(|label| (label, body.labels[label].name.clone())); + + scopes.set_scope(expr, *scope); + match &body[expr] { + Expr::MacroStmts { statements, tail } => { + compute_block_scopes(statements, *tail, body, scopes, scope); + } + Expr::Block { statements, tail, id, label } => { + let mut scope = scopes.new_block_scope(*scope, *id, make_label(label)); + // Overwrite the old scope for the block expr, so that every block scope can be found + // via the block itself (important for blocks that only contain items, no expressions). + scopes.set_scope(expr, scope); + compute_block_scopes(statements, *tail, body, scopes, &mut scope); + } + Expr::For { iterable, pat, body: body_expr, label } => { + compute_expr_scopes(*iterable, body, scopes, scope); + let mut scope = scopes.new_labeled_scope(*scope, make_label(label)); + scopes.add_bindings(body, scope, *pat); + compute_expr_scopes(*body_expr, body, scopes, &mut scope); + } + Expr::While { condition, body: body_expr, label } => { + let mut scope = scopes.new_labeled_scope(*scope, make_label(label)); + compute_expr_scopes(*condition, body, scopes, &mut scope); + compute_expr_scopes(*body_expr, body, scopes, &mut scope); + } + Expr::Loop { body: body_expr, label } => { + let mut scope = scopes.new_labeled_scope(*scope, make_label(label)); + compute_expr_scopes(*body_expr, body, scopes, &mut scope); + } + Expr::Closure { args, body: body_expr, .. } => { + let mut scope = scopes.new_scope(*scope); + scopes.add_params_bindings(body, scope, args); + compute_expr_scopes(*body_expr, body, scopes, &mut scope); + } + Expr::Match { expr, arms } => { + compute_expr_scopes(*expr, body, scopes, scope); + for arm in arms.iter() { + let mut scope = scopes.new_scope(*scope); + scopes.add_bindings(body, scope, arm.pat); + if let Some(guard) = arm.guard { + scope = scopes.new_scope(scope); + compute_expr_scopes(guard, body, scopes, &mut scope); + } + compute_expr_scopes(arm.expr, body, scopes, &mut scope); + } + } + &Expr::If { condition, then_branch, else_branch } => { + let mut then_branch_scope = scopes.new_scope(*scope); + compute_expr_scopes(condition, body, scopes, &mut then_branch_scope); + compute_expr_scopes(then_branch, body, scopes, &mut then_branch_scope); + if let Some(else_branch) = else_branch { + compute_expr_scopes(else_branch, body, scopes, scope); + } + } + &Expr::Let { pat, expr } => { + compute_expr_scopes(expr, body, scopes, scope); + *scope = scopes.new_scope(*scope); + scopes.add_bindings(body, *scope, pat); + } + e => e.walk_child_exprs(|e| compute_expr_scopes(e, body, scopes, scope)), + }; +} + +#[cfg(test)] +mod tests { + use base_db::{fixture::WithFixture, FileId, SourceDatabase}; + use hir_expand::{name::AsName, InFile}; + use syntax::{algo::find_node_at_offset, ast, AstNode}; + use test_utils::{assert_eq_text, extract_offset}; + + use crate::{db::DefDatabase, test_db::TestDB, FunctionId, ModuleDefId}; + + fn find_function(db: &TestDB, file_id: FileId) -> FunctionId { + let krate = db.test_crate(); + let crate_def_map = db.crate_def_map(krate); + + let module = crate_def_map.modules_for_file(file_id).next().unwrap(); + let (_, def) = crate_def_map[module].scope.entries().next().unwrap(); + match def.take_values().unwrap() { + ModuleDefId::FunctionId(it) => it, + _ => panic!(), + } + } + + fn do_check(ra_fixture: &str, expected: &[&str]) { + let (offset, code) = extract_offset(ra_fixture); + let code = { + let mut buf = String::new(); + let off: usize = offset.into(); + buf.push_str(&code[..off]); + buf.push_str("$0marker"); + buf.push_str(&code[off..]); + buf + }; + + let (db, position) = TestDB::with_position(&code); + let file_id = position.file_id; + let offset = position.offset; + + let file_syntax = db.parse(file_id).syntax_node(); + let marker: ast::PathExpr = find_node_at_offset(&file_syntax, offset).unwrap(); + let function = find_function(&db, file_id); + + let scopes = db.expr_scopes(function.into()); + let (_body, source_map) = db.body_with_source_map(function.into()); + + let expr_id = source_map + .node_expr(InFile { file_id: file_id.into(), value: &marker.into() }) + .unwrap(); + let scope = scopes.scope_for(expr_id); + + let actual = scopes + .scope_chain(scope) + .flat_map(|scope| scopes.entries(scope)) + .map(|it| it.name().to_smol_str()) + .collect::<Vec<_>>() + .join("\n"); + let expected = expected.join("\n"); + assert_eq_text!(&expected, &actual); + } + + #[test] + fn test_lambda_scope() { + do_check( + r" + fn quux(foo: i32) { + let f = |bar, baz: i32| { + $0 + }; + }", + &["bar", "baz", "foo"], + ); + } + + #[test] + fn test_call_scope() { + do_check( + r" + fn quux() { + f(|x| $0 ); + }", + &["x"], + ); + } + + #[test] + fn test_method_call_scope() { + do_check( + r" + fn quux() { + z.f(|x| $0 ); + }", + &["x"], + ); + } + + #[test] + fn test_loop_scope() { + do_check( + r" + fn quux() { + loop { + let x = (); + $0 + }; + }", + &["x"], + ); + } + + #[test] + fn test_match() { + do_check( + r" + fn quux() { + match () { + Some(x) => { + $0 + } + }; + }", + &["x"], + ); + } + + #[test] + fn test_shadow_variable() { + do_check( + r" + fn foo(x: String) { + let x : &str = &x$0; + }", + &["x"], + ); + } + + #[test] + fn test_bindings_after_at() { + do_check( + r" +fn foo() { + match Some(()) { + opt @ Some(unit) => { + $0 + } + _ => {} + } +} +", + &["opt", "unit"], + ); + } + + #[test] + fn macro_inner_item() { + do_check( + r" + macro_rules! mac { + () => {{ + fn inner() {} + inner(); + }}; + } + + fn foo() { + mac!(); + $0 + } + ", + &[], + ); + } + + #[test] + fn broken_inner_item() { + do_check( + r" + fn foo() { + trait {} + $0 + } + ", + &[], + ); + } + + fn do_check_local_name(ra_fixture: &str, expected_offset: u32) { + let (db, position) = TestDB::with_position(ra_fixture); + let file_id = position.file_id; + let offset = position.offset; + + let file = db.parse(file_id).ok().unwrap(); + let expected_name = find_node_at_offset::<ast::Name>(file.syntax(), expected_offset.into()) + .expect("failed to find a name at the target offset"); + let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), offset).unwrap(); + + let function = find_function(&db, file_id); + + let scopes = db.expr_scopes(function.into()); + let (_body, source_map) = db.body_with_source_map(function.into()); + + let expr_scope = { + let expr_ast = name_ref.syntax().ancestors().find_map(ast::Expr::cast).unwrap(); + let expr_id = + source_map.node_expr(InFile { file_id: file_id.into(), value: &expr_ast }).unwrap(); + scopes.scope_for(expr_id).unwrap() + }; + + let resolved = scopes.resolve_name_in_scope(expr_scope, &name_ref.as_name()).unwrap(); + let pat_src = source_map.pat_syntax(resolved.pat()).unwrap(); + + let local_name = pat_src.value.either( + |it| it.syntax_node_ptr().to_node(file.syntax()), + |it| it.syntax_node_ptr().to_node(file.syntax()), + ); + assert_eq!(local_name.text_range(), expected_name.syntax().text_range()); + } + + #[test] + fn test_resolve_local_name() { + do_check_local_name( + r#" +fn foo(x: i32, y: u32) { + { + let z = x * 2; + } + { + let t = x$0 * 3; + } +} +"#, + 7, + ); + } + + #[test] + fn test_resolve_local_name_declaration() { + do_check_local_name( + r#" +fn foo(x: String) { + let x : &str = &x$0; +} +"#, + 7, + ); + } + + #[test] + fn test_resolve_local_name_shadow() { + do_check_local_name( + r" +fn foo(x: String) { + let x : &str = &x; + x$0 +} +", + 28, + ); + } + + #[test] + fn ref_patterns_contribute_bindings() { + do_check_local_name( + r" +fn foo() { + if let Some(&from) = bar() { + from$0; + } +} +", + 28, + ); + } + + #[test] + fn while_let_adds_binding() { + do_check_local_name( + r#" +fn test() { + let foo: Option<f32> = None; + while let Option::Some(spam) = foo { + spam$0 + } +} +"#, + 75, + ); + do_check_local_name( + r#" +fn test() { + let foo: Option<f32> = None; + while (((let Option::Some(_) = foo))) && let Option::Some(spam) = foo { + spam$0 + } +} +"#, + 107, + ); + } + + #[test] + fn match_guard_if_let() { + do_check_local_name( + r#" +fn test() { + let foo: Option<f32> = None; + match foo { + _ if let Option::Some(spam) = foo => spam$0, + } +} +"#, + 93, + ); + } + + #[test] + fn let_chains_can_reference_previous_lets() { + do_check_local_name( + r#" +fn test() { + let foo: Option<i32> = None; + if let Some(spam) = foo && spa$0m > 1 && let Some(spam) = foo && spam > 1 {} +} +"#, + 61, + ); + do_check_local_name( + r#" +fn test() { + let foo: Option<i32> = None; + if let Some(spam) = foo && spam > 1 && let Some(spam) = foo && sp$0am > 1 {} +} +"#, + 100, + ); + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/tests.rs new file mode 100644 index 000000000..c9601f855 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/body/tests.rs @@ -0,0 +1,127 @@ +mod block; + +use base_db::{fixture::WithFixture, SourceDatabase}; +use expect_test::Expect; + +use crate::ModuleDefId; + +use super::*; + +fn lower(ra_fixture: &str) -> Arc<Body> { + let db = crate::test_db::TestDB::with_files(ra_fixture); + + let krate = db.crate_graph().iter().next().unwrap(); + let def_map = db.crate_def_map(krate); + let mut fn_def = None; + 'outer: for (_, module) in def_map.modules() { + for decl in module.scope.declarations() { + if let ModuleDefId::FunctionId(it) = decl { + fn_def = Some(it); + break 'outer; + } + } + } + + db.body(fn_def.unwrap().into()) +} + +fn block_def_map_at(ra_fixture: &str) -> String { + let (db, position) = crate::test_db::TestDB::with_position(ra_fixture); + + let module = db.module_at_position(position); + module.def_map(&db).dump(&db) +} + +fn check_block_scopes_at(ra_fixture: &str, expect: Expect) { + let (db, position) = crate::test_db::TestDB::with_position(ra_fixture); + + let module = db.module_at_position(position); + let actual = module.def_map(&db).dump_block_scopes(&db); + expect.assert_eq(&actual); +} + +fn check_at(ra_fixture: &str, expect: Expect) { + let actual = block_def_map_at(ra_fixture); + expect.assert_eq(&actual); +} + +#[test] +fn your_stack_belongs_to_me() { + cov_mark::check!(your_stack_belongs_to_me); + lower( + r#" +macro_rules! n_nuple { + ($e:tt) => (); + ($($rest:tt)*) => {{ + (n_nuple!($($rest)*)None,) + }}; +} +fn main() { n_nuple!(1,2,3); } +"#, + ); +} + +#[test] +fn recursion_limit() { + cov_mark::check!(your_stack_belongs_to_me); + + lower( + r#" +#![recursion_limit = "2"] +macro_rules! n_nuple { + ($e:tt) => (); + ($first:tt $($rest:tt)*) => {{ + n_nuple!($($rest)*) + }}; +} +fn main() { n_nuple!(1,2,3); } +"#, + ); +} + +#[test] +fn issue_3642_bad_macro_stackover() { + lower( + r#" +#[macro_export] +macro_rules! match_ast { + (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) }; + + (match ($node:expr) { + $( ast::$ast:ident($it:ident) => $res:expr, )* + _ => $catch_all:expr $(,)? + }) => {{ + $( if let Some($it) = ast::$ast::cast($node.clone()) { $res } else )* + { $catch_all } + }}; +} + +fn main() { + let anchor = match_ast! { + match parent { + as => {}, + _ => return None + } + }; +}"#, + ); +} + +#[test] +fn macro_resolve() { + // Regression test for a path resolution bug introduced with inner item handling. + lower( + r#" +macro_rules! vec { + () => { () }; + ($elem:expr; $n:expr) => { () }; + ($($x:expr),+ $(,)?) => { () }; +} +mod m { + fn outer() { + let _ = vec![FileSet::default(); self.len()]; + } +} +"#, + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs new file mode 100644 index 000000000..3bba08cfc --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs @@ -0,0 +1,397 @@ +use super::*; +use expect_test::expect; + +#[test] +fn inner_item_smoke() { + check_at( + r#" +struct inner {} +fn outer() { + $0 + fn inner() {} +} +"#, + expect![[r#" + block scope + inner: v + + crate + inner: t + outer: v + "#]], + ); +} + +#[test] +fn use_from_crate() { + check_at( + r#" +struct Struct {} +fn outer() { + fn Struct() {} + use Struct as PlainStruct; + use crate::Struct as CrateStruct; + use self::Struct as SelfStruct; + use super::Struct as SuperStruct; + $0 +} +"#, + expect![[r#" + block scope + CrateStruct: t + PlainStruct: t v + SelfStruct: t + Struct: v + SuperStruct: _ + + crate + Struct: t + outer: v + "#]], + ); +} + +#[test] +fn merge_namespaces() { + check_at( + r#" +struct name {} +fn outer() { + fn name() {} + + use name as imported; // should import both `name`s + + $0 +} +"#, + expect![[r#" + block scope + imported: t v + name: v + + crate + name: t + outer: v + "#]], + ); +} + +#[test] +fn nested_blocks() { + check_at( + r#" +fn outer() { + struct inner1 {} + fn inner() { + use inner1; + use outer; + fn inner2() {} + $0 + } +} +"#, + expect![[r#" + block scope + inner1: t + inner2: v + outer: v + + block scope + inner: v + inner1: t + + crate + outer: v + "#]], + ); +} + +#[test] +fn super_imports() { + check_at( + r#" +mod module { + fn f() { + use super::Struct; + $0 + } +} + +struct Struct {} +"#, + expect![[r#" + block scope + Struct: t + + crate + Struct: t + module: t + + crate::module + f: v + "#]], + ); +} + +#[test] +fn nested_module_scoping() { + check_block_scopes_at( + r#" +fn f() { + mod module { + struct Struct {} + fn f() { + use self::Struct; + $0 + } + } +} + "#, + expect![[r#" + BlockId(1) in ModuleId { krate: CrateId(0), block: Some(BlockId(0)), local_id: Idx::<ModuleData>(1) } + BlockId(0) in ModuleId { krate: CrateId(0), block: None, local_id: Idx::<ModuleData>(0) } + crate scope + "#]], + ); +} + +#[test] +fn legacy_macro_items() { + // Checks that legacy-scoped `macro_rules!` from parent namespaces are resolved and expanded + // correctly. + check_at( + r#" +macro_rules! mark { + () => { + struct Hit {} + } +} + +fn f() { + mark!(); + $0 +} +"#, + expect![[r#" + block scope + Hit: t + + crate + f: v + "#]], + ); +} + +#[test] +fn macro_resolve() { + check_at( + r#" +//- /lib.rs crate:lib deps:core +use core::cov_mark; + +fn f() { + fn nested() { + cov_mark::mark!(Hit); + $0 + } +} +//- /core.rs crate:core +pub mod cov_mark { + #[macro_export] + macro_rules! _mark { + ($name:ident) => { + struct $name {} + } + } + + pub use crate::_mark as mark; +} +"#, + expect![[r#" + block scope + Hit: t + + block scope + nested: v + + crate + cov_mark: t + f: v + "#]], + ); +} + +#[test] +fn macro_resolve_legacy() { + check_at( + r#" +//- /lib.rs +mod module; + +//- /module.rs +macro_rules! m { + () => { + struct Def {} + }; +} + +fn f() { + { + m!(); + $0 + } +} + "#, + expect![[r#" + block scope + Def: t + + crate + module: t + + crate::module + f: v + "#]], + ) +} + +#[test] +fn super_does_not_resolve_to_block_module() { + check_at( + r#" +fn main() { + struct Struct {} + mod module { + use super::Struct; + + $0 + } +} + "#, + expect![[r#" + block scope + Struct: t + module: t + + block scope::module + Struct: _ + + crate + main: v + "#]], + ); +} + +#[test] +fn underscore_import() { + // This used to panic, because the default (private) visibility inside block expressions would + // point into the containing `DefMap`, which visibilities should never be able to do. + cov_mark::check!(adjust_vis_in_block_def_map); + check_at( + r#" +mod m { + fn main() { + use Tr as _; + trait Tr {} + $0 + } +} + "#, + expect![[r#" + block scope + _: t + Tr: t + + crate + m: t + + crate::m + main: v + "#]], + ); +} + +#[test] +fn nested_macro_item_decl() { + cov_mark::check!(macro_call_in_macro_stmts_is_added_to_item_tree); + check_at( + r#" +macro_rules! inner_declare { + ($ident:ident) => { + static $ident: u32 = 0; + }; +} +macro_rules! declare { + ($ident:ident) => { + inner_declare!($ident); + }; +} + +fn foo() { + declare!(bar); + bar; + $0 +} + "#, + expect![[r#" + block scope + bar: v + + crate + foo: v + "#]], + ) +} + +#[test] +fn is_visible_from_same_def_map() { + // Regression test for https://github.com/rust-lang/rust-analyzer/issues/9481 + cov_mark::check!(is_visible_from_same_block_def_map); + check_at( + r#" +fn outer() { + mod tests { + use super::*; + } + use crate::name; + $0 +} + "#, + expect![[r#" + block scope + name: _ + tests: t + + block scope::tests + name: _ + outer: v + + crate + outer: v + "#]], + ); +} + +#[test] +fn stmt_macro_expansion_with_trailing_expr() { + cov_mark::check!(macro_stmt_with_trailing_macro_expr); + check_at( + r#" +macro_rules! mac { + () => { mac!($) }; + ($x:tt) => { fn inner() {} }; +} +fn foo() { + mac!(); + $0 +} + "#, + expect![[r#" + block scope + inner: v + + crate + foo: v + "#]], + ) +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/builtin_attr.rs b/src/tools/rust-analyzer/crates/hir-def/src/builtin_attr.rs new file mode 100644 index 000000000..0e7ce5f85 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/builtin_attr.rs @@ -0,0 +1,654 @@ +//! Builtin attributes resolved by nameres. +//! +//! The actual definitions were copied from rustc's `compiler/rustc_feature/src/builtin_attrs.rs`. +//! +//! It was last synchronized with upstream commit c1a2db3372a4d6896744919284f3287650a38ab7. +//! +//! The macros were adjusted to only expand to the attribute name, since that is all we need to do +//! name resolution, and `BUILTIN_ATTRIBUTES` is almost entirely unchanged from the original, to +//! ease updating. + +use once_cell::sync::OnceCell; +use rustc_hash::FxHashMap; + +/// Ignored attribute namespaces used by tools. +pub const TOOL_MODULES: &[&str] = &["rustfmt", "clippy"]; + +pub struct BuiltinAttribute { + pub name: &'static str, + pub template: AttributeTemplate, +} + +/// A template that the attribute input must match. +/// Only top-level shape (`#[attr]` vs `#[attr(...)]` vs `#[attr = ...]`) is considered now. +#[derive(Clone, Copy)] +pub struct AttributeTemplate { + pub word: bool, + pub list: Option<&'static str>, + pub name_value_str: Option<&'static str>, +} + +pub fn find_builtin_attr_idx(name: &str) -> Option<usize> { + static BUILTIN_LOOKUP_TABLE: OnceCell<FxHashMap<&'static str, usize>> = OnceCell::new(); + BUILTIN_LOOKUP_TABLE + .get_or_init(|| { + INERT_ATTRIBUTES.iter().map(|attr| attr.name).enumerate().map(|(a, b)| (b, a)).collect() + }) + .get(name) + .copied() +} + +// impl AttributeTemplate { +// const DEFAULT: AttributeTemplate = +// AttributeTemplate { word: false, list: None, name_value_str: None }; +// } + +/// A convenience macro for constructing attribute templates. +/// E.g., `template!(Word, List: "description")` means that the attribute +/// supports forms `#[attr]` and `#[attr(description)]`. +macro_rules! template { + (Word) => { template!(@ true, None, None) }; + (List: $descr: expr) => { template!(@ false, Some($descr), None) }; + (NameValueStr: $descr: expr) => { template!(@ false, None, Some($descr)) }; + (Word, List: $descr: expr) => { template!(@ true, Some($descr), None) }; + (Word, NameValueStr: $descr: expr) => { template!(@ true, None, Some($descr)) }; + (List: $descr1: expr, NameValueStr: $descr2: expr) => { + template!(@ false, Some($descr1), Some($descr2)) + }; + (Word, List: $descr1: expr, NameValueStr: $descr2: expr) => { + template!(@ true, Some($descr1), Some($descr2)) + }; + (@ $word: expr, $list: expr, $name_value_str: expr) => { + AttributeTemplate { + word: $word, list: $list, name_value_str: $name_value_str + } + }; +} + +macro_rules! ungated { + ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr $(, @only_local: $only_local:expr)? $(,)?) => { + BuiltinAttribute { name: stringify!($attr), template: $tpl } + }; +} + +macro_rules! gated { + ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr $(, @only_local: $only_local:expr)?, $gate:ident, $msg:expr $(,)?) => { + BuiltinAttribute { name: stringify!($attr), template: $tpl } + }; + ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr $(, @only_local: $only_local:expr)?, $msg:expr $(,)?) => { + BuiltinAttribute { name: stringify!($attr), template: $tpl } + }; +} + +macro_rules! rustc_attr { + (TEST, $attr:ident, $typ:expr, $tpl:expr, $duplicate:expr $(, @only_local: $only_local:expr)? $(,)?) => { + rustc_attr!( + $attr, + $typ, + $tpl, + $duplicate, + $(@only_local: $only_local,)? + concat!( + "the `#[", + stringify!($attr), + "]` attribute is just used for rustc unit tests \ + and will never be stable", + ), + ) + }; + ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr $(, @only_local: $only_local:expr)?, $msg:expr $(,)?) => { + BuiltinAttribute { name: stringify!($attr), template: $tpl } + }; +} + +#[allow(unused_macros)] +macro_rules! experimental { + ($attr:ident) => { + concat!("the `#[", stringify!($attr), "]` attribute is an experimental feature") + }; +} + +/// "Inert" built-in attributes that have a special meaning to rustc or rustdoc. +#[rustfmt::skip] +pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[ + // ========================================================================== + // Stable attributes: + // ========================================================================== + + // Conditional compilation: + ungated!(cfg, Normal, template!(List: "predicate"), DuplicatesOk), + ungated!(cfg_attr, Normal, template!(List: "predicate, attr1, attr2, ..."), DuplicatesOk), + + // Testing: + ungated!(ignore, Normal, template!(Word, NameValueStr: "reason"), WarnFollowing), + ungated!( + should_panic, Normal, + template!(Word, List: r#"expected = "reason"#, NameValueStr: "reason"), FutureWarnFollowing, + ), + // FIXME(Centril): This can be used on stable but shouldn't. + ungated!(reexport_test_harness_main, CrateLevel, template!(NameValueStr: "name"), ErrorFollowing), + + // Macros: + ungated!(automatically_derived, Normal, template!(Word), WarnFollowing), + ungated!(macro_use, Normal, template!(Word, List: "name1, name2, ..."), WarnFollowingWordOnly), + ungated!(macro_escape, Normal, template!(Word), WarnFollowing), // Deprecated synonym for `macro_use`. + ungated!(macro_export, Normal, template!(Word, List: "local_inner_macros"), WarnFollowing), + ungated!(proc_macro, Normal, template!(Word), ErrorFollowing), + ungated!( + proc_macro_derive, Normal, + template!(List: "TraitName, /*opt*/ attributes(name1, name2, ...)"), ErrorFollowing, + ), + ungated!(proc_macro_attribute, Normal, template!(Word), ErrorFollowing), + + // Lints: + ungated!( + warn, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), DuplicatesOk + ), + ungated!( + allow, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), DuplicatesOk + ), + gated!( + expect, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), DuplicatesOk, + lint_reasons, experimental!(expect) + ), + ungated!( + forbid, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), DuplicatesOk + ), + ungated!( + deny, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), DuplicatesOk + ), + ungated!(must_use, Normal, template!(Word, NameValueStr: "reason"), FutureWarnFollowing), + gated!( + must_not_suspend, Normal, template!(Word, NameValueStr: "reason"), WarnFollowing, + experimental!(must_not_suspend) + ), + ungated!( + deprecated, Normal, + template!( + Word, + List: r#"/*opt*/ since = "version", /*opt*/ note = "reason""#, + NameValueStr: "reason" + ), + ErrorFollowing + ), + + // Crate properties: + ungated!(crate_name, CrateLevel, template!(NameValueStr: "name"), FutureWarnFollowing), + ungated!(crate_type, CrateLevel, template!(NameValueStr: "bin|lib|..."), DuplicatesOk), + // crate_id is deprecated + ungated!(crate_id, CrateLevel, template!(NameValueStr: "ignored"), FutureWarnFollowing), + + // ABI, linking, symbols, and FFI + ungated!( + link, Normal, + template!(List: r#"name = "...", /*opt*/ kind = "dylib|static|...", /*opt*/ wasm_import_module = "...""#), + DuplicatesOk, + ), + ungated!(link_name, Normal, template!(NameValueStr: "name"), FutureWarnPreceding), + ungated!(no_link, Normal, template!(Word), WarnFollowing), + ungated!(repr, Normal, template!(List: "C"), DuplicatesOk), + ungated!(export_name, Normal, template!(NameValueStr: "name"), FutureWarnPreceding), + ungated!(link_section, Normal, template!(NameValueStr: "name"), FutureWarnPreceding), + ungated!(no_mangle, Normal, template!(Word), WarnFollowing, @only_local: true), + ungated!(used, Normal, template!(Word, List: "compiler|linker"), WarnFollowing, @only_local: true), + + // Limits: + ungated!(recursion_limit, CrateLevel, template!(NameValueStr: "N"), FutureWarnFollowing), + ungated!(type_length_limit, CrateLevel, template!(NameValueStr: "N"), FutureWarnFollowing), + gated!( + const_eval_limit, CrateLevel, template!(NameValueStr: "N"), ErrorFollowing, + const_eval_limit, experimental!(const_eval_limit) + ), + gated!( + move_size_limit, CrateLevel, template!(NameValueStr: "N"), ErrorFollowing, + large_assignments, experimental!(move_size_limit) + ), + + // Entry point: + ungated!(start, Normal, template!(Word), WarnFollowing), + ungated!(no_start, CrateLevel, template!(Word), WarnFollowing), + ungated!(no_main, CrateLevel, template!(Word), WarnFollowing), + + // Modules, prelude, and resolution: + ungated!(path, Normal, template!(NameValueStr: "file"), FutureWarnFollowing), + ungated!(no_std, CrateLevel, template!(Word), WarnFollowing), + ungated!(no_implicit_prelude, Normal, template!(Word), WarnFollowing), + ungated!(non_exhaustive, Normal, template!(Word), WarnFollowing), + + // Runtime + ungated!( + windows_subsystem, CrateLevel, + template!(NameValueStr: "windows|console"), FutureWarnFollowing + ), + ungated!(panic_handler, Normal, template!(Word), WarnFollowing), // RFC 2070 + + // Code generation: + ungated!(inline, Normal, template!(Word, List: "always|never"), FutureWarnFollowing, @only_local: true), + ungated!(cold, Normal, template!(Word), WarnFollowing, @only_local: true), + ungated!(no_builtins, CrateLevel, template!(Word), WarnFollowing), + ungated!(target_feature, Normal, template!(List: r#"enable = "name""#), DuplicatesOk), + ungated!(track_caller, Normal, template!(Word), WarnFollowing), + gated!( + no_sanitize, Normal, + template!(List: "address, memory, thread"), DuplicatesOk, + experimental!(no_sanitize) + ), + gated!(no_coverage, Normal, template!(Word), WarnFollowing, experimental!(no_coverage)), + + ungated!( + doc, Normal, template!(List: "hidden|inline|...", NameValueStr: "string"), DuplicatesOk + ), + + // ========================================================================== + // Unstable attributes: + // ========================================================================== + + // RFC #3191: #[debugger_visualizer] support + gated!( + debugger_visualizer, Normal, template!(List: r#"natvis_file = "...", gdb_script_file = "...""#), + DuplicatesOk, experimental!(debugger_visualizer) + ), + + // Linking: + gated!(naked, Normal, template!(Word), WarnFollowing, @only_local: true, naked_functions, experimental!(naked)), + gated!( + link_ordinal, Normal, template!(List: "ordinal"), ErrorPreceding, raw_dylib, + experimental!(link_ordinal) + ), + + // Plugins: + // XXX Modified for use in rust-analyzer + // BuiltinAttribute { + // name: sym::plugin, + // only_local: false, + // type_: CrateLevel, + // template: template!(List: "name"), + // duplicates: DuplicatesOk, + // gate: Gated( + // Stability::Deprecated( + // "https://github.com/rust-lang/rust/pull/64675", + // Some("may be removed in a future compiler version"), + // ), + // sym::plugin, + // "compiler plugins are deprecated", + // cfg_fn!(plugin) + // ), + // }, + BuiltinAttribute { + name: "plugin", + template: template!(List: "name"), + }, + + // Testing: + gated!( + test_runner, CrateLevel, template!(List: "path"), ErrorFollowing, custom_test_frameworks, + "custom test frameworks are an unstable feature", + ), + // RFC #1268 + gated!( + marker, Normal, template!(Word), WarnFollowing, marker_trait_attr, experimental!(marker) + ), + gated!( + thread_local, Normal, template!(Word), WarnFollowing, + "`#[thread_local]` is an experimental feature, and does not currently handle destructors", + ), + gated!(no_core, CrateLevel, template!(Word), WarnFollowing, experimental!(no_core)), + // RFC 2412 + gated!( + optimize, Normal, template!(List: "size|speed"), ErrorPreceding, optimize_attribute, + experimental!(optimize), + ), + // RFC 2867 + gated!( + instruction_set, Normal, template!(List: "set"), ErrorPreceding, + isa_attribute, experimental!(instruction_set) + ), + + gated!( + ffi_returns_twice, Normal, template!(Word), WarnFollowing, experimental!(ffi_returns_twice) + ), + gated!(ffi_pure, Normal, template!(Word), WarnFollowing, experimental!(ffi_pure)), + gated!(ffi_const, Normal, template!(Word), WarnFollowing, experimental!(ffi_const)), + gated!( + register_attr, CrateLevel, template!(List: "attr1, attr2, ..."), DuplicatesOk, + experimental!(register_attr), + ), + gated!( + register_tool, CrateLevel, template!(List: "tool1, tool2, ..."), DuplicatesOk, + experimental!(register_tool), + ), + + gated!( + cmse_nonsecure_entry, Normal, template!(Word), WarnFollowing, + experimental!(cmse_nonsecure_entry) + ), + // RFC 2632 + gated!( + const_trait, Normal, template!(Word), WarnFollowing, const_trait_impl, + "`const` is a temporary placeholder for marking a trait that is suitable for `const` \ + `impls` and all default bodies as `const`, which may be removed or renamed in the \ + future." + ), + // lang-team MCP 147 + gated!( + deprecated_safe, Normal, template!(List: r#"since = "version", note = "...""#), ErrorFollowing, + experimental!(deprecated_safe), + ), + + // ========================================================================== + // Internal attributes: Stability, deprecation, and unsafe: + // ========================================================================== + + ungated!(feature, CrateLevel, template!(List: "name1, name2, ..."), DuplicatesOk), + // DuplicatesOk since it has its own validation + ungated!( + stable, Normal, template!(List: r#"feature = "name", since = "version""#), DuplicatesOk, + ), + ungated!( + unstable, Normal, + template!(List: r#"feature = "name", reason = "...", issue = "N""#), DuplicatesOk, + ), + ungated!(rustc_const_unstable, Normal, template!(List: r#"feature = "name""#), DuplicatesOk), + ungated!(rustc_const_stable, Normal, template!(List: r#"feature = "name""#), DuplicatesOk), + gated!( + allow_internal_unstable, Normal, template!(Word, List: "feat1, feat2, ..."), DuplicatesOk, + "allow_internal_unstable side-steps feature gating and stability checks", + ), + gated!( + rustc_allow_const_fn_unstable, Normal, + template!(Word, List: "feat1, feat2, ..."), DuplicatesOk, + "rustc_allow_const_fn_unstable side-steps feature gating and stability checks" + ), + gated!( + allow_internal_unsafe, Normal, template!(Word), WarnFollowing, + "allow_internal_unsafe side-steps the unsafe_code lint", + ), + + // ========================================================================== + // Internal attributes: Type system related: + // ========================================================================== + + gated!(fundamental, Normal, template!(Word), WarnFollowing, experimental!(fundamental)), + gated!( + may_dangle, Normal, template!(Word), WarnFollowing, dropck_eyepatch, + "`may_dangle` has unstable semantics and may be removed in the future", + ), + + // ========================================================================== + // Internal attributes: Runtime related: + // ========================================================================== + + rustc_attr!(rustc_allocator, Normal, template!(Word), WarnFollowing, IMPL_DETAIL), + rustc_attr!(rustc_allocator_nounwind, Normal, template!(Word), WarnFollowing, IMPL_DETAIL), + gated!( + alloc_error_handler, Normal, template!(Word), WarnFollowing, + experimental!(alloc_error_handler) + ), + gated!( + default_lib_allocator, Normal, template!(Word), WarnFollowing, allocator_internals, + experimental!(default_lib_allocator), + ), + gated!( + needs_allocator, Normal, template!(Word), WarnFollowing, allocator_internals, + experimental!(needs_allocator), + ), + gated!(panic_runtime, Normal, template!(Word), WarnFollowing, experimental!(panic_runtime)), + gated!( + needs_panic_runtime, Normal, template!(Word), WarnFollowing, + experimental!(needs_panic_runtime) + ), + gated!( + compiler_builtins, Normal, template!(Word), WarnFollowing, + "the `#[compiler_builtins]` attribute is used to identify the `compiler_builtins` crate \ + which contains compiler-rt intrinsics and will never be stable", + ), + gated!( + profiler_runtime, Normal, template!(Word), WarnFollowing, + "the `#[profiler_runtime]` attribute is used to identify the `profiler_builtins` crate \ + which contains the profiler runtime and will never be stable", + ), + + // ========================================================================== + // Internal attributes, Linkage: + // ========================================================================== + + gated!( + linkage, Normal, template!(NameValueStr: "external|internal|..."), ErrorPreceding, @only_local: true, + "the `linkage` attribute is experimental and not portable across platforms", + ), + rustc_attr!( + rustc_std_internal_symbol, Normal, template!(Word), WarnFollowing, @only_local: true, INTERNAL_UNSTABLE + ), + + // ========================================================================== + // Internal attributes, Macro related: + // ========================================================================== + + rustc_attr!( + rustc_builtin_macro, Normal, + template!(Word, List: "name, /*opt*/ attributes(name1, name2, ...)"), ErrorFollowing, + IMPL_DETAIL, + ), + rustc_attr!(rustc_proc_macro_decls, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE), + rustc_attr!( + rustc_macro_transparency, Normal, + template!(NameValueStr: "transparent|semitransparent|opaque"), ErrorFollowing, + "used internally for testing macro hygiene", + ), + + // ========================================================================== + // Internal attributes, Diagnostics related: + // ========================================================================== + + rustc_attr!( + rustc_on_unimplemented, Normal, + template!( + List: r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#, + NameValueStr: "message" + ), + ErrorFollowing, + INTERNAL_UNSTABLE + ), + // Enumerates "identity-like" conversion methods to suggest on type mismatch. + rustc_attr!( + rustc_conversion_suggestion, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE + ), + // Prevents field reads in the marked trait or method to be considered + // during dead code analysis. + rustc_attr!( + rustc_trivial_field_reads, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE + ), + // Used by the `rustc::potential_query_instability` lint to warn methods which + // might not be stable during incremental compilation. + rustc_attr!(rustc_lint_query_instability, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE), + // Used by the `rustc::untranslatable_diagnostic` and `rustc::diagnostic_outside_of_impl` lints + // to assist in changes to diagnostic APIs. + rustc_attr!(rustc_lint_diagnostics, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE), + + // ========================================================================== + // Internal attributes, Const related: + // ========================================================================== + + rustc_attr!(rustc_promotable, Normal, template!(Word), WarnFollowing, IMPL_DETAIL), + rustc_attr!( + rustc_legacy_const_generics, Normal, template!(List: "N"), ErrorFollowing, + INTERNAL_UNSTABLE + ), + // Do not const-check this function's body. It will always get replaced during CTFE. + rustc_attr!( + rustc_do_not_const_check, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE + ), + + // ========================================================================== + // Internal attributes, Layout related: + // ========================================================================== + + rustc_attr!( + rustc_layout_scalar_valid_range_start, Normal, template!(List: "value"), ErrorFollowing, + "the `#[rustc_layout_scalar_valid_range_start]` attribute is just used to enable \ + niche optimizations in libcore and libstd and will never be stable", + ), + rustc_attr!( + rustc_layout_scalar_valid_range_end, Normal, template!(List: "value"), ErrorFollowing, + "the `#[rustc_layout_scalar_valid_range_end]` attribute is just used to enable \ + niche optimizations in libcore and libstd and will never be stable", + ), + rustc_attr!( + rustc_nonnull_optimization_guaranteed, Normal, template!(Word), WarnFollowing, + "the `#[rustc_nonnull_optimization_guaranteed]` attribute is just used to enable \ + niche optimizations in libcore and libstd and will never be stable", + ), + + // ========================================================================== + // Internal attributes, Misc: + // ========================================================================== + gated!( + lang, Normal, template!(NameValueStr: "name"), DuplicatesOk, @only_local: true, lang_items, + "language items are subject to change", + ), + rustc_attr!( + rustc_pass_by_value, Normal, + template!(Word), ErrorFollowing, + "#[rustc_pass_by_value] is used to mark types that must be passed by value instead of reference." + ), + rustc_attr!( + rustc_coherence_is_core, AttributeType::CrateLevel, template!(Word), ErrorFollowing, @only_local: true, + "#![rustc_coherence_is_core] allows inherent methods on builtin types, only intended to be used in `core`." + ), + rustc_attr!( + rustc_allow_incoherent_impl, AttributeType::Normal, template!(Word), ErrorFollowing, @only_local: true, + "#[rustc_allow_incoherent_impl] has to be added to all impl items of an incoherent inherent impl." + ), + rustc_attr!( + rustc_has_incoherent_inherent_impls, AttributeType::Normal, template!(Word), ErrorFollowing, + "#[rustc_has_incoherent_inherent_impls] allows the addition of incoherent inherent impls for \ + the given type by annotating all impl items with #[rustc_allow_incoherent_impl]." + ), + rustc_attr!( + rustc_box, AttributeType::Normal, template!(Word), ErrorFollowing, + "#[rustc_box] allows creating boxes \ + and it is only intended to be used in `alloc`." + ), + + // modified for r-a + // BuiltinAttribute { + // name: sym::rustc_diagnostic_item, + // // FIXME: This can be `true` once we always use `tcx.is_diagnostic_item`. + // only_local: false, + // type_: Normal, + // template: template!(NameValueStr: "name"), + // duplicates: ErrorFollowing, + // gate: Gated( + // Stability::Unstable, + // sym::rustc_attrs, + // "diagnostic items compiler internal support for linting", + // cfg_fn!(rustc_attrs), + // ), + // }, + BuiltinAttribute { + name: "rustc_diagnostic_item", + template: template!(NameValueStr: "name"), + }, + gated!( + // Used in resolve: + prelude_import, Normal, template!(Word), WarnFollowing, + "`#[prelude_import]` is for use by rustc only", + ), + gated!( + rustc_paren_sugar, Normal, template!(Word), WarnFollowing, unboxed_closures, + "unboxed_closures are still evolving", + ), + rustc_attr!( + rustc_inherit_overflow_checks, Normal, template!(Word), WarnFollowing, @only_local: true, + "the `#[rustc_inherit_overflow_checks]` attribute is just used to control \ + overflow checking behavior of several libcore functions that are inlined \ + across crates and will never be stable", + ), + rustc_attr!( + rustc_reservation_impl, Normal, + template!(NameValueStr: "reservation message"), ErrorFollowing, + "the `#[rustc_reservation_impl]` attribute is internally used \ + for reserving for `for<T> From<!> for T` impl" + ), + rustc_attr!( + rustc_test_marker, Normal, template!(Word), WarnFollowing, + "the `#[rustc_test_marker]` attribute is used internally to track tests", + ), + rustc_attr!( + rustc_unsafe_specialization_marker, Normal, template!(Word), WarnFollowing, + "the `#[rustc_unsafe_specialization_marker]` attribute is used to check specializations" + ), + rustc_attr!( + rustc_specialization_trait, Normal, template!(Word), WarnFollowing, + "the `#[rustc_specialization_trait]` attribute is used to check specializations" + ), + rustc_attr!( + rustc_main, Normal, template!(Word), WarnFollowing, + "the `#[rustc_main]` attribute is used internally to specify test entry point function", + ), + rustc_attr!( + rustc_skip_array_during_method_dispatch, Normal, template!(Word), WarnFollowing, + "the `#[rustc_skip_array_during_method_dispatch]` attribute is used to exclude a trait \ + from method dispatch when the receiver is an array, for compatibility in editions < 2021." + ), + rustc_attr!( + rustc_must_implement_one_of, Normal, template!(List: "function1, function2, ..."), ErrorFollowing, + "the `#[rustc_must_implement_one_of]` attribute is used to change minimal complete \ + definition of a trait, it's currently in experimental form and should be changed before \ + being exposed outside of the std" + ), + + // ========================================================================== + // Internal attributes, Testing: + // ========================================================================== + + rustc_attr!(TEST, rustc_outlives, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_capture_analysis, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_insignificant_dtor, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_strict_coherence, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_variance, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_layout, Normal, template!(List: "field1, field2, ..."), WarnFollowing), + rustc_attr!(TEST, rustc_regions, Normal, template!(Word), WarnFollowing), + rustc_attr!( + TEST, rustc_error, Normal, + template!(Word, List: "delay_span_bug_from_inside_query"), WarnFollowingWordOnly + ), + rustc_attr!(TEST, rustc_dump_user_substs, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_evaluate_where_clauses, Normal, template!(Word), WarnFollowing), + rustc_attr!( + TEST, rustc_if_this_changed, Normal, template!(Word, List: "DepNode"), DuplicatesOk + ), + rustc_attr!( + TEST, rustc_then_this_would_need, Normal, template!(List: "DepNode"), DuplicatesOk + ), + rustc_attr!( + TEST, rustc_clean, Normal, + template!(List: r#"cfg = "...", /*opt*/ label = "...", /*opt*/ except = "...""#), + DuplicatesOk, + ), + rustc_attr!( + TEST, rustc_partition_reused, Normal, + template!(List: r#"cfg = "...", module = "...""#), DuplicatesOk, + ), + rustc_attr!( + TEST, rustc_partition_codegened, Normal, + template!(List: r#"cfg = "...", module = "...""#), DuplicatesOk, + ), + rustc_attr!( + TEST, rustc_expected_cgu_reuse, Normal, + template!(List: r#"cfg = "...", module = "...", kind = "...""#), DuplicatesOk, + ), + rustc_attr!(TEST, rustc_symbol_name, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_polymorphize_error, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_def_path, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_mir, Normal, template!(List: "arg1, arg2, ..."), DuplicatesOk), + rustc_attr!(TEST, rustc_dump_program_clauses, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_dump_env_program_clauses, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_object_lifetime_default, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_dump_vtable, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_dummy, Normal, template!(Word /* doesn't matter*/), DuplicatesOk), + gated!( + omit_gdb_pretty_printer_section, Normal, template!(Word), WarnFollowing, + "the `#[omit_gdb_pretty_printer_section]` attribute is just used for the Rust test suite", + ), +]; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/builtin_type.rs b/src/tools/rust-analyzer/crates/hir-def/src/builtin_type.rs new file mode 100644 index 000000000..25a408036 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/builtin_type.rs @@ -0,0 +1,158 @@ +//! This module defines built-in types. +//! +//! A peculiarity of built-in types is that they are always available and are +//! not associated with any particular crate. + +use std::fmt; + +use hir_expand::name::{name, AsName, Name}; +/// Different signed int types. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum BuiltinInt { + Isize, + I8, + I16, + I32, + I64, + I128, +} + +/// Different unsigned int types. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum BuiltinUint { + Usize, + U8, + U16, + U32, + U64, + U128, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum BuiltinFloat { + F32, + F64, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum BuiltinType { + Char, + Bool, + Str, + Int(BuiltinInt), + Uint(BuiltinUint), + Float(BuiltinFloat), +} + +impl BuiltinType { + #[rustfmt::skip] + pub const ALL: &'static [(Name, BuiltinType)] = &[ + (name![char], BuiltinType::Char), + (name![bool], BuiltinType::Bool), + (name![str], BuiltinType::Str), + + (name![isize], BuiltinType::Int(BuiltinInt::Isize)), + (name![i8], BuiltinType::Int(BuiltinInt::I8)), + (name![i16], BuiltinType::Int(BuiltinInt::I16)), + (name![i32], BuiltinType::Int(BuiltinInt::I32)), + (name![i64], BuiltinType::Int(BuiltinInt::I64)), + (name![i128], BuiltinType::Int(BuiltinInt::I128)), + + (name![usize], BuiltinType::Uint(BuiltinUint::Usize)), + (name![u8], BuiltinType::Uint(BuiltinUint::U8)), + (name![u16], BuiltinType::Uint(BuiltinUint::U16)), + (name![u32], BuiltinType::Uint(BuiltinUint::U32)), + (name![u64], BuiltinType::Uint(BuiltinUint::U64)), + (name![u128], BuiltinType::Uint(BuiltinUint::U128)), + + (name![f32], BuiltinType::Float(BuiltinFloat::F32)), + (name![f64], BuiltinType::Float(BuiltinFloat::F64)), + ]; + + pub fn by_name(name: &Name) -> Option<Self> { + Self::ALL.iter().find_map(|(n, ty)| if n == name { Some(*ty) } else { None }) + } +} + +impl AsName for BuiltinType { + fn as_name(&self) -> Name { + match self { + BuiltinType::Char => name![char], + BuiltinType::Bool => name![bool], + BuiltinType::Str => name![str], + BuiltinType::Int(it) => match it { + BuiltinInt::Isize => name![isize], + BuiltinInt::I8 => name![i8], + BuiltinInt::I16 => name![i16], + BuiltinInt::I32 => name![i32], + BuiltinInt::I64 => name![i64], + BuiltinInt::I128 => name![i128], + }, + BuiltinType::Uint(it) => match it { + BuiltinUint::Usize => name![usize], + BuiltinUint::U8 => name![u8], + BuiltinUint::U16 => name![u16], + BuiltinUint::U32 => name![u32], + BuiltinUint::U64 => name![u64], + BuiltinUint::U128 => name![u128], + }, + BuiltinType::Float(it) => match it { + BuiltinFloat::F32 => name![f32], + BuiltinFloat::F64 => name![f64], + }, + } + } +} + +impl fmt::Display for BuiltinType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let type_name = self.as_name(); + type_name.fmt(f) + } +} + +#[rustfmt::skip] +impl BuiltinInt { + pub fn from_suffix(suffix: &str) -> Option<BuiltinInt> { + let res = match suffix { + "isize" => Self::Isize, + "i8" => Self::I8, + "i16" => Self::I16, + "i32" => Self::I32, + "i64" => Self::I64, + "i128" => Self::I128, + + _ => return None, + }; + Some(res) + } +} + +#[rustfmt::skip] +impl BuiltinUint { + pub fn from_suffix(suffix: &str) -> Option<BuiltinUint> { + let res = match suffix { + "usize" => Self::Usize, + "u8" => Self::U8, + "u16" => Self::U16, + "u32" => Self::U32, + "u64" => Self::U64, + "u128" => Self::U128, + + _ => return None, + }; + Some(res) + } +} + +#[rustfmt::skip] +impl BuiltinFloat { + pub fn from_suffix(suffix: &str) -> Option<BuiltinFloat> { + let res = match suffix { + "f32" => BuiltinFloat::F32, + "f64" => BuiltinFloat::F64, + _ => return None, + }; + Some(res) + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs b/src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs new file mode 100644 index 000000000..5b1435e8f --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs @@ -0,0 +1,207 @@ +//! When *constructing* `hir`, we start at some parent syntax node and recursively +//! lower the children. +//! +//! This modules allows one to go in the opposite direction: start with a syntax +//! node for a *child*, and get its hir. + +use either::Either; +use hir_expand::HirFileId; +use syntax::ast::HasDocComments; + +use crate::{ + db::DefDatabase, + dyn_map::DynMap, + item_scope::ItemScope, + keys, + src::{HasChildSource, HasSource}, + AdtId, AssocItemId, DefWithBodyId, EnumId, EnumVariantId, FieldId, ImplId, Lookup, MacroId, + ModuleDefId, ModuleId, TraitId, VariantId, +}; + +pub trait ChildBySource { + fn child_by_source(&self, db: &dyn DefDatabase, file_id: HirFileId) -> DynMap { + let mut res = DynMap::default(); + self.child_by_source_to(db, &mut res, file_id); + res + } + fn child_by_source_to(&self, db: &dyn DefDatabase, map: &mut DynMap, file_id: HirFileId); +} + +impl ChildBySource for TraitId { + fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) { + let data = db.trait_data(*self); + + data.attribute_calls().filter(|(ast_id, _)| ast_id.file_id == file_id).for_each( + |(ast_id, call_id)| { + res[keys::ATTR_MACRO_CALL].insert(ast_id.to_node(db.upcast()), call_id); + }, + ); + data.items.iter().for_each(|&(_, item)| { + add_assoc_item(db, res, file_id, item); + }); + } +} + +impl ChildBySource for ImplId { + fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) { + let data = db.impl_data(*self); + data.attribute_calls().filter(|(ast_id, _)| ast_id.file_id == file_id).for_each( + |(ast_id, call_id)| { + res[keys::ATTR_MACRO_CALL].insert(ast_id.to_node(db.upcast()), call_id); + }, + ); + data.items.iter().for_each(|&item| { + add_assoc_item(db, res, file_id, item); + }); + } +} + +fn add_assoc_item(db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId, item: AssocItemId) { + match item { + AssocItemId::FunctionId(func) => { + let loc = func.lookup(db); + if loc.id.file_id() == file_id { + res[keys::FUNCTION].insert(loc.source(db).value, func) + } + } + AssocItemId::ConstId(konst) => { + let loc = konst.lookup(db); + if loc.id.file_id() == file_id { + res[keys::CONST].insert(loc.source(db).value, konst) + } + } + AssocItemId::TypeAliasId(ty) => { + let loc = ty.lookup(db); + if loc.id.file_id() == file_id { + res[keys::TYPE_ALIAS].insert(loc.source(db).value, ty) + } + } + } +} + +impl ChildBySource for ModuleId { + fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) { + let def_map = self.def_map(db); + let module_data = &def_map[self.local_id]; + module_data.scope.child_by_source_to(db, res, file_id); + } +} + +impl ChildBySource for ItemScope { + fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) { + self.declarations().for_each(|item| add_module_def(db, res, file_id, item)); + self.impls().for_each(|imp| add_impl(db, res, file_id, imp)); + self.unnamed_consts().for_each(|konst| { + let loc = konst.lookup(db); + if loc.id.file_id() == file_id { + res[keys::CONST].insert(loc.source(db).value, konst); + } + }); + self.attr_macro_invocs().filter(|(id, _)| id.file_id == file_id).for_each( + |(ast_id, call_id)| { + res[keys::ATTR_MACRO_CALL].insert(ast_id.to_node(db.upcast()), call_id); + }, + ); + self.legacy_macros().for_each(|(_, ids)| { + ids.iter().for_each(|&id| { + if let MacroId::MacroRulesId(id) = id { + let loc = id.lookup(db); + if loc.id.file_id() == file_id { + res[keys::MACRO_RULES].insert(loc.source(db).value, id); + } + } + }) + }); + self.derive_macro_invocs().filter(|(id, _)| id.file_id == file_id).for_each( + |(ast_id, calls)| { + let adt = ast_id.to_node(db.upcast()); + calls.for_each(|(attr_id, call_id, calls)| { + if let Some(Either::Left(attr)) = + adt.doc_comments_and_attrs().nth(attr_id.ast_index as usize) + { + res[keys::DERIVE_MACRO_CALL].insert(attr, (attr_id, call_id, calls.into())); + } + }); + }, + ); + + fn add_module_def( + db: &dyn DefDatabase, + map: &mut DynMap, + file_id: HirFileId, + item: ModuleDefId, + ) { + macro_rules! insert { + ($map:ident[$key:path].$insert:ident($id:ident)) => {{ + let loc = $id.lookup(db); + if loc.id.file_id() == file_id { + $map[$key].$insert(loc.source(db).value, $id) + } + }}; + } + match item { + ModuleDefId::FunctionId(id) => insert!(map[keys::FUNCTION].insert(id)), + ModuleDefId::ConstId(id) => insert!(map[keys::CONST].insert(id)), + ModuleDefId::StaticId(id) => insert!(map[keys::STATIC].insert(id)), + ModuleDefId::TypeAliasId(id) => insert!(map[keys::TYPE_ALIAS].insert(id)), + ModuleDefId::TraitId(id) => insert!(map[keys::TRAIT].insert(id)), + ModuleDefId::AdtId(adt) => match adt { + AdtId::StructId(id) => insert!(map[keys::STRUCT].insert(id)), + AdtId::UnionId(id) => insert!(map[keys::UNION].insert(id)), + AdtId::EnumId(id) => insert!(map[keys::ENUM].insert(id)), + }, + ModuleDefId::MacroId(id) => match id { + MacroId::Macro2Id(id) => insert!(map[keys::MACRO2].insert(id)), + MacroId::MacroRulesId(id) => insert!(map[keys::MACRO_RULES].insert(id)), + MacroId::ProcMacroId(id) => insert!(map[keys::PROC_MACRO].insert(id)), + }, + ModuleDefId::ModuleId(_) + | ModuleDefId::EnumVariantId(_) + | ModuleDefId::BuiltinType(_) => (), + } + } + fn add_impl(db: &dyn DefDatabase, map: &mut DynMap, file_id: HirFileId, imp: ImplId) { + let loc = imp.lookup(db); + if loc.id.file_id() == file_id { + map[keys::IMPL].insert(loc.source(db).value, imp) + } + } + } +} + +impl ChildBySource for VariantId { + fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, _: HirFileId) { + let arena_map = self.child_source(db); + let arena_map = arena_map.as_ref(); + let parent = *self; + for (local_id, source) in arena_map.value.iter() { + let id = FieldId { parent, local_id }; + match source.clone() { + Either::Left(source) => res[keys::TUPLE_FIELD].insert(source, id), + Either::Right(source) => res[keys::RECORD_FIELD].insert(source, id), + } + } + } +} + +impl ChildBySource for EnumId { + fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, _: HirFileId) { + let arena_map = self.child_source(db); + let arena_map = arena_map.as_ref(); + for (local_id, source) in arena_map.value.iter() { + let id = EnumVariantId { parent: *self, local_id }; + res[keys::VARIANT].insert(source.clone(), id) + } + } +} + +impl ChildBySource for DefWithBodyId { + fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) { + let body = db.body(*self); + for (_, def_map) in body.blocks(db) { + // All block expressions are merged into the same map, because they logically all add + // inner items to the containing `DefWithBodyId`. + def_map[def_map.root()].scope.child_by_source_to(db, res, file_id); + } + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/data.rs b/src/tools/rust-analyzer/crates/hir-def/src/data.rs new file mode 100644 index 000000000..35c870895 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/data.rs @@ -0,0 +1,579 @@ +//! Contains basic data about various HIR declarations. + +use std::sync::Arc; + +use hir_expand::{name::Name, AstId, ExpandResult, HirFileId, MacroCallId, MacroDefKind}; +use smallvec::SmallVec; +use syntax::ast; + +use crate::{ + attr::Attrs, + body::{Expander, Mark}, + db::DefDatabase, + intern::Interned, + item_tree::{self, AssocItem, FnFlags, ItemTree, ItemTreeId, ModItem, Param, TreeId}, + nameres::{attr_resolution::ResolvedAttr, proc_macro::ProcMacroKind, DefMap}, + type_ref::{TraitRef, TypeBound, TypeRef}, + visibility::RawVisibility, + AssocItemId, AstIdWithPath, ConstId, ConstLoc, FunctionId, FunctionLoc, HasModule, ImplId, + Intern, ItemContainerId, ItemLoc, Lookup, Macro2Id, MacroRulesId, ModuleId, ProcMacroId, + StaticId, TraitId, TypeAliasId, TypeAliasLoc, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FunctionData { + pub name: Name, + pub params: Vec<(Option<Name>, Interned<TypeRef>)>, + pub ret_type: Interned<TypeRef>, + pub async_ret_type: Option<Interned<TypeRef>>, + pub attrs: Attrs, + pub visibility: RawVisibility, + pub abi: Option<Interned<str>>, + pub legacy_const_generics_indices: Box<[u32]>, + flags: FnFlags, +} + +impl FunctionData { + pub(crate) fn fn_data_query(db: &dyn DefDatabase, func: FunctionId) -> Arc<FunctionData> { + let loc = func.lookup(db); + let krate = loc.container.module(db).krate; + let crate_graph = db.crate_graph(); + let cfg_options = &crate_graph[krate].cfg_options; + let item_tree = loc.id.item_tree(db); + let func = &item_tree[loc.id.value]; + let visibility = if let ItemContainerId::TraitId(trait_id) = loc.container { + db.trait_data(trait_id).visibility.clone() + } else { + item_tree[func.visibility].clone() + }; + + let enabled_params = func + .params + .clone() + .filter(|¶m| item_tree.attrs(db, krate, param.into()).is_cfg_enabled(cfg_options)); + + // If last cfg-enabled param is a `...` param, it's a varargs function. + let is_varargs = enabled_params + .clone() + .next_back() + .map_or(false, |param| matches!(item_tree[param], Param::Varargs)); + + let mut flags = func.flags; + if is_varargs { + flags |= FnFlags::IS_VARARGS; + } + if flags.contains(FnFlags::HAS_SELF_PARAM) { + // If there's a self param in the syntax, but it is cfg'd out, remove the flag. + let is_cfgd_out = match func.params.clone().next() { + Some(param) => { + !item_tree.attrs(db, krate, param.into()).is_cfg_enabled(cfg_options) + } + None => { + stdx::never!("fn HAS_SELF_PARAM but no parameters allocated"); + true + } + }; + if is_cfgd_out { + cov_mark::hit!(cfgd_out_self_param); + flags.remove(FnFlags::HAS_SELF_PARAM); + } + } + + let legacy_const_generics_indices = item_tree + .attrs(db, krate, ModItem::from(loc.id.value).into()) + .by_key("rustc_legacy_const_generics") + .tt_values() + .next() + .map(parse_rustc_legacy_const_generics) + .unwrap_or_default(); + + Arc::new(FunctionData { + name: func.name.clone(), + params: enabled_params + .clone() + .filter_map(|id| match &item_tree[id] { + Param::Normal(name, ty) => Some((name.clone(), ty.clone())), + Param::Varargs => None, + }) + .collect(), + ret_type: func.ret_type.clone(), + async_ret_type: func.async_ret_type.clone(), + attrs: item_tree.attrs(db, krate, ModItem::from(loc.id.value).into()), + visibility, + abi: func.abi.clone(), + legacy_const_generics_indices, + flags, + }) + } + + pub fn has_body(&self) -> bool { + self.flags.contains(FnFlags::HAS_BODY) + } + + /// True if the first param is `self`. This is relevant to decide whether this + /// can be called as a method. + pub fn has_self_param(&self) -> bool { + self.flags.contains(FnFlags::HAS_SELF_PARAM) + } + + pub fn has_default_kw(&self) -> bool { + self.flags.contains(FnFlags::HAS_DEFAULT_KW) + } + + pub fn has_const_kw(&self) -> bool { + self.flags.contains(FnFlags::HAS_CONST_KW) + } + + pub fn has_async_kw(&self) -> bool { + self.flags.contains(FnFlags::HAS_ASYNC_KW) + } + + pub fn has_unsafe_kw(&self) -> bool { + self.flags.contains(FnFlags::HAS_UNSAFE_KW) + } + + pub fn is_varargs(&self) -> bool { + self.flags.contains(FnFlags::IS_VARARGS) + } +} + +fn parse_rustc_legacy_const_generics(tt: &tt::Subtree) -> Box<[u32]> { + let mut indices = Vec::new(); + for args in tt.token_trees.chunks(2) { + match &args[0] { + tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => match lit.text.parse() { + Ok(index) => indices.push(index), + Err(_) => break, + }, + _ => break, + } + + if let Some(comma) = args.get(1) { + match comma { + tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if punct.char == ',' => {} + _ => break, + } + } + } + + indices.into_boxed_slice() +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TypeAliasData { + pub name: Name, + pub type_ref: Option<Interned<TypeRef>>, + pub visibility: RawVisibility, + pub is_extern: bool, + /// Bounds restricting the type alias itself (eg. `type Ty: Bound;` in a trait or impl). + pub bounds: Vec<Interned<TypeBound>>, +} + +impl TypeAliasData { + pub(crate) fn type_alias_data_query( + db: &dyn DefDatabase, + typ: TypeAliasId, + ) -> Arc<TypeAliasData> { + let loc = typ.lookup(db); + let item_tree = loc.id.item_tree(db); + let typ = &item_tree[loc.id.value]; + let visibility = if let ItemContainerId::TraitId(trait_id) = loc.container { + db.trait_data(trait_id).visibility.clone() + } else { + item_tree[typ.visibility].clone() + }; + + Arc::new(TypeAliasData { + name: typ.name.clone(), + type_ref: typ.type_ref.clone(), + visibility, + is_extern: matches!(loc.container, ItemContainerId::ExternBlockId(_)), + bounds: typ.bounds.to_vec(), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TraitData { + pub name: Name, + pub items: Vec<(Name, AssocItemId)>, + pub is_auto: bool, + pub is_unsafe: bool, + pub visibility: RawVisibility, + /// Whether the trait has `#[rust_skip_array_during_method_dispatch]`. `hir_ty` will ignore + /// method calls to this trait's methods when the receiver is an array and the crate edition is + /// 2015 or 2018. + pub skip_array_during_method_dispatch: bool, + // box it as the vec is usually empty anyways + pub attribute_calls: Option<Box<Vec<(AstId<ast::Item>, MacroCallId)>>>, +} + +impl TraitData { + pub(crate) fn trait_data_query(db: &dyn DefDatabase, tr: TraitId) -> Arc<TraitData> { + let tr_loc @ ItemLoc { container: module_id, id: tree_id } = tr.lookup(db); + let item_tree = tree_id.item_tree(db); + let tr_def = &item_tree[tree_id.value]; + let _cx = stdx::panic_context::enter(format!( + "trait_data_query({:?} -> {:?} -> {:?})", + tr, tr_loc, tr_def + )); + let name = tr_def.name.clone(); + let is_auto = tr_def.is_auto; + let is_unsafe = tr_def.is_unsafe; + let visibility = item_tree[tr_def.visibility].clone(); + let skip_array_during_method_dispatch = item_tree + .attrs(db, module_id.krate(), ModItem::from(tree_id.value).into()) + .by_key("rustc_skip_array_during_method_dispatch") + .exists(); + + let mut collector = + AssocItemCollector::new(db, module_id, tree_id.file_id(), ItemContainerId::TraitId(tr)); + collector.collect(&item_tree, tree_id.tree_id(), &tr_def.items); + let (items, attribute_calls) = collector.finish(); + + Arc::new(TraitData { + name, + attribute_calls, + items, + is_auto, + is_unsafe, + visibility, + skip_array_during_method_dispatch, + }) + } + + pub fn associated_types(&self) -> impl Iterator<Item = TypeAliasId> + '_ { + self.items.iter().filter_map(|(_name, item)| match item { + AssocItemId::TypeAliasId(t) => Some(*t), + _ => None, + }) + } + + pub fn associated_type_by_name(&self, name: &Name) -> Option<TypeAliasId> { + self.items.iter().find_map(|(item_name, item)| match item { + AssocItemId::TypeAliasId(t) if item_name == name => Some(*t), + _ => None, + }) + } + + pub fn method_by_name(&self, name: &Name) -> Option<FunctionId> { + self.items.iter().find_map(|(item_name, item)| match item { + AssocItemId::FunctionId(t) if item_name == name => Some(*t), + _ => None, + }) + } + + pub fn attribute_calls(&self) -> impl Iterator<Item = (AstId<ast::Item>, MacroCallId)> + '_ { + self.attribute_calls.iter().flat_map(|it| it.iter()).copied() + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ImplData { + pub target_trait: Option<Interned<TraitRef>>, + pub self_ty: Interned<TypeRef>, + pub items: Vec<AssocItemId>, + pub is_negative: bool, + // box it as the vec is usually empty anyways + pub attribute_calls: Option<Box<Vec<(AstId<ast::Item>, MacroCallId)>>>, +} + +impl ImplData { + pub(crate) fn impl_data_query(db: &dyn DefDatabase, id: ImplId) -> Arc<ImplData> { + let _p = profile::span("impl_data_query"); + let ItemLoc { container: module_id, id: tree_id } = id.lookup(db); + + let item_tree = tree_id.item_tree(db); + let impl_def = &item_tree[tree_id.value]; + let target_trait = impl_def.target_trait.clone(); + let self_ty = impl_def.self_ty.clone(); + let is_negative = impl_def.is_negative; + + let mut collector = + AssocItemCollector::new(db, module_id, tree_id.file_id(), ItemContainerId::ImplId(id)); + collector.collect(&item_tree, tree_id.tree_id(), &impl_def.items); + + let (items, attribute_calls) = collector.finish(); + let items = items.into_iter().map(|(_, item)| item).collect(); + + Arc::new(ImplData { target_trait, self_ty, items, is_negative, attribute_calls }) + } + + pub fn attribute_calls(&self) -> impl Iterator<Item = (AstId<ast::Item>, MacroCallId)> + '_ { + self.attribute_calls.iter().flat_map(|it| it.iter()).copied() + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Macro2Data { + pub name: Name, + pub visibility: RawVisibility, +} + +impl Macro2Data { + pub(crate) fn macro2_data_query(db: &dyn DefDatabase, makro: Macro2Id) -> Arc<Macro2Data> { + let loc = makro.lookup(db); + let item_tree = loc.id.item_tree(db); + let makro = &item_tree[loc.id.value]; + + Arc::new(Macro2Data { + name: makro.name.clone(), + visibility: item_tree[makro.visibility].clone(), + }) + } +} +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MacroRulesData { + pub name: Name, + pub macro_export: bool, +} + +impl MacroRulesData { + pub(crate) fn macro_rules_data_query( + db: &dyn DefDatabase, + makro: MacroRulesId, + ) -> Arc<MacroRulesData> { + let loc = makro.lookup(db); + let item_tree = loc.id.item_tree(db); + let makro = &item_tree[loc.id.value]; + + let macro_export = item_tree + .attrs(db, loc.container.krate(), ModItem::from(loc.id.value).into()) + .by_key("macro_export") + .exists(); + + Arc::new(MacroRulesData { name: makro.name.clone(), macro_export }) + } +} +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ProcMacroData { + pub name: Name, + /// Derive helpers, if this is a derive + pub helpers: Option<Box<[Name]>>, +} + +impl ProcMacroData { + pub(crate) fn proc_macro_data_query( + db: &dyn DefDatabase, + makro: ProcMacroId, + ) -> Arc<ProcMacroData> { + let loc = makro.lookup(db); + let item_tree = loc.id.item_tree(db); + let makro = &item_tree[loc.id.value]; + + let (name, helpers) = if let Some(def) = item_tree + .attrs(db, loc.container.krate(), ModItem::from(loc.id.value).into()) + .parse_proc_macro_decl(&makro.name) + { + ( + def.name, + match def.kind { + ProcMacroKind::CustomDerive { helpers } => Some(helpers), + ProcMacroKind::FnLike | ProcMacroKind::Attr => None, + }, + ) + } else { + // eeeh... + stdx::never!("proc macro declaration is not a proc macro"); + (makro.name.clone(), None) + }; + Arc::new(ProcMacroData { name, helpers }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ConstData { + /// `None` for `const _: () = ();` + pub name: Option<Name>, + pub type_ref: Interned<TypeRef>, + pub visibility: RawVisibility, +} + +impl ConstData { + pub(crate) fn const_data_query(db: &dyn DefDatabase, konst: ConstId) -> Arc<ConstData> { + let loc = konst.lookup(db); + let item_tree = loc.id.item_tree(db); + let konst = &item_tree[loc.id.value]; + let visibility = if let ItemContainerId::TraitId(trait_id) = loc.container { + db.trait_data(trait_id).visibility.clone() + } else { + item_tree[konst.visibility].clone() + }; + + Arc::new(ConstData { + name: konst.name.clone(), + type_ref: konst.type_ref.clone(), + visibility, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StaticData { + pub name: Name, + pub type_ref: Interned<TypeRef>, + pub visibility: RawVisibility, + pub mutable: bool, + pub is_extern: bool, +} + +impl StaticData { + pub(crate) fn static_data_query(db: &dyn DefDatabase, konst: StaticId) -> Arc<StaticData> { + let loc = konst.lookup(db); + let item_tree = loc.id.item_tree(db); + let statik = &item_tree[loc.id.value]; + + Arc::new(StaticData { + name: statik.name.clone(), + type_ref: statik.type_ref.clone(), + visibility: item_tree[statik.visibility].clone(), + mutable: statik.mutable, + is_extern: matches!(loc.container, ItemContainerId::ExternBlockId(_)), + }) + } +} + +struct AssocItemCollector<'a> { + db: &'a dyn DefDatabase, + module_id: ModuleId, + def_map: Arc<DefMap>, + container: ItemContainerId, + expander: Expander, + + items: Vec<(Name, AssocItemId)>, + attr_calls: Vec<(AstId<ast::Item>, MacroCallId)>, +} + +impl<'a> AssocItemCollector<'a> { + fn new( + db: &'a dyn DefDatabase, + module_id: ModuleId, + file_id: HirFileId, + container: ItemContainerId, + ) -> Self { + Self { + db, + module_id, + def_map: module_id.def_map(db), + container, + expander: Expander::new(db, file_id, module_id), + items: Vec::new(), + attr_calls: Vec::new(), + } + } + + fn finish( + self, + ) -> (Vec<(Name, AssocItemId)>, Option<Box<Vec<(AstId<ast::Item>, MacroCallId)>>>) { + ( + self.items, + if self.attr_calls.is_empty() { None } else { Some(Box::new(self.attr_calls)) }, + ) + } + + // FIXME: proc-macro diagnostics + fn collect(&mut self, item_tree: &ItemTree, tree_id: TreeId, assoc_items: &[AssocItem]) { + let container = self.container; + self.items.reserve(assoc_items.len()); + + 'items: for &item in assoc_items { + let attrs = item_tree.attrs(self.db, self.module_id.krate, ModItem::from(item).into()); + if !attrs.is_cfg_enabled(self.expander.cfg_options()) { + continue; + } + + 'attrs: for attr in &*attrs { + let ast_id = + AstId::new(self.expander.current_file_id(), item.ast_id(&item_tree).upcast()); + let ast_id_with_path = AstIdWithPath { path: (*attr.path).clone(), ast_id }; + + if let Ok(ResolvedAttr::Macro(call_id)) = self.def_map.resolve_attr_macro( + self.db, + self.module_id.local_id, + ast_id_with_path, + attr, + ) { + self.attr_calls.push((ast_id, call_id)); + // If proc attribute macro expansion is disabled, skip expanding it here + if !self.db.enable_proc_attr_macros() { + continue 'attrs; + } + let loc = self.db.lookup_intern_macro_call(call_id); + if let MacroDefKind::ProcMacro(exp, ..) = loc.def.kind { + // If there's no expander for the proc macro (e.g. the + // proc macro is ignored, or building the proc macro + // crate failed), skip expansion like we would if it was + // disabled. This is analogous to the handling in + // `DefCollector::collect_macros`. + if exp.is_dummy() { + continue 'attrs; + } + } + match self.expander.enter_expand_id::<ast::MacroItems>(self.db, call_id) { + ExpandResult { value: Some((mark, _)), .. } => { + self.collect_macro_items(mark); + continue 'items; + } + ExpandResult { .. } => {} + } + } + } + + match item { + AssocItem::Function(id) => { + let item = &item_tree[id]; + + let def = + FunctionLoc { container, id: ItemTreeId::new(tree_id, id) }.intern(self.db); + self.items.push((item.name.clone(), def.into())); + } + AssocItem::Const(id) => { + let item = &item_tree[id]; + + let name = match item.name.clone() { + Some(name) => name, + None => continue, + }; + let def = + ConstLoc { container, id: ItemTreeId::new(tree_id, id) }.intern(self.db); + self.items.push((name, def.into())); + } + AssocItem::TypeAlias(id) => { + let item = &item_tree[id]; + + let def = TypeAliasLoc { container, id: ItemTreeId::new(tree_id, id) } + .intern(self.db); + self.items.push((item.name.clone(), def.into())); + } + AssocItem::MacroCall(call) => { + if let Some(root) = self.db.parse_or_expand(self.expander.current_file_id()) { + let call = &item_tree[call]; + + let ast_id_map = self.db.ast_id_map(self.expander.current_file_id()); + let call = ast_id_map.get(call.ast_id).to_node(&root); + let _cx = stdx::panic_context::enter(format!( + "collect_items MacroCall: {}", + call + )); + let res = self.expander.enter_expand::<ast::MacroItems>(self.db, call); + + if let Ok(ExpandResult { value: Some((mark, _)), .. }) = res { + self.collect_macro_items(mark); + } + } + } + } + } + } + + fn collect_macro_items(&mut self, mark: Mark) { + let tree_id = item_tree::TreeId::new(self.expander.current_file_id(), None); + let item_tree = tree_id.item_tree(self.db); + let iter: SmallVec<[_; 2]> = + item_tree.top_level_items().iter().filter_map(ModItem::as_assoc_item).collect(); + + self.collect(&item_tree, tree_id, &iter); + + self.expander.exit(self.db, mark); + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/db.rs b/src/tools/rust-analyzer/crates/hir-def/src/db.rs new file mode 100644 index 000000000..df6dcb024 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/db.rs @@ -0,0 +1,243 @@ +//! Defines database & queries for name resolution. +use std::sync::Arc; + +use base_db::{salsa, CrateId, SourceDatabase, Upcast}; +use either::Either; +use hir_expand::{db::AstDatabase, HirFileId}; +use la_arena::ArenaMap; +use syntax::{ast, AstPtr, SmolStr}; + +use crate::{ + adt::{EnumData, StructData}, + attr::{Attrs, AttrsWithOwner}, + body::{scope::ExprScopes, Body, BodySourceMap}, + data::{ + ConstData, FunctionData, ImplData, Macro2Data, MacroRulesData, ProcMacroData, StaticData, + TraitData, TypeAliasData, + }, + generics::GenericParams, + import_map::ImportMap, + intern::Interned, + item_tree::{AttrOwner, ItemTree}, + lang_item::{LangItemTarget, LangItems}, + nameres::DefMap, + visibility::{self, Visibility}, + AttrDefId, BlockId, BlockLoc, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, ExternBlockId, + ExternBlockLoc, FunctionId, FunctionLoc, GenericDefId, ImplId, ImplLoc, LocalEnumVariantId, + LocalFieldId, Macro2Id, Macro2Loc, MacroRulesId, MacroRulesLoc, ProcMacroId, ProcMacroLoc, + StaticId, StaticLoc, StructId, StructLoc, TraitId, TraitLoc, TypeAliasId, TypeAliasLoc, + UnionId, UnionLoc, VariantId, +}; + +#[salsa::query_group(InternDatabaseStorage)] +pub trait InternDatabase: SourceDatabase { + #[salsa::interned] + fn intern_function(&self, loc: FunctionLoc) -> FunctionId; + #[salsa::interned] + fn intern_struct(&self, loc: StructLoc) -> StructId; + #[salsa::interned] + fn intern_union(&self, loc: UnionLoc) -> UnionId; + #[salsa::interned] + fn intern_enum(&self, loc: EnumLoc) -> EnumId; + #[salsa::interned] + fn intern_const(&self, loc: ConstLoc) -> ConstId; + #[salsa::interned] + fn intern_static(&self, loc: StaticLoc) -> StaticId; + #[salsa::interned] + fn intern_trait(&self, loc: TraitLoc) -> TraitId; + #[salsa::interned] + fn intern_type_alias(&self, loc: TypeAliasLoc) -> TypeAliasId; + #[salsa::interned] + fn intern_impl(&self, loc: ImplLoc) -> ImplId; + #[salsa::interned] + fn intern_extern_block(&self, loc: ExternBlockLoc) -> ExternBlockId; + #[salsa::interned] + fn intern_block(&self, loc: BlockLoc) -> BlockId; + #[salsa::interned] + fn intern_macro2(&self, loc: Macro2Loc) -> Macro2Id; + #[salsa::interned] + fn intern_proc_macro(&self, loc: ProcMacroLoc) -> ProcMacroId; + #[salsa::interned] + fn intern_macro_rules(&self, loc: MacroRulesLoc) -> MacroRulesId; +} + +#[salsa::query_group(DefDatabaseStorage)] +pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> { + #[salsa::input] + fn enable_proc_attr_macros(&self) -> bool; + + #[salsa::invoke(ItemTree::file_item_tree_query)] + fn file_item_tree(&self, file_id: HirFileId) -> Arc<ItemTree>; + + #[salsa::invoke(crate_def_map_wait)] + #[salsa::transparent] + fn crate_def_map(&self, krate: CrateId) -> Arc<DefMap>; + + #[salsa::invoke(DefMap::crate_def_map_query)] + fn crate_def_map_query(&self, krate: CrateId) -> Arc<DefMap>; + + /// Computes the block-level `DefMap`, returning `None` when `block` doesn't contain any inner + /// items directly. + /// + /// For example: + /// + /// ``` + /// fn f() { // (0) + /// { // (1) + /// fn inner() {} + /// } + /// } + /// ``` + /// + /// The `block_def_map` for block 0 would return `None`, while `block_def_map` of block 1 would + /// return a `DefMap` containing `inner`. + #[salsa::invoke(DefMap::block_def_map_query)] + fn block_def_map(&self, block: BlockId) -> Option<Arc<DefMap>>; + + #[salsa::invoke(StructData::struct_data_query)] + fn struct_data(&self, id: StructId) -> Arc<StructData>; + + #[salsa::invoke(StructData::union_data_query)] + fn union_data(&self, id: UnionId) -> Arc<StructData>; + + #[salsa::invoke(EnumData::enum_data_query)] + fn enum_data(&self, e: EnumId) -> Arc<EnumData>; + + #[salsa::invoke(ImplData::impl_data_query)] + fn impl_data(&self, e: ImplId) -> Arc<ImplData>; + + #[salsa::invoke(TraitData::trait_data_query)] + fn trait_data(&self, e: TraitId) -> Arc<TraitData>; + + #[salsa::invoke(TypeAliasData::type_alias_data_query)] + fn type_alias_data(&self, e: TypeAliasId) -> Arc<TypeAliasData>; + + #[salsa::invoke(FunctionData::fn_data_query)] + fn function_data(&self, func: FunctionId) -> Arc<FunctionData>; + + #[salsa::invoke(ConstData::const_data_query)] + fn const_data(&self, konst: ConstId) -> Arc<ConstData>; + + #[salsa::invoke(StaticData::static_data_query)] + fn static_data(&self, konst: StaticId) -> Arc<StaticData>; + + #[salsa::invoke(Macro2Data::macro2_data_query)] + fn macro2_data(&self, makro: Macro2Id) -> Arc<Macro2Data>; + + #[salsa::invoke(MacroRulesData::macro_rules_data_query)] + fn macro_rules_data(&self, makro: MacroRulesId) -> Arc<MacroRulesData>; + + #[salsa::invoke(ProcMacroData::proc_macro_data_query)] + fn proc_macro_data(&self, makro: ProcMacroId) -> Arc<ProcMacroData>; + + #[salsa::invoke(Body::body_with_source_map_query)] + fn body_with_source_map(&self, def: DefWithBodyId) -> (Arc<Body>, Arc<BodySourceMap>); + + #[salsa::invoke(Body::body_query)] + fn body(&self, def: DefWithBodyId) -> Arc<Body>; + + #[salsa::invoke(ExprScopes::expr_scopes_query)] + fn expr_scopes(&self, def: DefWithBodyId) -> Arc<ExprScopes>; + + #[salsa::invoke(GenericParams::generic_params_query)] + fn generic_params(&self, def: GenericDefId) -> Interned<GenericParams>; + + #[salsa::invoke(Attrs::variants_attrs_query)] + fn variants_attrs(&self, def: EnumId) -> Arc<ArenaMap<LocalEnumVariantId, Attrs>>; + + #[salsa::invoke(Attrs::fields_attrs_query)] + fn fields_attrs(&self, def: VariantId) -> Arc<ArenaMap<LocalFieldId, Attrs>>; + + #[salsa::invoke(crate::attr::variants_attrs_source_map)] + fn variants_attrs_source_map( + &self, + def: EnumId, + ) -> Arc<ArenaMap<LocalEnumVariantId, AstPtr<ast::Variant>>>; + + #[salsa::invoke(crate::attr::fields_attrs_source_map)] + fn fields_attrs_source_map( + &self, + def: VariantId, + ) -> Arc<ArenaMap<LocalFieldId, Either<AstPtr<ast::TupleField>, AstPtr<ast::RecordField>>>>; + + #[salsa::invoke(AttrsWithOwner::attrs_query)] + fn attrs(&self, def: AttrDefId) -> AttrsWithOwner; + + #[salsa::invoke(LangItems::crate_lang_items_query)] + fn crate_lang_items(&self, krate: CrateId) -> Arc<LangItems>; + + #[salsa::invoke(LangItems::lang_item_query)] + fn lang_item(&self, start_crate: CrateId, item: SmolStr) -> Option<LangItemTarget>; + + #[salsa::invoke(ImportMap::import_map_query)] + fn import_map(&self, krate: CrateId) -> Arc<ImportMap>; + + #[salsa::invoke(visibility::field_visibilities_query)] + fn field_visibilities(&self, var: VariantId) -> Arc<ArenaMap<LocalFieldId, Visibility>>; + + // FIXME: unify function_visibility and const_visibility? + #[salsa::invoke(visibility::function_visibility_query)] + fn function_visibility(&self, def: FunctionId) -> Visibility; + + #[salsa::invoke(visibility::const_visibility_query)] + fn const_visibility(&self, def: ConstId) -> Visibility; + + #[salsa::transparent] + fn crate_limits(&self, crate_id: CrateId) -> CrateLimits; + + fn crate_supports_no_std(&self, crate_id: CrateId) -> bool; +} + +fn crate_def_map_wait(db: &dyn DefDatabase, krate: CrateId) -> Arc<DefMap> { + let _p = profile::span("crate_def_map:wait"); + db.crate_def_map_query(krate) +} + +pub struct CrateLimits { + /// The maximum depth for potentially infinitely-recursive compile-time operations like macro expansion or auto-dereference. + pub recursion_limit: u32, +} + +fn crate_limits(db: &dyn DefDatabase, crate_id: CrateId) -> CrateLimits { + let def_map = db.crate_def_map(crate_id); + + CrateLimits { + // 128 is the default in rustc. + recursion_limit: def_map.recursion_limit().unwrap_or(128), + } +} + +fn crate_supports_no_std(db: &dyn DefDatabase, crate_id: CrateId) -> bool { + let file = db.crate_graph()[crate_id].root_file_id; + let item_tree = db.file_item_tree(file.into()); + let attrs = item_tree.raw_attrs(AttrOwner::TopLevel); + for attr in &**attrs { + match attr.path().as_ident().and_then(|id| id.as_text()) { + Some(ident) if ident == "no_std" => return true, + Some(ident) if ident == "cfg_attr" => {} + _ => continue, + } + + // This is a `cfg_attr`; check if it could possibly expand to `no_std`. + // Syntax is: `#[cfg_attr(condition(cfg, style), attr0, attr1, <...>)]` + let tt = match attr.token_tree_value() { + Some(tt) => &tt.token_trees, + None => continue, + }; + + let segments = tt.split(|tt| match tt { + tt::TokenTree::Leaf(tt::Leaf::Punct(p)) if p.char == ',' => true, + _ => false, + }); + for output in segments.skip(1) { + match output { + [tt::TokenTree::Leaf(tt::Leaf::Ident(ident))] if ident.text == "no_std" => { + return true + } + _ => {} + } + } + } + + false +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs b/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs new file mode 100644 index 000000000..166aa04da --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs @@ -0,0 +1,116 @@ +//! This module defines a `DynMap` -- a container for heterogeneous maps. +//! +//! This means that `DynMap` stores a bunch of hash maps inside, and those maps +//! can be of different types. +//! +//! It is used like this: +//! +//! ``` +//! // keys define submaps of a `DynMap` +//! const STRING_TO_U32: Key<String, u32> = Key::new(); +//! const U32_TO_VEC: Key<u32, Vec<bool>> = Key::new(); +//! +//! // Note: concrete type, no type params! +//! let mut map = DynMap::new(); +//! +//! // To access a specific map, index the `DynMap` by `Key`: +//! map[STRING_TO_U32].insert("hello".to_string(), 92); +//! let value = map[U32_TO_VEC].get(92); +//! assert!(value.is_none()); +//! ``` +//! +//! This is a work of fiction. Any similarities to Kotlin's `BindingContext` are +//! a coincidence. +use std::{ + hash::Hash, + marker::PhantomData, + ops::{Index, IndexMut}, +}; + +use anymap::Map; +use rustc_hash::FxHashMap; + +pub struct Key<K, V, P = (K, V)> { + _phantom: PhantomData<(K, V, P)>, +} + +impl<K, V, P> Key<K, V, P> { + pub(crate) const fn new() -> Key<K, V, P> { + Key { _phantom: PhantomData } + } +} + +impl<K, V, P> Copy for Key<K, V, P> {} + +impl<K, V, P> Clone for Key<K, V, P> { + fn clone(&self) -> Key<K, V, P> { + *self + } +} + +pub trait Policy { + type K; + type V; + + fn insert(map: &mut DynMap, key: Self::K, value: Self::V); + fn get<'a>(map: &'a DynMap, key: &Self::K) -> Option<&'a Self::V>; + fn is_empty(map: &DynMap) -> bool; +} + +impl<K: Hash + Eq + 'static, V: 'static> Policy for (K, V) { + type K = K; + type V = V; + fn insert(map: &mut DynMap, key: K, value: V) { + map.map.entry::<FxHashMap<K, V>>().or_insert_with(Default::default).insert(key, value); + } + fn get<'a>(map: &'a DynMap, key: &K) -> Option<&'a V> { + map.map.get::<FxHashMap<K, V>>()?.get(key) + } + fn is_empty(map: &DynMap) -> bool { + map.map.get::<FxHashMap<K, V>>().map_or(true, |it| it.is_empty()) + } +} + +pub struct DynMap { + pub(crate) map: Map, +} + +impl Default for DynMap { + fn default() -> Self { + DynMap { map: Map::new() } + } +} + +#[repr(transparent)] +pub struct KeyMap<KEY> { + map: DynMap, + _phantom: PhantomData<KEY>, +} + +impl<P: Policy> KeyMap<Key<P::K, P::V, P>> { + pub fn insert(&mut self, key: P::K, value: P::V) { + P::insert(&mut self.map, key, value) + } + pub fn get(&self, key: &P::K) -> Option<&P::V> { + P::get(&self.map, key) + } + + pub fn is_empty(&self) -> bool { + P::is_empty(&self.map) + } +} + +impl<P: Policy> Index<Key<P::K, P::V, P>> for DynMap { + type Output = KeyMap<Key<P::K, P::V, P>>; + fn index(&self, _key: Key<P::K, P::V, P>) -> &Self::Output { + // Safe due to `#[repr(transparent)]`. + unsafe { std::mem::transmute::<&DynMap, &KeyMap<Key<P::K, P::V, P>>>(self) } + } +} + +impl<P: Policy> IndexMut<Key<P::K, P::V, P>> for DynMap { + fn index_mut(&mut self, _key: Key<P::K, P::V, P>) -> &mut Self::Output { + // Safe due to `#[repr(transparent)]`. + unsafe { std::mem::transmute::<&mut DynMap, &mut KeyMap<Key<P::K, P::V, P>>>(self) } + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr.rs new file mode 100644 index 000000000..c1b3788ac --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr.rs @@ -0,0 +1,444 @@ +//! This module describes hir-level representation of expressions. +//! +//! This representation is: +//! +//! 1. Identity-based. Each expression has an `id`, so we can distinguish +//! between different `1` in `1 + 1`. +//! 2. Independent of syntax. Though syntactic provenance information can be +//! attached separately via id-based side map. +//! 3. Unresolved. Paths are stored as sequences of names, and not as defs the +//! names refer to. +//! 4. Desugared. There's no `if let`. +//! +//! See also a neighboring `body` module. + +use hir_expand::name::Name; +use la_arena::{Idx, RawIdx}; + +use crate::{ + builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint}, + intern::Interned, + path::{GenericArgs, Path}, + type_ref::{Mutability, Rawness, TypeRef}, + BlockId, +}; + +pub use syntax::ast::{ArithOp, BinaryOp, CmpOp, LogicOp, Ordering, RangeOp, UnaryOp}; + +pub type ExprId = Idx<Expr>; + +/// FIXME: this is a hacky function which should be removed +pub(crate) fn dummy_expr_id() -> ExprId { + ExprId::from_raw(RawIdx::from(u32::MAX)) +} + +pub type PatId = Idx<Pat>; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Label { + pub name: Name, +} +pub type LabelId = Idx<Label>; + +// We convert float values into bits and that's how we don't need to deal with f32 and f64. +// For PartialEq, bits comparison should work, as ordering is not important +// https://github.com/rust-lang/rust-analyzer/issues/12380#issuecomment-1137284360 +#[derive(Default, Debug, Clone, Eq, PartialEq)] +pub struct FloatTypeWrapper(u64); + +impl FloatTypeWrapper { + pub fn new(value: f64) -> Self { + Self(value.to_bits()) + } +} + +impl std::fmt::Display for FloatTypeWrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", f64::from_bits(self.0)) + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum Literal { + String(Box<str>), + ByteString(Box<[u8]>), + Char(char), + Bool(bool), + Int(i128, Option<BuiltinInt>), + Uint(u128, Option<BuiltinUint>), + // Here we are using a wrapper around float because f32 and f64 do not implement Eq, so they + // could not be used directly here, to understand how the wrapper works go to definition of + // FloatTypeWrapper + Float(FloatTypeWrapper, Option<BuiltinFloat>), +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum Expr { + /// This is produced if the syntax tree does not have a required expression piece. + Missing, + Path(Path), + If { + condition: ExprId, + then_branch: ExprId, + else_branch: Option<ExprId>, + }, + Let { + pat: PatId, + expr: ExprId, + }, + Block { + id: BlockId, + statements: Box<[Statement]>, + tail: Option<ExprId>, + label: Option<LabelId>, + }, + Loop { + body: ExprId, + label: Option<LabelId>, + }, + While { + condition: ExprId, + body: ExprId, + label: Option<LabelId>, + }, + For { + iterable: ExprId, + pat: PatId, + body: ExprId, + label: Option<LabelId>, + }, + Call { + callee: ExprId, + args: Box<[ExprId]>, + is_assignee_expr: bool, + }, + MethodCall { + receiver: ExprId, + method_name: Name, + args: Box<[ExprId]>, + generic_args: Option<Box<GenericArgs>>, + }, + Match { + expr: ExprId, + arms: Box<[MatchArm]>, + }, + Continue { + label: Option<Name>, + }, + Break { + expr: Option<ExprId>, + label: Option<Name>, + }, + Return { + expr: Option<ExprId>, + }, + Yield { + expr: Option<ExprId>, + }, + RecordLit { + path: Option<Box<Path>>, + fields: Box<[RecordLitField]>, + spread: Option<ExprId>, + ellipsis: bool, + is_assignee_expr: bool, + }, + Field { + expr: ExprId, + name: Name, + }, + Await { + expr: ExprId, + }, + Try { + expr: ExprId, + }, + TryBlock { + body: ExprId, + }, + Async { + body: ExprId, + }, + Const { + body: ExprId, + }, + Cast { + expr: ExprId, + type_ref: Interned<TypeRef>, + }, + Ref { + expr: ExprId, + rawness: Rawness, + mutability: Mutability, + }, + Box { + expr: ExprId, + }, + UnaryOp { + expr: ExprId, + op: UnaryOp, + }, + BinaryOp { + lhs: ExprId, + rhs: ExprId, + op: Option<BinaryOp>, + }, + Range { + lhs: Option<ExprId>, + rhs: Option<ExprId>, + range_type: RangeOp, + }, + Index { + base: ExprId, + index: ExprId, + }, + Closure { + args: Box<[PatId]>, + arg_types: Box<[Option<Interned<TypeRef>>]>, + ret_type: Option<Interned<TypeRef>>, + body: ExprId, + }, + Tuple { + exprs: Box<[ExprId]>, + is_assignee_expr: bool, + }, + Unsafe { + body: ExprId, + }, + MacroStmts { + statements: Box<[Statement]>, + tail: Option<ExprId>, + }, + Array(Array), + Literal(Literal), + Underscore, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum Array { + ElementList { elements: Box<[ExprId]>, is_assignee_expr: bool }, + Repeat { initializer: ExprId, repeat: ExprId }, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct MatchArm { + pub pat: PatId, + pub guard: Option<ExprId>, + pub expr: ExprId, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct RecordLitField { + pub name: Name, + pub expr: ExprId, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum Statement { + Let { + pat: PatId, + type_ref: Option<Interned<TypeRef>>, + initializer: Option<ExprId>, + else_branch: Option<ExprId>, + }, + Expr { + expr: ExprId, + has_semi: bool, + }, +} + +impl Expr { + pub fn walk_child_exprs(&self, mut f: impl FnMut(ExprId)) { + match self { + Expr::Missing => {} + Expr::Path(_) => {} + Expr::If { condition, then_branch, else_branch } => { + f(*condition); + f(*then_branch); + if let &Some(else_branch) = else_branch { + f(else_branch); + } + } + Expr::Let { expr, .. } => { + f(*expr); + } + Expr::MacroStmts { tail, statements } | Expr::Block { statements, tail, .. } => { + for stmt in statements.iter() { + match stmt { + Statement::Let { initializer, .. } => { + if let &Some(expr) = initializer { + f(expr); + } + } + Statement::Expr { expr: expression, .. } => f(*expression), + } + } + if let &Some(expr) = tail { + f(expr); + } + } + Expr::TryBlock { body } + | Expr::Unsafe { body } + | Expr::Async { body } + | Expr::Const { body } => f(*body), + Expr::Loop { body, .. } => f(*body), + Expr::While { condition, body, .. } => { + f(*condition); + f(*body); + } + Expr::For { iterable, body, .. } => { + f(*iterable); + f(*body); + } + Expr::Call { callee, args, .. } => { + f(*callee); + args.iter().copied().for_each(f); + } + Expr::MethodCall { receiver, args, .. } => { + f(*receiver); + args.iter().copied().for_each(f); + } + Expr::Match { expr, arms } => { + f(*expr); + arms.iter().map(|arm| arm.expr).for_each(f); + } + Expr::Continue { .. } => {} + Expr::Break { expr, .. } | Expr::Return { expr } | Expr::Yield { expr } => { + if let &Some(expr) = expr { + f(expr); + } + } + Expr::RecordLit { fields, spread, .. } => { + for field in fields.iter() { + f(field.expr); + } + if let &Some(expr) = spread { + f(expr); + } + } + Expr::Closure { body, .. } => { + f(*body); + } + Expr::BinaryOp { lhs, rhs, .. } => { + f(*lhs); + f(*rhs); + } + Expr::Range { lhs, rhs, .. } => { + if let &Some(lhs) = rhs { + f(lhs); + } + if let &Some(rhs) = lhs { + f(rhs); + } + } + Expr::Index { base, index } => { + f(*base); + f(*index); + } + Expr::Field { expr, .. } + | Expr::Await { expr } + | Expr::Try { expr } + | Expr::Cast { expr, .. } + | Expr::Ref { expr, .. } + | Expr::UnaryOp { expr, .. } + | Expr::Box { expr } => { + f(*expr); + } + Expr::Tuple { exprs, .. } => exprs.iter().copied().for_each(f), + Expr::Array(a) => match a { + Array::ElementList { elements, .. } => elements.iter().copied().for_each(f), + Array::Repeat { initializer, repeat } => { + f(*initializer); + f(*repeat) + } + }, + Expr::Literal(_) => {} + Expr::Underscore => {} + } + } +} + +/// Explicit binding annotations given in the HIR for a binding. Note +/// that this is not the final binding *mode* that we infer after type +/// inference. +#[derive(Clone, PartialEq, Eq, Debug, Copy)] +pub enum BindingAnnotation { + /// No binding annotation given: this means that the final binding mode + /// will depend on whether we have skipped through a `&` reference + /// when matching. For example, the `x` in `Some(x)` will have binding + /// mode `None`; if you do `let Some(x) = &Some(22)`, it will + /// ultimately be inferred to be by-reference. + Unannotated, + + /// Annotated with `mut x` -- could be either ref or not, similar to `None`. + Mutable, + + /// Annotated as `ref`, like `ref x` + Ref, + + /// Annotated as `ref mut x`. + RefMut, +} + +impl BindingAnnotation { + pub fn new(is_mutable: bool, is_ref: bool) -> Self { + match (is_mutable, is_ref) { + (true, true) => BindingAnnotation::RefMut, + (false, true) => BindingAnnotation::Ref, + (true, false) => BindingAnnotation::Mutable, + (false, false) => BindingAnnotation::Unannotated, + } + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct RecordFieldPat { + pub name: Name, + pub pat: PatId, +} + +/// Close relative to rustc's hir::PatKind +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum Pat { + Missing, + Wild, + Tuple { args: Box<[PatId]>, ellipsis: Option<usize> }, + Or(Box<[PatId]>), + Record { path: Option<Box<Path>>, args: Box<[RecordFieldPat]>, ellipsis: bool }, + Range { start: ExprId, end: ExprId }, + Slice { prefix: Box<[PatId]>, slice: Option<PatId>, suffix: Box<[PatId]> }, + Path(Box<Path>), + Lit(ExprId), + Bind { mode: BindingAnnotation, name: Name, subpat: Option<PatId> }, + TupleStruct { path: Option<Box<Path>>, args: Box<[PatId]>, ellipsis: Option<usize> }, + Ref { pat: PatId, mutability: Mutability }, + Box { inner: PatId }, + ConstBlock(ExprId), +} + +impl Pat { + pub fn walk_child_pats(&self, mut f: impl FnMut(PatId)) { + match self { + Pat::Range { .. } + | Pat::Lit(..) + | Pat::Path(..) + | Pat::ConstBlock(..) + | Pat::Wild + | Pat::Missing => {} + Pat::Bind { subpat, .. } => { + subpat.iter().copied().for_each(f); + } + Pat::Or(args) | Pat::Tuple { args, .. } | Pat::TupleStruct { args, .. } => { + args.iter().copied().for_each(f); + } + Pat::Ref { pat, .. } => f(*pat), + Pat::Slice { prefix, slice, suffix } => { + let total_iter = prefix.iter().chain(slice.iter()).chain(suffix.iter()); + total_iter.copied().for_each(f); + } + Pat::Record { args, .. } => { + args.iter().map(|f| f.pat).for_each(f); + } + Pat::Box { inner } => f(*inner), + } + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs b/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs new file mode 100644 index 000000000..89e961f84 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs @@ -0,0 +1,1134 @@ +//! An algorithm to find a path to refer to a certain item. + +use std::iter; + +use hir_expand::name::{known, AsName, Name}; +use rustc_hash::FxHashSet; + +use crate::{ + db::DefDatabase, + item_scope::ItemInNs, + nameres::DefMap, + path::{ModPath, PathKind}, + visibility::Visibility, + ModuleDefId, ModuleId, +}; + +/// Find a path that can be used to refer to a certain item. This can depend on +/// *from where* you're referring to the item, hence the `from` parameter. +pub fn find_path(db: &dyn DefDatabase, item: ItemInNs, from: ModuleId) -> Option<ModPath> { + let _p = profile::span("find_path"); + find_path_inner(db, item, from, None) +} + +pub fn find_path_prefixed( + db: &dyn DefDatabase, + item: ItemInNs, + from: ModuleId, + prefix_kind: PrefixKind, +) -> Option<ModPath> { + let _p = profile::span("find_path_prefixed"); + find_path_inner(db, item, from, Some(prefix_kind)) +} + +const MAX_PATH_LEN: usize = 15; + +trait ModPathExt { + fn starts_with_std(&self) -> bool; + fn can_start_with_std(&self) -> bool; +} + +impl ModPathExt for ModPath { + fn starts_with_std(&self) -> bool { + self.segments().first() == Some(&known::std) + } + + // Can we replace the first segment with `std::` and still get a valid, identical path? + fn can_start_with_std(&self) -> bool { + let first_segment = self.segments().first(); + first_segment == Some(&known::alloc) || first_segment == Some(&known::core) + } +} + +fn check_self_super(def_map: &DefMap, item: ItemInNs, from: ModuleId) -> Option<ModPath> { + if item == ItemInNs::Types(from.into()) { + // - if the item is the module we're in, use `self` + Some(ModPath::from_segments(PathKind::Super(0), None)) + } else if let Some(parent_id) = def_map[from.local_id].parent { + // - if the item is the parent module, use `super` (this is not used recursively, since `super::super` is ugly) + let parent_id = def_map.module_id(parent_id); + if item == ItemInNs::Types(ModuleDefId::ModuleId(parent_id)) { + Some(ModPath::from_segments(PathKind::Super(1), None)) + } else { + None + } + } else { + None + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum PrefixKind { + /// Causes paths to always start with either `self`, `super`, `crate` or a crate-name. + /// This is the same as plain, just that paths will start with `self` iprepended f the path + /// starts with an identifier that is not a crate. + BySelf, + /// Causes paths to ignore imports in the local module. + Plain, + /// Causes paths to start with `crate` where applicable, effectively forcing paths to be absolute. + ByCrate, +} + +impl PrefixKind { + #[inline] + fn prefix(self) -> PathKind { + match self { + PrefixKind::BySelf => PathKind::Super(0), + PrefixKind::Plain => PathKind::Plain, + PrefixKind::ByCrate => PathKind::Crate, + } + } + + #[inline] + fn is_absolute(&self) -> bool { + self == &PrefixKind::ByCrate + } +} +/// Attempts to find a path to refer to the given `item` visible from the `from` ModuleId +fn find_path_inner( + db: &dyn DefDatabase, + item: ItemInNs, + from: ModuleId, + prefixed: Option<PrefixKind>, +) -> Option<ModPath> { + // FIXME: Do fast path for std/core libs? + + let mut visited_modules = FxHashSet::default(); + let def_map = from.def_map(db); + find_path_inner_(db, &def_map, from, item, MAX_PATH_LEN, prefixed, &mut visited_modules) +} + +fn find_path_inner_( + db: &dyn DefDatabase, + def_map: &DefMap, + from: ModuleId, + item: ItemInNs, + max_len: usize, + mut prefixed: Option<PrefixKind>, + visited_modules: &mut FxHashSet<ModuleId>, +) -> Option<ModPath> { + if max_len == 0 { + return None; + } + + // Base cases: + + // - if the item is already in scope, return the name under which it is + let scope_name = def_map.with_ancestor_maps(db, from.local_id, &mut |def_map, local_id| { + def_map[local_id].scope.name_of(item).map(|(name, _)| name.clone()) + }); + if prefixed.is_none() { + if let Some(scope_name) = scope_name { + return Some(ModPath::from_segments(PathKind::Plain, Some(scope_name))); + } + } + + // - if the item is a builtin, it's in scope + if let ItemInNs::Types(ModuleDefId::BuiltinType(builtin)) = item { + return Some(ModPath::from_segments(PathKind::Plain, Some(builtin.as_name()))); + } + + // - if the item is the crate root, return `crate` + let crate_root = def_map.crate_root(db); + if item == ItemInNs::Types(ModuleDefId::ModuleId(crate_root)) { + return Some(ModPath::from_segments(PathKind::Crate, None)); + } + + if prefixed.filter(PrefixKind::is_absolute).is_none() { + if let modpath @ Some(_) = check_self_super(&def_map, item, from) { + return modpath; + } + } + + // - if the item is the crate root of a dependency crate, return the name from the extern prelude + let root_def_map = crate_root.def_map(db); + if let ItemInNs::Types(ModuleDefId::ModuleId(item)) = item { + for (name, &def_id) in root_def_map.extern_prelude() { + if item == def_id { + let name = scope_name.unwrap_or_else(|| name.clone()); + + let name_already_occupied_in_type_ns = def_map + .with_ancestor_maps(db, from.local_id, &mut |def_map, local_id| { + def_map[local_id] + .scope + .type_(&name) + .filter(|&(id, _)| id != ModuleDefId::ModuleId(def_id)) + }) + .is_some(); + let kind = if name_already_occupied_in_type_ns { + cov_mark::hit!(ambiguous_crate_start); + PathKind::Abs + } else { + PathKind::Plain + }; + return Some(ModPath::from_segments(kind, Some(name))); + } + } + } + + // - if the item is in the prelude, return the name from there + if let Some(prelude_module) = root_def_map.prelude() { + // Preludes in block DefMaps are ignored, only the crate DefMap is searched + let prelude_def_map = prelude_module.def_map(db); + let prelude_scope = &prelude_def_map[prelude_module.local_id].scope; + if let Some((name, vis)) = prelude_scope.name_of(item) { + if vis.is_visible_from(db, from) { + return Some(ModPath::from_segments(PathKind::Plain, Some(name.clone()))); + } + } + } + + // Recursive case: + // - if the item is an enum variant, refer to it via the enum + if let Some(ModuleDefId::EnumVariantId(variant)) = item.as_module_def_id() { + if let Some(mut path) = find_path(db, ItemInNs::Types(variant.parent.into()), from) { + let data = db.enum_data(variant.parent); + path.push_segment(data.variants[variant.local_id].name.clone()); + return Some(path); + } + // If this doesn't work, it seems we have no way of referring to the + // enum; that's very weird, but there might still be a reexport of the + // variant somewhere + } + + // - otherwise, look for modules containing (reexporting) it and import it from one of those + let prefer_no_std = db.crate_supports_no_std(crate_root.krate); + let mut best_path = None; + let mut best_path_len = max_len; + + if item.krate(db) == Some(from.krate) { + // Item was defined in the same crate that wants to import it. It cannot be found in any + // dependency in this case. + // FIXME: this should have a fast path that doesn't look through the prelude again? + for (module_id, name) in find_local_import_locations(db, item, from) { + if !visited_modules.insert(module_id) { + cov_mark::hit!(recursive_imports); + continue; + } + if let Some(mut path) = find_path_inner_( + db, + def_map, + from, + ItemInNs::Types(ModuleDefId::ModuleId(module_id)), + best_path_len - 1, + prefixed, + visited_modules, + ) { + path.push_segment(name); + + let new_path = match best_path { + Some(best_path) => select_best_path(best_path, path, prefer_no_std), + None => path, + }; + best_path_len = new_path.len(); + best_path = Some(new_path); + } + } + } else { + // Item was defined in some upstream crate. This means that it must be exported from one, + // too (unless we can't name it at all). It could *also* be (re)exported by the same crate + // that wants to import it here, but we always prefer to use the external path here. + + let crate_graph = db.crate_graph(); + let extern_paths = crate_graph[from.krate].dependencies.iter().filter_map(|dep| { + let import_map = db.import_map(dep.crate_id); + import_map.import_info_for(item).and_then(|info| { + // Determine best path for containing module and append last segment from `info`. + // FIXME: we should guide this to look up the path locally, or from the same crate again? + let mut path = find_path_inner_( + db, + def_map, + from, + ItemInNs::Types(ModuleDefId::ModuleId(info.container)), + best_path_len - 1, + prefixed, + visited_modules, + )?; + cov_mark::hit!(partially_imported); + path.push_segment(info.path.segments.last()?.clone()); + Some(path) + }) + }); + + for path in extern_paths { + let new_path = match best_path { + Some(best_path) => select_best_path(best_path, path, prefer_no_std), + None => path, + }; + best_path = Some(new_path); + } + } + + // If the item is declared inside a block expression, don't use a prefix, as we don't handle + // that correctly (FIXME). + if let Some(item_module) = item.as_module_def_id().and_then(|did| did.module(db)) { + if item_module.def_map(db).block_id().is_some() && prefixed.is_some() { + cov_mark::hit!(prefixed_in_block_expression); + prefixed = Some(PrefixKind::Plain); + } + } + + match prefixed.map(PrefixKind::prefix) { + Some(prefix) => best_path.or_else(|| { + scope_name.map(|scope_name| ModPath::from_segments(prefix, Some(scope_name))) + }), + None => best_path, + } +} + +fn select_best_path(old_path: ModPath, new_path: ModPath, prefer_no_std: bool) -> ModPath { + if old_path.starts_with_std() && new_path.can_start_with_std() { + if prefer_no_std { + cov_mark::hit!(prefer_no_std_paths); + new_path + } else { + cov_mark::hit!(prefer_std_paths); + old_path + } + } else if new_path.starts_with_std() && old_path.can_start_with_std() { + if prefer_no_std { + cov_mark::hit!(prefer_no_std_paths); + old_path + } else { + cov_mark::hit!(prefer_std_paths); + new_path + } + } else if new_path.len() < old_path.len() { + new_path + } else { + old_path + } +} + +/// Finds locations in `from.krate` from which `item` can be imported by `from`. +fn find_local_import_locations( + db: &dyn DefDatabase, + item: ItemInNs, + from: ModuleId, +) -> Vec<(ModuleId, Name)> { + let _p = profile::span("find_local_import_locations"); + + // `from` can import anything below `from` with visibility of at least `from`, and anything + // above `from` with any visibility. That means we do not need to descend into private siblings + // of `from` (and similar). + + let def_map = from.def_map(db); + + // Compute the initial worklist. We start with all direct child modules of `from` as well as all + // of its (recursive) parent modules. + let data = &def_map[from.local_id]; + let mut worklist = + data.children.values().map(|child| def_map.module_id(*child)).collect::<Vec<_>>(); + // FIXME: do we need to traverse out of block expressions here? + for ancestor in iter::successors(from.containing_module(db), |m| m.containing_module(db)) { + worklist.push(ancestor); + } + + let def_map = def_map.crate_root(db).def_map(db); + + let mut seen: FxHashSet<_> = FxHashSet::default(); + + let mut locations = Vec::new(); + while let Some(module) = worklist.pop() { + if !seen.insert(module) { + continue; // already processed this module + } + + let ext_def_map; + let data = if module.krate == from.krate { + if module.block.is_some() { + // Re-query the block's DefMap + ext_def_map = module.def_map(db); + &ext_def_map[module.local_id] + } else { + // Reuse the root DefMap + &def_map[module.local_id] + } + } else { + // The crate might reexport a module defined in another crate. + ext_def_map = module.def_map(db); + &ext_def_map[module.local_id] + }; + + if let Some((name, vis)) = data.scope.name_of(item) { + if vis.is_visible_from(db, from) { + let is_private = match vis { + Visibility::Module(private_to) => private_to.local_id == module.local_id, + Visibility::Public => false, + }; + let is_original_def = match item.as_module_def_id() { + Some(module_def_id) => data.scope.declarations().any(|it| it == module_def_id), + None => false, + }; + + // Ignore private imports. these could be used if we are + // in a submodule of this module, but that's usually not + // what the user wants; and if this module can import + // the item and we're a submodule of it, so can we. + // Also this keeps the cached data smaller. + if !is_private || is_original_def { + locations.push((module, name.clone())); + } + } + } + + // Descend into all modules visible from `from`. + for (ty, vis) in data.scope.types() { + if let ModuleDefId::ModuleId(module) = ty { + if vis.is_visible_from(db, from) { + worklist.push(module); + } + } + } + } + + locations +} + +#[cfg(test)] +mod tests { + use base_db::fixture::WithFixture; + use hir_expand::hygiene::Hygiene; + use syntax::ast::AstNode; + + use crate::test_db::TestDB; + + use super::*; + + /// `code` needs to contain a cursor marker; checks that `find_path` for the + /// item the `path` refers to returns that same path when called from the + /// module the cursor is in. + fn check_found_path_(ra_fixture: &str, path: &str, prefix_kind: Option<PrefixKind>) { + let (db, pos) = TestDB::with_position(ra_fixture); + let module = db.module_at_position(pos); + let parsed_path_file = syntax::SourceFile::parse(&format!("use {};", path)); + let ast_path = + parsed_path_file.syntax_node().descendants().find_map(syntax::ast::Path::cast).unwrap(); + let mod_path = ModPath::from_src(&db, ast_path, &Hygiene::new_unhygienic()).unwrap(); + + let def_map = module.def_map(&db); + let resolved = def_map + .resolve_path( + &db, + module.local_id, + &mod_path, + crate::item_scope::BuiltinShadowMode::Module, + ) + .0 + .take_types() + .unwrap(); + + let found_path = find_path_inner(&db, ItemInNs::Types(resolved), module, prefix_kind); + assert_eq!(found_path, Some(mod_path), "{:?}", prefix_kind); + } + + fn check_found_path( + ra_fixture: &str, + unprefixed: &str, + prefixed: &str, + absolute: &str, + self_prefixed: &str, + ) { + check_found_path_(ra_fixture, unprefixed, None); + check_found_path_(ra_fixture, prefixed, Some(PrefixKind::Plain)); + check_found_path_(ra_fixture, absolute, Some(PrefixKind::ByCrate)); + check_found_path_(ra_fixture, self_prefixed, Some(PrefixKind::BySelf)); + } + + #[test] + fn same_module() { + check_found_path( + r#" +struct S; +$0 + "#, + "S", + "S", + "crate::S", + "self::S", + ); + } + + #[test] + fn enum_variant() { + check_found_path( + r#" +enum E { A } +$0 + "#, + "E::A", + "E::A", + "E::A", + "E::A", + ); + } + + #[test] + fn sub_module() { + check_found_path( + r#" +mod foo { + pub struct S; +} +$0 + "#, + "foo::S", + "foo::S", + "crate::foo::S", + "self::foo::S", + ); + } + + #[test] + fn super_module() { + check_found_path( + r#" +//- /main.rs +mod foo; +//- /foo.rs +mod bar; +struct S; +//- /foo/bar.rs +$0 + "#, + "super::S", + "super::S", + "crate::foo::S", + "super::S", + ); + } + + #[test] + fn self_module() { + check_found_path( + r#" +//- /main.rs +mod foo; +//- /foo.rs +$0 + "#, + "self", + "self", + "crate::foo", + "self", + ); + } + + #[test] + fn crate_root() { + check_found_path( + r#" +//- /main.rs +mod foo; +//- /foo.rs +$0 + "#, + "crate", + "crate", + "crate", + "crate", + ); + } + + #[test] + fn same_crate() { + check_found_path( + r#" +//- /main.rs +mod foo; +struct S; +//- /foo.rs +$0 + "#, + "crate::S", + "crate::S", + "crate::S", + "crate::S", + ); + } + + #[test] + fn different_crate() { + check_found_path( + r#" +//- /main.rs crate:main deps:std +$0 +//- /std.rs crate:std +pub struct S; + "#, + "std::S", + "std::S", + "std::S", + "std::S", + ); + } + + #[test] + fn different_crate_renamed() { + check_found_path( + r#" +//- /main.rs crate:main deps:std +extern crate std as std_renamed; +$0 +//- /std.rs crate:std +pub struct S; + "#, + "std_renamed::S", + "std_renamed::S", + "std_renamed::S", + "std_renamed::S", + ); + } + + #[test] + fn partially_imported() { + cov_mark::check!(partially_imported); + // Tests that short paths are used even for external items, when parts of the path are + // already in scope. + check_found_path( + r#" +//- /main.rs crate:main deps:syntax + +use syntax::ast; +$0 + +//- /lib.rs crate:syntax +pub mod ast { + pub enum ModuleItem { + A, B, C, + } +} + "#, + "ast::ModuleItem", + "syntax::ast::ModuleItem", + "syntax::ast::ModuleItem", + "syntax::ast::ModuleItem", + ); + + check_found_path( + r#" +//- /main.rs crate:main deps:syntax +$0 + +//- /lib.rs crate:syntax +pub mod ast { + pub enum ModuleItem { + A, B, C, + } +} + "#, + "syntax::ast::ModuleItem", + "syntax::ast::ModuleItem", + "syntax::ast::ModuleItem", + "syntax::ast::ModuleItem", + ); + } + + #[test] + fn same_crate_reexport() { + check_found_path( + r#" +mod bar { + mod foo { pub(super) struct S; } + pub(crate) use foo::*; +} +$0 + "#, + "bar::S", + "bar::S", + "crate::bar::S", + "self::bar::S", + ); + } + + #[test] + fn same_crate_reexport_rename() { + check_found_path( + r#" +mod bar { + mod foo { pub(super) struct S; } + pub(crate) use foo::S as U; +} +$0 + "#, + "bar::U", + "bar::U", + "crate::bar::U", + "self::bar::U", + ); + } + + #[test] + fn different_crate_reexport() { + check_found_path( + r#" +//- /main.rs crate:main deps:std +$0 +//- /std.rs crate:std deps:core +pub use core::S; +//- /core.rs crate:core +pub struct S; + "#, + "std::S", + "std::S", + "std::S", + "std::S", + ); + } + + #[test] + fn prelude() { + check_found_path( + r#" +//- /main.rs crate:main deps:std +$0 +//- /std.rs crate:std +pub mod prelude { + pub mod rust_2018 { + pub struct S; + } +} + "#, + "S", + "S", + "S", + "S", + ); + } + + #[test] + fn enum_variant_from_prelude() { + let code = r#" +//- /main.rs crate:main deps:std +$0 +//- /std.rs crate:std +pub mod prelude { + pub mod rust_2018 { + pub enum Option<T> { Some(T), None } + pub use Option::*; + } +} + "#; + check_found_path(code, "None", "None", "None", "None"); + check_found_path(code, "Some", "Some", "Some", "Some"); + } + + #[test] + fn shortest_path() { + check_found_path( + r#" +//- /main.rs +pub mod foo; +pub mod baz; +struct S; +$0 +//- /foo.rs +pub mod bar { pub struct S; } +//- /baz.rs +pub use crate::foo::bar::S; + "#, + "baz::S", + "baz::S", + "crate::baz::S", + "self::baz::S", + ); + } + + #[test] + fn discount_private_imports() { + check_found_path( + r#" +//- /main.rs +mod foo; +pub mod bar { pub struct S; } +use bar::S; +//- /foo.rs +$0 + "#, + // crate::S would be shorter, but using private imports seems wrong + "crate::bar::S", + "crate::bar::S", + "crate::bar::S", + "crate::bar::S", + ); + } + + #[test] + fn import_cycle() { + check_found_path( + r#" +//- /main.rs +pub mod foo; +pub mod bar; +pub mod baz; +//- /bar.rs +$0 +//- /foo.rs +pub use super::baz; +pub struct S; +//- /baz.rs +pub use super::foo; + "#, + "crate::foo::S", + "crate::foo::S", + "crate::foo::S", + "crate::foo::S", + ); + } + + #[test] + fn prefer_std_paths_over_alloc() { + cov_mark::check!(prefer_std_paths); + check_found_path( + r#" +//- /main.rs crate:main deps:alloc,std +$0 + +//- /std.rs crate:std deps:alloc +pub mod sync { + pub use alloc::sync::Arc; +} + +//- /zzz.rs crate:alloc +pub mod sync { + pub struct Arc; +} + "#, + "std::sync::Arc", + "std::sync::Arc", + "std::sync::Arc", + "std::sync::Arc", + ); + } + + #[test] + fn prefer_core_paths_over_std() { + cov_mark::check!(prefer_no_std_paths); + check_found_path( + r#" +//- /main.rs crate:main deps:core,std +#![no_std] + +$0 + +//- /std.rs crate:std deps:core + +pub mod fmt { + pub use core::fmt::Error; +} + +//- /zzz.rs crate:core + +pub mod fmt { + pub struct Error; +} + "#, + "core::fmt::Error", + "core::fmt::Error", + "core::fmt::Error", + "core::fmt::Error", + ); + + // Should also work (on a best-effort basis) if `no_std` is conditional. + check_found_path( + r#" +//- /main.rs crate:main deps:core,std +#![cfg_attr(not(test), no_std)] + +$0 + +//- /std.rs crate:std deps:core + +pub mod fmt { + pub use core::fmt::Error; +} + +//- /zzz.rs crate:core + +pub mod fmt { + pub struct Error; +} + "#, + "core::fmt::Error", + "core::fmt::Error", + "core::fmt::Error", + "core::fmt::Error", + ); + } + + #[test] + fn prefer_alloc_paths_over_std() { + check_found_path( + r#" +//- /main.rs crate:main deps:alloc,std +#![no_std] + +$0 + +//- /std.rs crate:std deps:alloc + +pub mod sync { + pub use alloc::sync::Arc; +} + +//- /zzz.rs crate:alloc + +pub mod sync { + pub struct Arc; +} + "#, + "alloc::sync::Arc", + "alloc::sync::Arc", + "alloc::sync::Arc", + "alloc::sync::Arc", + ); + } + + #[test] + fn prefer_shorter_paths_if_not_alloc() { + check_found_path( + r#" +//- /main.rs crate:main deps:megaalloc,std +$0 + +//- /std.rs crate:std deps:megaalloc +pub mod sync { + pub use megaalloc::sync::Arc; +} + +//- /zzz.rs crate:megaalloc +pub struct Arc; + "#, + "megaalloc::Arc", + "megaalloc::Arc", + "megaalloc::Arc", + "megaalloc::Arc", + ); + } + + #[test] + fn builtins_are_in_scope() { + let code = r#" +$0 + +pub mod primitive { + pub use u8; +} + "#; + check_found_path(code, "u8", "u8", "u8", "u8"); + check_found_path(code, "u16", "u16", "u16", "u16"); + } + + #[test] + fn inner_items() { + check_found_path( + r#" +fn main() { + struct Inner {} + $0 +} + "#, + "Inner", + "Inner", + "Inner", + "Inner", + ); + } + + #[test] + fn inner_items_from_outer_scope() { + check_found_path( + r#" +fn main() { + struct Struct {} + { + $0 + } +} + "#, + "Struct", + "Struct", + "Struct", + "Struct", + ); + } + + #[test] + fn inner_items_from_inner_module() { + cov_mark::check!(prefixed_in_block_expression); + check_found_path( + r#" +fn main() { + mod module { + struct Struct {} + } + { + $0 + } +} + "#, + "module::Struct", + "module::Struct", + "module::Struct", + "module::Struct", + ); + } + + #[test] + fn outer_items_with_inner_items_present() { + check_found_path( + r#" +mod module { + pub struct CompleteMe; +} + +fn main() { + fn inner() {} + $0 +} + "#, + // FIXME: these could use fewer/better prefixes + "module::CompleteMe", + "crate::module::CompleteMe", + "crate::module::CompleteMe", + "crate::module::CompleteMe", + ) + } + + #[test] + fn from_inside_module() { + // This worked correctly, but the test suite logic was broken. + cov_mark::check!(submodule_in_testdb); + check_found_path( + r#" +mod baz { + pub struct Foo {} +} + +mod bar { + fn bar() { + $0 + } +} + "#, + "crate::baz::Foo", + "crate::baz::Foo", + "crate::baz::Foo", + "crate::baz::Foo", + ) + } + + #[test] + fn from_inside_module_with_inner_items() { + check_found_path( + r#" +mod baz { + pub struct Foo {} +} + +mod bar { + fn bar() { + fn inner() {} + $0 + } +} + "#, + "crate::baz::Foo", + "crate::baz::Foo", + "crate::baz::Foo", + "crate::baz::Foo", + ) + } + + #[test] + fn recursive_pub_mod_reexport() { + cov_mark::check!(recursive_imports); + check_found_path( + r#" +fn main() { + let _ = 22_i32.as_name$0(); +} + +pub mod name { + pub trait AsName { + fn as_name(&self) -> String; + } + impl AsName for i32 { + fn as_name(&self) -> String { + format!("Name: {}", self) + } + } + pub use crate::name; +} +"#, + "name::AsName", + "name::AsName", + "crate::name::AsName", + "self::name::AsName", + ); + } + + #[test] + fn extern_crate() { + check_found_path( + r#" +//- /main.rs crate:main deps:dep +$0 +//- /dep.rs crate:dep +"#, + "dep", + "dep", + "dep", + "dep", + ); + + check_found_path( + r#" +//- /main.rs crate:main deps:dep +fn f() { + fn inner() {} + $0 +} +//- /dep.rs crate:dep +"#, + "dep", + "dep", + "dep", + "dep", + ); + } + + #[test] + fn prelude_with_inner_items() { + check_found_path( + r#" +//- /main.rs crate:main deps:std +fn f() { + fn inner() {} + $0 +} +//- /std.rs crate:std +pub mod prelude { + pub mod rust_2018 { + pub enum Option { None } + pub use Option::*; + } +} + "#, + "None", + "None", + "None", + "None", + ); + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/generics.rs b/src/tools/rust-analyzer/crates/hir-def/src/generics.rs new file mode 100644 index 000000000..2397cf501 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/generics.rs @@ -0,0 +1,522 @@ +//! Many kinds of items or constructs can have generic parameters: functions, +//! structs, impls, traits, etc. This module provides a common HIR for these +//! generic parameters. See also the `Generics` type and the `generics_of` query +//! in rustc. + +use base_db::FileId; +use either::Either; +use hir_expand::{ + name::{AsName, Name}, + ExpandResult, HirFileId, InFile, +}; +use la_arena::{Arena, ArenaMap, Idx}; +use once_cell::unsync::Lazy; +use std::ops::DerefMut; +use stdx::impl_from; +use syntax::ast::{self, HasGenericParams, HasName, HasTypeBounds}; + +use crate::{ + body::{Expander, LowerCtx}, + child_by_source::ChildBySource, + db::DefDatabase, + dyn_map::DynMap, + intern::Interned, + keys, + src::{HasChildSource, HasSource}, + type_ref::{LifetimeRef, TypeBound, TypeRef}, + AdtId, ConstParamId, GenericDefId, HasModule, LifetimeParamId, LocalLifetimeParamId, + LocalTypeOrConstParamId, Lookup, TypeOrConstParamId, TypeParamId, +}; + +/// Data about a generic type parameter (to a function, struct, impl, ...). +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub struct TypeParamData { + pub name: Option<Name>, + pub default: Option<Interned<TypeRef>>, + pub provenance: TypeParamProvenance, +} + +/// Data about a generic lifetime parameter (to a function, struct, impl, ...). +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub struct LifetimeParamData { + pub name: Name, +} + +/// Data about a generic const parameter (to a function, struct, impl, ...). +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub struct ConstParamData { + pub name: Name, + pub ty: Interned<TypeRef>, + pub has_default: bool, +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +pub enum TypeParamProvenance { + TypeParamList, + TraitSelf, + ArgumentImplTrait, +} + +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub enum TypeOrConstParamData { + TypeParamData(TypeParamData), + ConstParamData(ConstParamData), +} + +impl TypeOrConstParamData { + pub fn name(&self) -> Option<&Name> { + match self { + TypeOrConstParamData::TypeParamData(x) => x.name.as_ref(), + TypeOrConstParamData::ConstParamData(x) => Some(&x.name), + } + } + + pub fn has_default(&self) -> bool { + match self { + TypeOrConstParamData::TypeParamData(x) => x.default.is_some(), + TypeOrConstParamData::ConstParamData(x) => x.has_default, + } + } + + pub fn type_param(&self) -> Option<&TypeParamData> { + match self { + TypeOrConstParamData::TypeParamData(x) => Some(x), + TypeOrConstParamData::ConstParamData(_) => None, + } + } + + pub fn const_param(&self) -> Option<&ConstParamData> { + match self { + TypeOrConstParamData::TypeParamData(_) => None, + TypeOrConstParamData::ConstParamData(x) => Some(x), + } + } + + pub fn is_trait_self(&self) -> bool { + match self { + TypeOrConstParamData::TypeParamData(x) => { + x.provenance == TypeParamProvenance::TraitSelf + } + TypeOrConstParamData::ConstParamData(_) => false, + } + } +} + +impl_from!(TypeParamData, ConstParamData for TypeOrConstParamData); + +/// Data about the generic parameters of a function, struct, impl, etc. +#[derive(Clone, PartialEq, Eq, Debug, Default, Hash)] +pub struct GenericParams { + pub type_or_consts: Arena<TypeOrConstParamData>, + pub lifetimes: Arena<LifetimeParamData>, + pub where_predicates: Vec<WherePredicate>, +} + +/// A single predicate from a where clause, i.e. `where Type: Trait`. Combined +/// where clauses like `where T: Foo + Bar` are turned into multiple of these. +/// It might still result in multiple actual predicates though, because of +/// associated type bindings like `Iterator<Item = u32>`. +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub enum WherePredicate { + TypeBound { + target: WherePredicateTypeTarget, + bound: Interned<TypeBound>, + }, + Lifetime { + target: LifetimeRef, + bound: LifetimeRef, + }, + ForLifetime { + lifetimes: Box<[Name]>, + target: WherePredicateTypeTarget, + bound: Interned<TypeBound>, + }, +} + +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub enum WherePredicateTypeTarget { + TypeRef(Interned<TypeRef>), + /// For desugared where predicates that can directly refer to a type param. + TypeOrConstParam(LocalTypeOrConstParamId), +} + +impl GenericParams { + /// Iterator of type_or_consts field + pub fn iter<'a>( + &'a self, + ) -> impl DoubleEndedIterator<Item = (Idx<TypeOrConstParamData>, &TypeOrConstParamData)> { + self.type_or_consts.iter() + } + + pub(crate) fn generic_params_query( + db: &dyn DefDatabase, + def: GenericDefId, + ) -> Interned<GenericParams> { + let _p = profile::span("generic_params_query"); + + macro_rules! id_to_generics { + ($id:ident) => {{ + let id = $id.lookup(db).id; + let tree = id.item_tree(db); + let item = &tree[id.value]; + item.generic_params.clone() + }}; + } + + match def { + GenericDefId::FunctionId(id) => { + let loc = id.lookup(db); + let tree = loc.id.item_tree(db); + let item = &tree[loc.id.value]; + + let mut generic_params = GenericParams::clone(&item.explicit_generic_params); + + let module = loc.container.module(db); + let func_data = db.function_data(id); + + // Don't create an `Expander` nor call `loc.source(db)` if not needed since this + // causes a reparse after the `ItemTree` has been created. + let mut expander = Lazy::new(|| Expander::new(db, loc.source(db).file_id, module)); + for (_, param) in &func_data.params { + generic_params.fill_implicit_impl_trait_args(db, &mut expander, param); + } + + Interned::new(generic_params) + } + GenericDefId::AdtId(AdtId::StructId(id)) => id_to_generics!(id), + GenericDefId::AdtId(AdtId::EnumId(id)) => id_to_generics!(id), + GenericDefId::AdtId(AdtId::UnionId(id)) => id_to_generics!(id), + GenericDefId::TraitId(id) => id_to_generics!(id), + GenericDefId::TypeAliasId(id) => id_to_generics!(id), + GenericDefId::ImplId(id) => id_to_generics!(id), + GenericDefId::EnumVariantId(_) | GenericDefId::ConstId(_) => { + Interned::new(GenericParams::default()) + } + } + } + + pub(crate) fn fill(&mut self, lower_ctx: &LowerCtx<'_>, node: &dyn HasGenericParams) { + if let Some(params) = node.generic_param_list() { + self.fill_params(lower_ctx, params) + } + if let Some(where_clause) = node.where_clause() { + self.fill_where_predicates(lower_ctx, where_clause); + } + } + + pub(crate) fn fill_bounds( + &mut self, + lower_ctx: &LowerCtx<'_>, + node: &dyn ast::HasTypeBounds, + target: Either<TypeRef, LifetimeRef>, + ) { + for bound in + node.type_bound_list().iter().flat_map(|type_bound_list| type_bound_list.bounds()) + { + self.add_where_predicate_from_bound(lower_ctx, bound, None, target.clone()); + } + } + + fn fill_params(&mut self, lower_ctx: &LowerCtx<'_>, params: ast::GenericParamList) { + for type_or_const_param in params.type_or_const_params() { + match type_or_const_param { + ast::TypeOrConstParam::Type(type_param) => { + let name = type_param.name().map_or_else(Name::missing, |it| it.as_name()); + // FIXME: Use `Path::from_src` + let default = type_param + .default_type() + .map(|it| Interned::new(TypeRef::from_ast(lower_ctx, it))); + let param = TypeParamData { + name: Some(name.clone()), + default, + provenance: TypeParamProvenance::TypeParamList, + }; + self.type_or_consts.alloc(param.into()); + let type_ref = TypeRef::Path(name.into()); + self.fill_bounds(lower_ctx, &type_param, Either::Left(type_ref)); + } + ast::TypeOrConstParam::Const(const_param) => { + let name = const_param.name().map_or_else(Name::missing, |it| it.as_name()); + let ty = const_param + .ty() + .map_or(TypeRef::Error, |it| TypeRef::from_ast(lower_ctx, it)); + let param = ConstParamData { + name, + ty: Interned::new(ty), + has_default: const_param.default_val().is_some(), + }; + self.type_or_consts.alloc(param.into()); + } + } + } + for lifetime_param in params.lifetime_params() { + let name = + lifetime_param.lifetime().map_or_else(Name::missing, |lt| Name::new_lifetime(<)); + let param = LifetimeParamData { name: name.clone() }; + self.lifetimes.alloc(param); + let lifetime_ref = LifetimeRef::new_name(name); + self.fill_bounds(lower_ctx, &lifetime_param, Either::Right(lifetime_ref)); + } + } + + fn fill_where_predicates(&mut self, lower_ctx: &LowerCtx<'_>, where_clause: ast::WhereClause) { + for pred in where_clause.predicates() { + let target = if let Some(type_ref) = pred.ty() { + Either::Left(TypeRef::from_ast(lower_ctx, type_ref)) + } else if let Some(lifetime) = pred.lifetime() { + Either::Right(LifetimeRef::new(&lifetime)) + } else { + continue; + }; + + let lifetimes: Option<Box<_>> = pred.generic_param_list().map(|param_list| { + // Higher-Ranked Trait Bounds + param_list + .lifetime_params() + .map(|lifetime_param| { + lifetime_param + .lifetime() + .map_or_else(Name::missing, |lt| Name::new_lifetime(<)) + }) + .collect() + }); + for bound in pred.type_bound_list().iter().flat_map(|l| l.bounds()) { + self.add_where_predicate_from_bound( + lower_ctx, + bound, + lifetimes.as_ref(), + target.clone(), + ); + } + } + } + + fn add_where_predicate_from_bound( + &mut self, + lower_ctx: &LowerCtx<'_>, + bound: ast::TypeBound, + hrtb_lifetimes: Option<&Box<[Name]>>, + target: Either<TypeRef, LifetimeRef>, + ) { + let bound = TypeBound::from_ast(lower_ctx, bound); + let predicate = match (target, bound) { + (Either::Left(type_ref), bound) => match hrtb_lifetimes { + Some(hrtb_lifetimes) => WherePredicate::ForLifetime { + lifetimes: hrtb_lifetimes.clone(), + target: WherePredicateTypeTarget::TypeRef(Interned::new(type_ref)), + bound: Interned::new(bound), + }, + None => WherePredicate::TypeBound { + target: WherePredicateTypeTarget::TypeRef(Interned::new(type_ref)), + bound: Interned::new(bound), + }, + }, + (Either::Right(lifetime), TypeBound::Lifetime(bound)) => { + WherePredicate::Lifetime { target: lifetime, bound } + } + _ => return, + }; + self.where_predicates.push(predicate); + } + + pub(crate) fn fill_implicit_impl_trait_args( + &mut self, + db: &dyn DefDatabase, + expander: &mut impl DerefMut<Target = Expander>, + type_ref: &TypeRef, + ) { + type_ref.walk(&mut |type_ref| { + if let TypeRef::ImplTrait(bounds) = type_ref { + let param = TypeParamData { + name: None, + default: None, + provenance: TypeParamProvenance::ArgumentImplTrait, + }; + let param_id = self.type_or_consts.alloc(param.into()); + for bound in bounds { + self.where_predicates.push(WherePredicate::TypeBound { + target: WherePredicateTypeTarget::TypeOrConstParam(param_id), + bound: bound.clone(), + }); + } + } + if let TypeRef::Macro(mc) = type_ref { + let macro_call = mc.to_node(db.upcast()); + match expander.enter_expand::<ast::Type>(db, macro_call) { + Ok(ExpandResult { value: Some((mark, expanded)), .. }) => { + let ctx = LowerCtx::new(db, expander.current_file_id()); + let type_ref = TypeRef::from_ast(&ctx, expanded); + self.fill_implicit_impl_trait_args(db, expander, &type_ref); + expander.exit(db, mark); + } + _ => {} + } + } + }); + } + + pub(crate) fn shrink_to_fit(&mut self) { + let Self { lifetimes, type_or_consts: types, where_predicates } = self; + lifetimes.shrink_to_fit(); + types.shrink_to_fit(); + where_predicates.shrink_to_fit(); + } + + pub fn find_type_by_name(&self, name: &Name, parent: GenericDefId) -> Option<TypeParamId> { + self.type_or_consts.iter().find_map(|(id, p)| { + if p.name().as_ref() == Some(&name) && p.type_param().is_some() { + Some(TypeParamId::from_unchecked(TypeOrConstParamId { local_id: id, parent })) + } else { + None + } + }) + } + + pub fn find_const_by_name(&self, name: &Name, parent: GenericDefId) -> Option<ConstParamId> { + self.type_or_consts.iter().find_map(|(id, p)| { + if p.name().as_ref() == Some(&name) && p.const_param().is_some() { + Some(ConstParamId::from_unchecked(TypeOrConstParamId { local_id: id, parent })) + } else { + None + } + }) + } + + pub fn find_trait_self_param(&self) -> Option<LocalTypeOrConstParamId> { + self.type_or_consts.iter().find_map(|(id, p)| { + matches!( + p, + TypeOrConstParamData::TypeParamData(TypeParamData { + provenance: TypeParamProvenance::TraitSelf, + .. + }) + ) + .then(|| id) + }) + } +} + +fn file_id_and_params_of( + def: GenericDefId, + db: &dyn DefDatabase, +) -> (HirFileId, Option<ast::GenericParamList>) { + match def { + GenericDefId::FunctionId(it) => { + let src = it.lookup(db).source(db); + (src.file_id, src.value.generic_param_list()) + } + GenericDefId::AdtId(AdtId::StructId(it)) => { + let src = it.lookup(db).source(db); + (src.file_id, src.value.generic_param_list()) + } + GenericDefId::AdtId(AdtId::UnionId(it)) => { + let src = it.lookup(db).source(db); + (src.file_id, src.value.generic_param_list()) + } + GenericDefId::AdtId(AdtId::EnumId(it)) => { + let src = it.lookup(db).source(db); + (src.file_id, src.value.generic_param_list()) + } + GenericDefId::TraitId(it) => { + let src = it.lookup(db).source(db); + (src.file_id, src.value.generic_param_list()) + } + GenericDefId::TypeAliasId(it) => { + let src = it.lookup(db).source(db); + (src.file_id, src.value.generic_param_list()) + } + GenericDefId::ImplId(it) => { + let src = it.lookup(db).source(db); + (src.file_id, src.value.generic_param_list()) + } + // We won't be using this ID anyway + GenericDefId::EnumVariantId(_) | GenericDefId::ConstId(_) => (FileId(!0).into(), None), + } +} + +impl HasChildSource<LocalTypeOrConstParamId> for GenericDefId { + type Value = Either<ast::TypeOrConstParam, ast::Trait>; + fn child_source( + &self, + db: &dyn DefDatabase, + ) -> InFile<ArenaMap<LocalTypeOrConstParamId, Self::Value>> { + let generic_params = db.generic_params(*self); + let mut idx_iter = generic_params.type_or_consts.iter().map(|(idx, _)| idx); + + let (file_id, generic_params_list) = file_id_and_params_of(*self, db); + + let mut params = ArenaMap::default(); + + // For traits the first type index is `Self`, we need to add it before the other params. + if let GenericDefId::TraitId(id) = *self { + let trait_ref = id.lookup(db).source(db).value; + let idx = idx_iter.next().unwrap(); + params.insert(idx, Either::Right(trait_ref)) + } + + if let Some(generic_params_list) = generic_params_list { + for (idx, ast_param) in idx_iter.zip(generic_params_list.type_or_const_params()) { + params.insert(idx, Either::Left(ast_param)); + } + } + + InFile::new(file_id, params) + } +} + +impl HasChildSource<LocalLifetimeParamId> for GenericDefId { + type Value = ast::LifetimeParam; + fn child_source( + &self, + db: &dyn DefDatabase, + ) -> InFile<ArenaMap<LocalLifetimeParamId, Self::Value>> { + let generic_params = db.generic_params(*self); + let idx_iter = generic_params.lifetimes.iter().map(|(idx, _)| idx); + + let (file_id, generic_params_list) = file_id_and_params_of(*self, db); + + let mut params = ArenaMap::default(); + + if let Some(generic_params_list) = generic_params_list { + for (idx, ast_param) in idx_iter.zip(generic_params_list.lifetime_params()) { + params.insert(idx, ast_param); + } + } + + InFile::new(file_id, params) + } +} + +impl ChildBySource for GenericDefId { + fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) { + let (gfile_id, generic_params_list) = file_id_and_params_of(*self, db); + if gfile_id != file_id { + return; + } + + let generic_params = db.generic_params(*self); + let mut toc_idx_iter = generic_params.type_or_consts.iter().map(|(idx, _)| idx); + let lts_idx_iter = generic_params.lifetimes.iter().map(|(idx, _)| idx); + + // For traits the first type index is `Self`, skip it. + if let GenericDefId::TraitId(_) = *self { + toc_idx_iter.next().unwrap(); // advance_by(1); + } + + if let Some(generic_params_list) = generic_params_list { + for (local_id, ast_param) in + toc_idx_iter.zip(generic_params_list.type_or_const_params()) + { + let id = TypeOrConstParamId { parent: *self, local_id }; + match ast_param { + ast::TypeOrConstParam::Type(a) => res[keys::TYPE_PARAM].insert(a, id), + ast::TypeOrConstParam::Const(a) => res[keys::CONST_PARAM].insert(a, id), + } + } + for (local_id, ast_param) in lts_idx_iter.zip(generic_params_list.lifetime_params()) { + let id = LifetimeParamId { parent: *self, local_id }; + res[keys::LIFETIME_PARAM].insert(ast_param, id); + } + } + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs b/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs new file mode 100644 index 000000000..688055e43 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs @@ -0,0 +1,1108 @@ +//! A map of all publicly exported items in a crate. + +use std::{fmt, hash::BuildHasherDefault, sync::Arc}; + +use base_db::CrateId; +use fst::{self, Streamer}; +use hir_expand::name::Name; +use indexmap::{map::Entry, IndexMap}; +use itertools::Itertools; +use rustc_hash::{FxHashSet, FxHasher}; + +use crate::{ + db::DefDatabase, item_scope::ItemInNs, visibility::Visibility, AssocItemId, ModuleDefId, + ModuleId, TraitId, +}; + +type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>; + +/// Item import details stored in the `ImportMap`. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct ImportInfo { + /// A path that can be used to import the item, relative to the crate's root. + pub path: ImportPath, + /// The module containing this item. + pub container: ModuleId, + /// Whether the import is a trait associated item or not. + pub is_trait_assoc_item: bool, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct ImportPath { + pub segments: Vec<Name>, +} + +impl fmt::Display for ImportPath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.segments.iter().format("::"), f) + } +} + +impl ImportPath { + fn len(&self) -> usize { + self.segments.len() + } +} + +/// A map from publicly exported items to the path needed to import/name them from a downstream +/// crate. +/// +/// Reexports of items are taken into account, ie. if something is exported under multiple +/// names, the one with the shortest import path will be used. +/// +/// Note that all paths are relative to the containing crate's root, so the crate name still needs +/// to be prepended to the `ModPath` before the path is valid. +#[derive(Default)] +pub struct ImportMap { + map: FxIndexMap<ItemInNs, ImportInfo>, + + /// List of keys stored in `map`, sorted lexicographically by their `ModPath`. Indexed by the + /// values returned by running `fst`. + /// + /// Since a path can refer to multiple items due to namespacing, we store all items with the + /// same path right after each other. This allows us to find all items after the FST gives us + /// the index of the first one. + importables: Vec<ItemInNs>, + fst: fst::Map<Vec<u8>>, +} + +impl ImportMap { + pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> { + let _p = profile::span("import_map_query"); + + let mut import_map = collect_import_map(db, krate); + + let mut importables = import_map + .map + .iter() + .map(|(item, info)| (item, fst_path(&info.path))) + .collect::<Vec<_>>(); + importables.sort_by(|(_, fst_path), (_, fst_path2)| fst_path.cmp(fst_path2)); + + // Build the FST, taking care not to insert duplicate values. + + let mut builder = fst::MapBuilder::memory(); + let mut last_batch_start = 0; + + for idx in 0..importables.len() { + let key = &importables[last_batch_start].1; + if let Some((_, fst_path)) = importables.get(idx + 1) { + if key == fst_path { + continue; + } + } + + let _ = builder.insert(key, last_batch_start as u64); + + last_batch_start = idx + 1; + } + + import_map.fst = builder.into_map(); + import_map.importables = importables.iter().map(|&(&item, _)| item).collect(); + + Arc::new(import_map) + } + + /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root. + pub fn path_of(&self, item: ItemInNs) -> Option<&ImportPath> { + self.import_info_for(item).map(|it| &it.path) + } + + pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> { + self.map.get(&item) + } + + fn collect_trait_assoc_items( + &mut self, + db: &dyn DefDatabase, + tr: TraitId, + is_type_in_ns: bool, + original_import_info: &ImportInfo, + ) { + let _p = profile::span("collect_trait_assoc_items"); + for (assoc_item_name, item) in &db.trait_data(tr).items { + let module_def_id = match item { + AssocItemId::FunctionId(f) => ModuleDefId::from(*f), + AssocItemId::ConstId(c) => ModuleDefId::from(*c), + // cannot use associated type aliases directly: need a `<Struct as Trait>::TypeAlias` + // qualifier, ergo no need to store it for imports in import_map + AssocItemId::TypeAliasId(_) => { + cov_mark::hit!(type_aliases_ignored); + continue; + } + }; + let assoc_item = if is_type_in_ns { + ItemInNs::Types(module_def_id) + } else { + ItemInNs::Values(module_def_id) + }; + + let mut assoc_item_info = original_import_info.clone(); + assoc_item_info.path.segments.push(assoc_item_name.to_owned()); + assoc_item_info.is_trait_assoc_item = true; + self.map.insert(assoc_item, assoc_item_info); + } + } +} + +fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> ImportMap { + let _p = profile::span("collect_import_map"); + + let def_map = db.crate_def_map(krate); + let mut import_map = ImportMap::default(); + + // We look only into modules that are public(ly reexported), starting with the crate root. + let empty = ImportPath { segments: vec![] }; + let root = def_map.module_id(def_map.root()); + let mut worklist = vec![(root, empty)]; + while let Some((module, mod_path)) = worklist.pop() { + let ext_def_map; + let mod_data = if module.krate == krate { + &def_map[module.local_id] + } else { + // The crate might reexport a module defined in another crate. + ext_def_map = module.def_map(db); + &ext_def_map[module.local_id] + }; + + let visible_items = mod_data.scope.entries().filter_map(|(name, per_ns)| { + let per_ns = per_ns.filter_visibility(|vis| vis == Visibility::Public); + if per_ns.is_none() { None } else { Some((name, per_ns)) } + }); + + for (name, per_ns) in visible_items { + let mk_path = || { + let mut path = mod_path.clone(); + path.segments.push(name.clone()); + path + }; + + for item in per_ns.iter_items() { + let path = mk_path(); + let path_len = path.len(); + let import_info = + ImportInfo { path, container: module, is_trait_assoc_item: false }; + + if let Some(ModuleDefId::TraitId(tr)) = item.as_module_def_id() { + import_map.collect_trait_assoc_items( + db, + tr, + matches!(item, ItemInNs::Types(_)), + &import_info, + ); + } + + match import_map.map.entry(item) { + Entry::Vacant(entry) => { + entry.insert(import_info); + } + Entry::Occupied(mut entry) => { + // If the new path is shorter, prefer that one. + if path_len < entry.get().path.len() { + *entry.get_mut() = import_info; + } else { + continue; + } + } + } + + // If we've just added a path to a module, descend into it. We might traverse + // modules multiple times, but only if the new path to it is shorter than the + // first (else we `continue` above). + if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() { + worklist.push((mod_id, mk_path())); + } + } + } + } + + import_map +} + +impl PartialEq for ImportMap { + fn eq(&self, other: &Self) -> bool { + // `fst` and `importables` are built from `map`, so we don't need to compare them. + self.map == other.map + } +} + +impl Eq for ImportMap {} + +impl fmt::Debug for ImportMap { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut importable_paths: Vec<_> = self + .map + .iter() + .map(|(item, info)| { + let ns = match item { + ItemInNs::Types(_) => "t", + ItemInNs::Values(_) => "v", + ItemInNs::Macros(_) => "m", + }; + format!("- {} ({})", info.path, ns) + }) + .collect(); + + importable_paths.sort(); + f.write_str(&importable_paths.join("\n")) + } +} + +fn fst_path(path: &ImportPath) -> String { + let _p = profile::span("fst_path"); + let mut s = path.to_string(); + s.make_ascii_lowercase(); + s +} + +#[derive(Debug, Eq, PartialEq, Hash)] +pub enum ImportKind { + Module, + Function, + Adt, + EnumVariant, + Const, + Static, + Trait, + TypeAlias, + BuiltinType, + AssociatedItem, + Macro, +} + +/// A way to match import map contents against the search query. +#[derive(Debug)] +pub enum SearchMode { + /// Import map entry should strictly match the query string. + Equals, + /// Import map entry should contain the query string. + Contains, + /// Import map entry should contain all letters from the query string, + /// in the same order, but not necessary adjacent. + Fuzzy, +} + +#[derive(Debug)] +pub struct Query { + query: String, + lowercased: String, + name_only: bool, + assoc_items_only: bool, + search_mode: SearchMode, + case_sensitive: bool, + limit: usize, + exclude_import_kinds: FxHashSet<ImportKind>, +} + +impl Query { + pub fn new(query: String) -> Self { + let lowercased = query.to_lowercase(); + Self { + query, + lowercased, + name_only: false, + assoc_items_only: false, + search_mode: SearchMode::Contains, + case_sensitive: false, + limit: usize::max_value(), + exclude_import_kinds: FxHashSet::default(), + } + } + + /// Matches entries' names only, ignoring the rest of + /// the qualifier. + /// Example: for `std::marker::PhantomData`, the name is `PhantomData`. + pub fn name_only(self) -> Self { + Self { name_only: true, ..self } + } + + /// Matches only the entries that are associated items, ignoring the rest. + pub fn assoc_items_only(self) -> Self { + Self { assoc_items_only: true, ..self } + } + + /// Specifies the way to search for the entries using the query. + pub fn search_mode(self, search_mode: SearchMode) -> Self { + Self { search_mode, ..self } + } + + /// Limits the returned number of items to `limit`. + pub fn limit(self, limit: usize) -> Self { + Self { limit, ..self } + } + + /// Respect casing of the query string when matching. + pub fn case_sensitive(self) -> Self { + Self { case_sensitive: true, ..self } + } + + /// Do not include imports of the specified kind in the search results. + pub fn exclude_import_kind(mut self, import_kind: ImportKind) -> Self { + self.exclude_import_kinds.insert(import_kind); + self + } + + fn import_matches(&self, import: &ImportInfo, enforce_lowercase: bool) -> bool { + let _p = profile::span("import_map::Query::import_matches"); + if import.is_trait_assoc_item { + if self.exclude_import_kinds.contains(&ImportKind::AssociatedItem) { + return false; + } + } else if self.assoc_items_only { + return false; + } + + let mut input = if import.is_trait_assoc_item || self.name_only { + import.path.segments.last().unwrap().to_string() + } else { + import.path.to_string() + }; + if enforce_lowercase || !self.case_sensitive { + input.make_ascii_lowercase(); + } + + let query_string = + if !enforce_lowercase && self.case_sensitive { &self.query } else { &self.lowercased }; + + match self.search_mode { + SearchMode::Equals => &input == query_string, + SearchMode::Contains => input.contains(query_string), + SearchMode::Fuzzy => { + let mut unchecked_query_chars = query_string.chars(); + let mut mismatching_query_char = unchecked_query_chars.next(); + + for input_char in input.chars() { + match mismatching_query_char { + None => return true, + Some(matching_query_char) if matching_query_char == input_char => { + mismatching_query_char = unchecked_query_chars.next(); + } + _ => (), + } + } + mismatching_query_char.is_none() + } + } + } +} + +/// Searches dependencies of `krate` for an importable path matching `query`. +/// +/// This returns a list of items that could be imported from dependencies of `krate`. +pub fn search_dependencies<'a>( + db: &'a dyn DefDatabase, + krate: CrateId, + query: Query, +) -> FxHashSet<ItemInNs> { + let _p = profile::span("search_dependencies").detail(|| format!("{:?}", query)); + + let graph = db.crate_graph(); + let import_maps: Vec<_> = + graph[krate].dependencies.iter().map(|dep| db.import_map(dep.crate_id)).collect(); + + let automaton = fst::automaton::Subsequence::new(&query.lowercased); + + let mut op = fst::map::OpBuilder::new(); + for map in &import_maps { + op = op.add(map.fst.search(&automaton)); + } + + let mut stream = op.union(); + + let mut all_indexed_values = FxHashSet::default(); + while let Some((_, indexed_values)) = stream.next() { + all_indexed_values.extend(indexed_values.iter().copied()); + } + + let mut res = FxHashSet::default(); + for indexed_value in all_indexed_values { + let import_map = &import_maps[indexed_value.index]; + let importables = &import_map.importables[indexed_value.value as usize..]; + + let common_importable_data = &import_map.map[&importables[0]]; + if !query.import_matches(common_importable_data, true) { + continue; + } + + // Path shared by the importable items in this group. + let common_importables_path_fst = fst_path(&common_importable_data.path); + // Add the items from this `ModPath` group. Those are all subsequent items in + // `importables` whose paths match `path`. + let iter = importables + .iter() + .copied() + .take_while(|item| common_importables_path_fst == fst_path(&import_map.map[item].path)) + .filter(|&item| match item_import_kind(item) { + Some(import_kind) => !query.exclude_import_kinds.contains(&import_kind), + None => true, + }) + .filter(|item| { + !query.case_sensitive // we've already checked the common importables path case-insensitively + || query.import_matches(&import_map.map[item], false) + }); + res.extend(iter); + + if res.len() >= query.limit { + return res; + } + } + + res +} + +fn item_import_kind(item: ItemInNs) -> Option<ImportKind> { + Some(match item.as_module_def_id()? { + ModuleDefId::ModuleId(_) => ImportKind::Module, + ModuleDefId::FunctionId(_) => ImportKind::Function, + ModuleDefId::AdtId(_) => ImportKind::Adt, + ModuleDefId::EnumVariantId(_) => ImportKind::EnumVariant, + ModuleDefId::ConstId(_) => ImportKind::Const, + ModuleDefId::StaticId(_) => ImportKind::Static, + ModuleDefId::TraitId(_) => ImportKind::Trait, + ModuleDefId::TypeAliasId(_) => ImportKind::TypeAlias, + ModuleDefId::BuiltinType(_) => ImportKind::BuiltinType, + ModuleDefId::MacroId(_) => ImportKind::Macro, + }) +} + +#[cfg(test)] +mod tests { + use base_db::{fixture::WithFixture, SourceDatabase, Upcast}; + use expect_test::{expect, Expect}; + + use crate::{test_db::TestDB, ItemContainerId, Lookup}; + + use super::*; + + fn check_search(ra_fixture: &str, crate_name: &str, query: Query, expect: Expect) { + let db = TestDB::with_files(ra_fixture); + let crate_graph = db.crate_graph(); + let krate = crate_graph + .iter() + .find(|krate| { + crate_graph[*krate].display_name.as_ref().map(|n| n.to_string()) + == Some(crate_name.to_string()) + }) + .unwrap(); + + let actual = search_dependencies(db.upcast(), krate, query) + .into_iter() + .filter_map(|dependency| { + let dependency_krate = dependency.krate(db.upcast())?; + let dependency_imports = db.import_map(dependency_krate); + + let (path, mark) = match assoc_item_path(&db, &dependency_imports, dependency) { + Some(assoc_item_path) => (assoc_item_path, "a"), + None => ( + dependency_imports.path_of(dependency)?.to_string(), + match dependency { + ItemInNs::Types(ModuleDefId::FunctionId(_)) + | ItemInNs::Values(ModuleDefId::FunctionId(_)) => "f", + ItemInNs::Types(_) => "t", + ItemInNs::Values(_) => "v", + ItemInNs::Macros(_) => "m", + }, + ), + }; + + Some(format!( + "{}::{} ({})\n", + crate_graph[dependency_krate].display_name.as_ref()?, + path, + mark + )) + }) + // HashSet iteration order isn't defined - it's different on + // x86_64 and i686 at the very least + .sorted() + .collect::<String>(); + expect.assert_eq(&actual) + } + + fn assoc_item_path( + db: &dyn DefDatabase, + dependency_imports: &ImportMap, + dependency: ItemInNs, + ) -> Option<String> { + let dependency_assoc_item_id = match dependency { + ItemInNs::Types(ModuleDefId::FunctionId(id)) + | ItemInNs::Values(ModuleDefId::FunctionId(id)) => AssocItemId::from(id), + ItemInNs::Types(ModuleDefId::ConstId(id)) + | ItemInNs::Values(ModuleDefId::ConstId(id)) => AssocItemId::from(id), + ItemInNs::Types(ModuleDefId::TypeAliasId(id)) + | ItemInNs::Values(ModuleDefId::TypeAliasId(id)) => AssocItemId::from(id), + _ => return None, + }; + + let trait_ = assoc_to_trait(db, dependency)?; + if let ModuleDefId::TraitId(tr) = trait_.as_module_def_id()? { + let trait_data = db.trait_data(tr); + let assoc_item_name = + trait_data.items.iter().find_map(|(assoc_item_name, assoc_item_id)| { + if &dependency_assoc_item_id == assoc_item_id { + Some(assoc_item_name) + } else { + None + } + })?; + return Some(format!("{}::{}", dependency_imports.path_of(trait_)?, assoc_item_name)); + } + None + } + + fn assoc_to_trait(db: &dyn DefDatabase, item: ItemInNs) -> Option<ItemInNs> { + let assoc: AssocItemId = match item { + ItemInNs::Types(it) | ItemInNs::Values(it) => match it { + ModuleDefId::TypeAliasId(it) => it.into(), + ModuleDefId::FunctionId(it) => it.into(), + ModuleDefId::ConstId(it) => it.into(), + _ => return None, + }, + _ => return None, + }; + + let container = match assoc { + AssocItemId::FunctionId(it) => it.lookup(db).container, + AssocItemId::ConstId(it) => it.lookup(db).container, + AssocItemId::TypeAliasId(it) => it.lookup(db).container, + }; + + match container { + ItemContainerId::TraitId(it) => Some(ItemInNs::Types(it.into())), + _ => None, + } + } + + fn check(ra_fixture: &str, expect: Expect) { + let db = TestDB::with_files(ra_fixture); + let crate_graph = db.crate_graph(); + + let actual = crate_graph + .iter() + .filter_map(|krate| { + let cdata = &crate_graph[krate]; + let name = cdata.display_name.as_ref()?; + + let map = db.import_map(krate); + + Some(format!("{}:\n{:?}\n", name, map)) + }) + .sorted() + .collect::<String>(); + + expect.assert_eq(&actual) + } + + #[test] + fn smoke() { + check( + r" + //- /main.rs crate:main deps:lib + + mod private { + pub use lib::Pub; + pub struct InPrivateModule; + } + + pub mod publ1 { + use lib::Pub; + } + + pub mod real_pub { + pub use lib::Pub; + } + pub mod real_pu2 { // same path length as above + pub use lib::Pub; + } + + //- /lib.rs crate:lib + pub struct Pub {} + pub struct Pub2; // t + v + struct Priv; + ", + expect![[r#" + lib: + - Pub (t) + - Pub2 (t) + - Pub2 (v) + main: + - publ1 (t) + - real_pu2 (t) + - real_pub (t) + - real_pub::Pub (t) + "#]], + ); + } + + #[test] + fn prefers_shortest_path() { + check( + r" + //- /main.rs crate:main + + pub mod sub { + pub mod subsub { + pub struct Def {} + } + + pub use super::sub::subsub::Def; + } + ", + expect![[r#" + main: + - sub (t) + - sub::Def (t) + - sub::subsub (t) + "#]], + ); + } + + #[test] + fn type_reexport_cross_crate() { + // Reexports need to be visible from a crate, even if the original crate exports the item + // at a shorter path. + check( + r" + //- /main.rs crate:main deps:lib + pub mod m { + pub use lib::S; + } + //- /lib.rs crate:lib + pub struct S; + ", + expect![[r#" + lib: + - S (t) + - S (v) + main: + - m (t) + - m::S (t) + - m::S (v) + "#]], + ); + } + + #[test] + fn macro_reexport() { + check( + r" + //- /main.rs crate:main deps:lib + pub mod m { + pub use lib::pub_macro; + } + //- /lib.rs crate:lib + #[macro_export] + macro_rules! pub_macro { + () => {}; + } + ", + expect![[r#" + lib: + - pub_macro (m) + main: + - m (t) + - m::pub_macro (m) + "#]], + ); + } + + #[test] + fn module_reexport() { + // Reexporting modules from a dependency adds all contents to the import map. + check( + r" + //- /main.rs crate:main deps:lib + pub use lib::module as reexported_module; + //- /lib.rs crate:lib + pub mod module { + pub struct S; + } + ", + expect![[r#" + lib: + - module (t) + - module::S (t) + - module::S (v) + main: + - reexported_module (t) + - reexported_module::S (t) + - reexported_module::S (v) + "#]], + ); + } + + #[test] + fn cyclic_module_reexport() { + // A cyclic reexport does not hang. + check( + r" + //- /lib.rs crate:lib + pub mod module { + pub struct S; + pub use super::sub::*; + } + + pub mod sub { + pub use super::module; + } + ", + expect![[r#" + lib: + - module (t) + - module::S (t) + - module::S (v) + - sub (t) + "#]], + ); + } + + #[test] + fn private_macro() { + check( + r" + //- /lib.rs crate:lib + macro_rules! private_macro { + () => {}; + } + ", + expect![[r#" + lib: + + "#]], + ); + } + + #[test] + fn namespacing() { + check( + r" + //- /lib.rs crate:lib + pub struct Thing; // t + v + #[macro_export] + macro_rules! Thing { // m + () => {}; + } + ", + expect![[r#" + lib: + - Thing (m) + - Thing (t) + - Thing (v) + "#]], + ); + + check( + r" + //- /lib.rs crate:lib + pub mod Thing {} // t + #[macro_export] + macro_rules! Thing { // m + () => {}; + } + ", + expect![[r#" + lib: + - Thing (m) + - Thing (t) + "#]], + ); + } + + #[test] + fn fuzzy_import_trait_and_assoc_items() { + cov_mark::check!(type_aliases_ignored); + let ra_fixture = r#" + //- /main.rs crate:main deps:dep + //- /dep.rs crate:dep + pub mod fmt { + pub trait Display { + type FmtTypeAlias; + const FMT_CONST: bool; + + fn format_function(); + fn format_method(&self); + } + } + "#; + + check_search( + ra_fixture, + "main", + Query::new("fmt".to_string()).search_mode(SearchMode::Fuzzy), + expect![[r#" + dep::fmt (t) + dep::fmt::Display (t) + dep::fmt::Display::FMT_CONST (a) + dep::fmt::Display::format_function (a) + dep::fmt::Display::format_method (a) + "#]], + ); + } + + #[test] + fn assoc_items_filtering() { + let ra_fixture = r#" + //- /main.rs crate:main deps:dep + //- /dep.rs crate:dep + pub mod fmt { + pub trait Display { + type FmtTypeAlias; + const FMT_CONST: bool; + + fn format_function(); + fn format_method(&self); + } + } + "#; + + check_search( + ra_fixture, + "main", + Query::new("fmt".to_string()).search_mode(SearchMode::Fuzzy).assoc_items_only(), + expect![[r#" + dep::fmt::Display::FMT_CONST (a) + dep::fmt::Display::format_function (a) + dep::fmt::Display::format_method (a) + "#]], + ); + + check_search( + ra_fixture, + "main", + Query::new("fmt".to_string()) + .search_mode(SearchMode::Fuzzy) + .exclude_import_kind(ImportKind::AssociatedItem), + expect![[r#" + dep::fmt (t) + dep::fmt::Display (t) + "#]], + ); + + check_search( + ra_fixture, + "main", + Query::new("fmt".to_string()) + .search_mode(SearchMode::Fuzzy) + .assoc_items_only() + .exclude_import_kind(ImportKind::AssociatedItem), + expect![[r#""#]], + ); + } + + #[test] + fn search_mode() { + let ra_fixture = r#" + //- /main.rs crate:main deps:dep + //- /dep.rs crate:dep deps:tdep + use tdep::fmt as fmt_dep; + pub mod fmt { + pub trait Display { + fn fmt(); + } + } + #[macro_export] + macro_rules! Fmt { + () => {}; + } + pub struct Fmt; + + pub fn format() {} + pub fn no() {} + + //- /tdep.rs crate:tdep + pub mod fmt { + pub struct NotImportableFromMain; + } + "#; + + check_search( + ra_fixture, + "main", + Query::new("fmt".to_string()).search_mode(SearchMode::Fuzzy), + expect![[r#" + dep::Fmt (m) + dep::Fmt (t) + dep::Fmt (v) + dep::fmt (t) + dep::fmt::Display (t) + dep::fmt::Display::fmt (a) + dep::format (f) + "#]], + ); + + check_search( + ra_fixture, + "main", + Query::new("fmt".to_string()).search_mode(SearchMode::Equals), + expect![[r#" + dep::Fmt (m) + dep::Fmt (t) + dep::Fmt (v) + dep::fmt (t) + dep::fmt::Display::fmt (a) + "#]], + ); + + check_search( + ra_fixture, + "main", + Query::new("fmt".to_string()).search_mode(SearchMode::Contains), + expect![[r#" + dep::Fmt (m) + dep::Fmt (t) + dep::Fmt (v) + dep::fmt (t) + dep::fmt::Display (t) + dep::fmt::Display::fmt (a) + "#]], + ); + } + + #[test] + fn name_only() { + let ra_fixture = r#" + //- /main.rs crate:main deps:dep + //- /dep.rs crate:dep deps:tdep + use tdep::fmt as fmt_dep; + pub mod fmt { + pub trait Display { + fn fmt(); + } + } + #[macro_export] + macro_rules! Fmt { + () => {}; + } + pub struct Fmt; + + pub fn format() {} + pub fn no() {} + + //- /tdep.rs crate:tdep + pub mod fmt { + pub struct NotImportableFromMain; + } + "#; + + check_search( + ra_fixture, + "main", + Query::new("fmt".to_string()), + expect![[r#" + dep::Fmt (m) + dep::Fmt (t) + dep::Fmt (v) + dep::fmt (t) + dep::fmt::Display (t) + dep::fmt::Display::fmt (a) + "#]], + ); + + check_search( + ra_fixture, + "main", + Query::new("fmt".to_string()).name_only(), + expect![[r#" + dep::Fmt (m) + dep::Fmt (t) + dep::Fmt (v) + dep::fmt (t) + dep::fmt::Display::fmt (a) + "#]], + ); + } + + #[test] + fn search_casing() { + let ra_fixture = r#" + //- /main.rs crate:main deps:dep + //- /dep.rs crate:dep + + pub struct fmt; + pub struct FMT; + "#; + + check_search( + ra_fixture, + "main", + Query::new("FMT".to_string()), + expect![[r#" + dep::FMT (t) + dep::FMT (v) + dep::fmt (t) + dep::fmt (v) + "#]], + ); + + check_search( + ra_fixture, + "main", + Query::new("FMT".to_string()).case_sensitive(), + expect![[r#" + dep::FMT (t) + dep::FMT (v) + "#]], + ); + } + + #[test] + fn search_limit() { + check_search( + r#" + //- /main.rs crate:main deps:dep + //- /dep.rs crate:dep + pub mod fmt { + pub trait Display { + fn fmt(); + } + } + #[macro_export] + macro_rules! Fmt { + () => {}; + } + pub struct Fmt; + + pub fn format() {} + pub fn no() {} + "#, + "main", + Query::new("".to_string()).limit(2), + expect![[r#" + dep::Fmt (m) + dep::Fmt (t) + dep::Fmt (v) + dep::fmt (t) + "#]], + ); + } + + #[test] + fn search_exclusions() { + let ra_fixture = r#" + //- /main.rs crate:main deps:dep + //- /dep.rs crate:dep + + pub struct fmt; + pub struct FMT; + "#; + + check_search( + ra_fixture, + "main", + Query::new("FMT".to_string()), + expect![[r#" + dep::FMT (t) + dep::FMT (v) + dep::fmt (t) + dep::fmt (v) + "#]], + ); + + check_search( + ra_fixture, + "main", + Query::new("FMT".to_string()).exclude_import_kind(ImportKind::Adt), + expect![[r#""#]], + ); + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/intern.rs b/src/tools/rust-analyzer/crates/hir-def/src/intern.rs new file mode 100644 index 000000000..f08521a34 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/intern.rs @@ -0,0 +1,227 @@ +//! Global `Arc`-based object interning infrastructure. +//! +//! Eventually this should probably be replaced with salsa-based interning. + +use std::{ + fmt::{self, Debug, Display}, + hash::{BuildHasherDefault, Hash, Hasher}, + ops::Deref, + sync::Arc, +}; + +use dashmap::{DashMap, SharedValue}; +use hashbrown::HashMap; +use once_cell::sync::OnceCell; +use rustc_hash::FxHasher; + +use crate::generics::GenericParams; + +type InternMap<T> = DashMap<Arc<T>, (), BuildHasherDefault<FxHasher>>; +type Guard<T> = dashmap::RwLockWriteGuard< + 'static, + HashMap<Arc<T>, SharedValue<()>, BuildHasherDefault<FxHasher>>, +>; + +pub struct Interned<T: Internable + ?Sized> { + arc: Arc<T>, +} + +impl<T: Internable> Interned<T> { + pub fn new(obj: T) -> Self { + match Interned::lookup(&obj) { + Ok(this) => this, + Err(shard) => { + let arc = Arc::new(obj); + Self::alloc(arc, shard) + } + } + } +} + +impl<T: Internable + ?Sized> Interned<T> { + fn lookup(obj: &T) -> Result<Self, Guard<T>> { + let storage = T::storage().get(); + let shard_idx = storage.determine_map(obj); + let shard = &storage.shards()[shard_idx]; + let shard = shard.write(); + + // Atomically, + // - check if `obj` is already in the map + // - if so, clone its `Arc` and return it + // - if not, box it up, insert it, and return a clone + // This needs to be atomic (locking the shard) to avoid races with other thread, which could + // insert the same object between us looking it up and inserting it. + + // FIXME: avoid double lookup/hashing by using raw entry API (once stable, or when + // hashbrown can be plugged into dashmap) + match shard.get_key_value(obj) { + Some((arc, _)) => Ok(Self { arc: arc.clone() }), + None => Err(shard), + } + } + + fn alloc(arc: Arc<T>, mut shard: Guard<T>) -> Self { + let arc2 = arc.clone(); + + shard.insert(arc2, SharedValue::new(())); + + Self { arc } + } +} + +impl Interned<str> { + pub fn new_str(s: &str) -> Self { + match Interned::lookup(s) { + Ok(this) => this, + Err(shard) => { + let arc = Arc::<str>::from(s); + Self::alloc(arc, shard) + } + } + } +} + +impl<T: Internable + ?Sized> Drop for Interned<T> { + #[inline] + fn drop(&mut self) { + // When the last `Ref` is dropped, remove the object from the global map. + if Arc::strong_count(&self.arc) == 2 { + // Only `self` and the global map point to the object. + + self.drop_slow(); + } + } +} + +impl<T: Internable + ?Sized> Interned<T> { + #[cold] + fn drop_slow(&mut self) { + let storage = T::storage().get(); + let shard_idx = storage.determine_map(&self.arc); + let shard = &storage.shards()[shard_idx]; + let mut shard = shard.write(); + + // FIXME: avoid double lookup + let (arc, _) = shard.get_key_value(&self.arc).expect("interned value removed prematurely"); + + if Arc::strong_count(arc) != 2 { + // Another thread has interned another copy + return; + } + + shard.remove(&self.arc); + + // Shrink the backing storage if the shard is less than 50% occupied. + if shard.len() * 2 < shard.capacity() { + shard.shrink_to_fit(); + } + } +} + +/// Compares interned `Ref`s using pointer equality. +impl<T: Internable> PartialEq for Interned<T> { + // NOTE: No `?Sized` because `ptr_eq` doesn't work right with trait objects. + + #[inline] + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.arc, &other.arc) + } +} + +impl<T: Internable> Eq for Interned<T> {} + +impl PartialEq for Interned<str> { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.arc, &other.arc) + } +} + +impl Eq for Interned<str> {} + +impl<T: Internable + ?Sized> Hash for Interned<T> { + fn hash<H: Hasher>(&self, state: &mut H) { + // NOTE: Cast disposes vtable pointer / slice/str length. + state.write_usize(Arc::as_ptr(&self.arc) as *const () as usize) + } +} + +impl<T: Internable + ?Sized> AsRef<T> for Interned<T> { + #[inline] + fn as_ref(&self) -> &T { + &self.arc + } +} + +impl<T: Internable + ?Sized> Deref for Interned<T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.arc + } +} + +impl<T: Internable + ?Sized> Clone for Interned<T> { + fn clone(&self) -> Self { + Self { arc: self.arc.clone() } + } +} + +impl<T: Debug + Internable + ?Sized> Debug for Interned<T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (*self.arc).fmt(f) + } +} + +impl<T: Display + Internable + ?Sized> Display for Interned<T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (*self.arc).fmt(f) + } +} + +pub struct InternStorage<T: ?Sized> { + map: OnceCell<InternMap<T>>, +} + +impl<T: ?Sized> InternStorage<T> { + pub const fn new() -> Self { + Self { map: OnceCell::new() } + } +} + +impl<T: Internable + ?Sized> InternStorage<T> { + fn get(&self) -> &InternMap<T> { + self.map.get_or_init(DashMap::default) + } +} + +pub trait Internable: Hash + Eq + 'static { + fn storage() -> &'static InternStorage<Self>; +} + +/// Implements `Internable` for a given list of types, making them usable with `Interned`. +#[macro_export] +#[doc(hidden)] +macro_rules! _impl_internable { + ( $($t:path),+ $(,)? ) => { $( + impl Internable for $t { + fn storage() -> &'static InternStorage<Self> { + static STORAGE: InternStorage<$t> = InternStorage::new(); + &STORAGE + } + } + )+ }; +} + +pub use crate::_impl_internable as impl_internable; + +impl_internable!( + crate::type_ref::TypeRef, + crate::type_ref::TraitRef, + crate::type_ref::TypeBound, + crate::path::ModPath, + crate::path::GenericArgs, + crate::attr::AttrInput, + GenericParams, + str, +); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs new file mode 100644 index 000000000..a11a92204 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs @@ -0,0 +1,464 @@ +//! Describes items defined or visible (ie, imported) in a certain scope. +//! This is shared between modules and blocks. + +use std::collections::hash_map::Entry; + +use base_db::CrateId; +use hir_expand::{name::Name, AstId, MacroCallId}; +use itertools::Itertools; +use once_cell::sync::Lazy; +use profile::Count; +use rustc_hash::{FxHashMap, FxHashSet}; +use smallvec::{smallvec, SmallVec}; +use stdx::format_to; +use syntax::ast; + +use crate::{ + attr::AttrId, db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, + ConstId, HasModule, ImplId, LocalModuleId, MacroId, ModuleDefId, ModuleId, TraitId, +}; + +#[derive(Copy, Clone)] +pub(crate) enum ImportType { + Glob, + Named, +} + +#[derive(Debug, Default)] +pub struct PerNsGlobImports { + types: FxHashSet<(LocalModuleId, Name)>, + values: FxHashSet<(LocalModuleId, Name)>, + macros: FxHashSet<(LocalModuleId, Name)>, +} + +#[derive(Debug, Default, PartialEq, Eq)] +pub struct ItemScope { + _c: Count<Self>, + + /// Defs visible in this scope. This includes `declarations`, but also + /// imports. + types: FxHashMap<Name, (ModuleDefId, Visibility)>, + values: FxHashMap<Name, (ModuleDefId, Visibility)>, + macros: FxHashMap<Name, (MacroId, Visibility)>, + unresolved: FxHashSet<Name>, + + /// The defs declared in this scope. Each def has a single scope where it is + /// declared. + declarations: Vec<ModuleDefId>, + + impls: Vec<ImplId>, + unnamed_consts: Vec<ConstId>, + /// Traits imported via `use Trait as _;`. + unnamed_trait_imports: FxHashMap<TraitId, Visibility>, + /// Macros visible in current module in legacy textual scope + /// + /// For macros invoked by an unqualified identifier like `bar!()`, `legacy_macros` will be searched in first. + /// If it yields no result, then it turns to module scoped `macros`. + /// It macros with name qualified with a path like `crate::foo::bar!()`, `legacy_macros` will be skipped, + /// and only normal scoped `macros` will be searched in. + /// + /// Note that this automatically inherit macros defined textually before the definition of module itself. + /// + /// Module scoped macros will be inserted into `items` instead of here. + // FIXME: Macro shadowing in one module is not properly handled. Non-item place macros will + // be all resolved to the last one defined if shadowing happens. + legacy_macros: FxHashMap<Name, SmallVec<[MacroId; 1]>>, + /// The derive macro invocations in this scope. + attr_macros: FxHashMap<AstId<ast::Item>, MacroCallId>, + /// The derive macro invocations in this scope, keyed by the owner item over the actual derive attributes + /// paired with the derive macro invocations for the specific attribute. + derive_macros: FxHashMap<AstId<ast::Adt>, SmallVec<[DeriveMacroInvocation; 1]>>, +} + +#[derive(Debug, PartialEq, Eq)] +struct DeriveMacroInvocation { + attr_id: AttrId, + attr_call_id: MacroCallId, + derive_call_ids: SmallVec<[Option<MacroCallId>; 1]>, +} + +pub(crate) static BUILTIN_SCOPE: Lazy<FxHashMap<Name, PerNs>> = Lazy::new(|| { + BuiltinType::ALL + .iter() + .map(|(name, ty)| (name.clone(), PerNs::types((*ty).into(), Visibility::Public))) + .collect() +}); + +/// Shadow mode for builtin type which can be shadowed by module. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub(crate) enum BuiltinShadowMode { + /// Prefer user-defined modules (or other types) over builtins. + Module, + /// Prefer builtins over user-defined modules (but not other types). + Other, +} + +/// Legacy macros can only be accessed through special methods like `get_legacy_macros`. +/// Other methods will only resolve values, types and module scoped macros only. +impl ItemScope { + pub fn entries<'a>(&'a self) -> impl Iterator<Item = (&'a Name, PerNs)> + 'a { + // FIXME: shadowing + self.types + .keys() + .chain(self.values.keys()) + .chain(self.macros.keys()) + .chain(self.unresolved.iter()) + .sorted() + .unique() + .map(move |name| (name, self.get(name))) + } + + pub fn declarations(&self) -> impl Iterator<Item = ModuleDefId> + '_ { + self.declarations.iter().copied() + } + + pub fn impls(&self) -> impl Iterator<Item = ImplId> + ExactSizeIterator + '_ { + self.impls.iter().copied() + } + + pub fn values( + &self, + ) -> impl Iterator<Item = (ModuleDefId, Visibility)> + ExactSizeIterator + '_ { + self.values.values().copied() + } + + pub fn types( + &self, + ) -> impl Iterator<Item = (ModuleDefId, Visibility)> + ExactSizeIterator + '_ { + self.types.values().copied() + } + + pub fn unnamed_consts(&self) -> impl Iterator<Item = ConstId> + '_ { + self.unnamed_consts.iter().copied() + } + + /// Iterate over all module scoped macros + pub(crate) fn macros(&self) -> impl Iterator<Item = (&Name, MacroId)> + '_ { + self.entries().filter_map(|(name, def)| def.take_macros().map(|macro_| (name, macro_))) + } + + /// Iterate over all legacy textual scoped macros visible at the end of the module + pub fn legacy_macros(&self) -> impl Iterator<Item = (&Name, &[MacroId])> + '_ { + self.legacy_macros.iter().map(|(name, def)| (name, &**def)) + } + + /// Get a name from current module scope, legacy macros are not included + pub(crate) fn get(&self, name: &Name) -> PerNs { + PerNs { + types: self.types.get(name).copied(), + values: self.values.get(name).copied(), + macros: self.macros.get(name).copied(), + } + } + + pub(crate) fn type_(&self, name: &Name) -> Option<(ModuleDefId, Visibility)> { + self.types.get(name).copied() + } + + /// XXX: this is O(N) rather than O(1), try to not introduce new usages. + pub(crate) fn name_of(&self, item: ItemInNs) -> Option<(&Name, Visibility)> { + let (def, mut iter) = match item { + ItemInNs::Macros(def) => { + return self + .macros + .iter() + .find_map(|(name, &(other_def, vis))| (other_def == def).then(|| (name, vis))); + } + ItemInNs::Types(def) => (def, self.types.iter()), + ItemInNs::Values(def) => (def, self.values.iter()), + }; + iter.find_map(|(name, &(other_def, vis))| (other_def == def).then(|| (name, vis))) + } + + pub(crate) fn traits<'a>(&'a self) -> impl Iterator<Item = TraitId> + 'a { + self.types + .values() + .filter_map(|&(def, _)| match def { + ModuleDefId::TraitId(t) => Some(t), + _ => None, + }) + .chain(self.unnamed_trait_imports.keys().copied()) + } + + pub(crate) fn declare(&mut self, def: ModuleDefId) { + self.declarations.push(def) + } + + pub(crate) fn get_legacy_macro(&self, name: &Name) -> Option<&[MacroId]> { + self.legacy_macros.get(name).map(|it| &**it) + } + + pub(crate) fn define_impl(&mut self, imp: ImplId) { + self.impls.push(imp) + } + + pub(crate) fn define_unnamed_const(&mut self, konst: ConstId) { + self.unnamed_consts.push(konst); + } + + pub(crate) fn define_legacy_macro(&mut self, name: Name, mac: MacroId) { + self.legacy_macros.entry(name).or_default().push(mac); + } + + pub(crate) fn add_attr_macro_invoc(&mut self, item: AstId<ast::Item>, call: MacroCallId) { + self.attr_macros.insert(item, call); + } + + pub(crate) fn attr_macro_invocs( + &self, + ) -> impl Iterator<Item = (AstId<ast::Item>, MacroCallId)> + '_ { + self.attr_macros.iter().map(|(k, v)| (*k, *v)) + } + + pub(crate) fn set_derive_macro_invoc( + &mut self, + adt: AstId<ast::Adt>, + call: MacroCallId, + id: AttrId, + idx: usize, + ) { + if let Some(derives) = self.derive_macros.get_mut(&adt) { + if let Some(DeriveMacroInvocation { derive_call_ids, .. }) = + derives.iter_mut().find(|&&mut DeriveMacroInvocation { attr_id, .. }| id == attr_id) + { + derive_call_ids[idx] = Some(call); + } + } + } + + /// We are required to set this up front as derive invocation recording happens out of order + /// due to the fixed pointer iteration loop being able to record some derives later than others + /// independent of their indices. + pub(crate) fn init_derive_attribute( + &mut self, + adt: AstId<ast::Adt>, + attr_id: AttrId, + attr_call_id: MacroCallId, + len: usize, + ) { + self.derive_macros.entry(adt).or_default().push(DeriveMacroInvocation { + attr_id, + attr_call_id, + derive_call_ids: smallvec![None; len], + }); + } + + pub(crate) fn derive_macro_invocs( + &self, + ) -> impl Iterator< + Item = ( + AstId<ast::Adt>, + impl Iterator<Item = (AttrId, MacroCallId, &[Option<MacroCallId>])>, + ), + > + '_ { + self.derive_macros.iter().map(|(k, v)| { + ( + *k, + v.iter().map(|DeriveMacroInvocation { attr_id, attr_call_id, derive_call_ids }| { + (*attr_id, *attr_call_id, &**derive_call_ids) + }), + ) + }) + } + + pub(crate) fn unnamed_trait_vis(&self, tr: TraitId) -> Option<Visibility> { + self.unnamed_trait_imports.get(&tr).copied() + } + + pub(crate) fn push_unnamed_trait(&mut self, tr: TraitId, vis: Visibility) { + self.unnamed_trait_imports.insert(tr, vis); + } + + pub(crate) fn push_res_with_import( + &mut self, + glob_imports: &mut PerNsGlobImports, + lookup: (LocalModuleId, Name), + def: PerNs, + def_import_type: ImportType, + ) -> bool { + let mut changed = false; + + macro_rules! check_changed { + ( + $changed:ident, + ( $this:ident / $def:ident ) . $field:ident, + $glob_imports:ident [ $lookup:ident ], + $def_import_type:ident + ) => {{ + if let Some(fld) = $def.$field { + let existing = $this.$field.entry($lookup.1.clone()); + match existing { + Entry::Vacant(entry) => { + match $def_import_type { + ImportType::Glob => { + $glob_imports.$field.insert($lookup.clone()); + } + ImportType::Named => { + $glob_imports.$field.remove(&$lookup); + } + } + + entry.insert(fld); + $changed = true; + } + Entry::Occupied(mut entry) + if $glob_imports.$field.contains(&$lookup) + && matches!($def_import_type, ImportType::Named) => + { + cov_mark::hit!(import_shadowed); + $glob_imports.$field.remove(&$lookup); + entry.insert(fld); + $changed = true; + } + _ => {} + } + } + }}; + } + + check_changed!(changed, (self / def).types, glob_imports[lookup], def_import_type); + check_changed!(changed, (self / def).values, glob_imports[lookup], def_import_type); + check_changed!(changed, (self / def).macros, glob_imports[lookup], def_import_type); + + if def.is_none() && self.unresolved.insert(lookup.1) { + changed = true; + } + + changed + } + + pub(crate) fn resolutions<'a>(&'a self) -> impl Iterator<Item = (Option<Name>, PerNs)> + 'a { + self.entries().map(|(name, res)| (Some(name.clone()), res)).chain( + self.unnamed_trait_imports + .iter() + .map(|(tr, vis)| (None, PerNs::types(ModuleDefId::TraitId(*tr), *vis))), + ) + } + + pub(crate) fn collect_legacy_macros(&self) -> FxHashMap<Name, SmallVec<[MacroId; 1]>> { + self.legacy_macros.clone() + } + + /// Marks everything that is not a procedural macro as private to `this_module`. + pub(crate) fn censor_non_proc_macros(&mut self, this_module: ModuleId) { + self.types + .values_mut() + .chain(self.values.values_mut()) + .map(|(_, v)| v) + .chain(self.unnamed_trait_imports.values_mut()) + .for_each(|vis| *vis = Visibility::Module(this_module)); + + for (mac, vis) in self.macros.values_mut() { + if let MacroId::ProcMacroId(_) = mac { + // FIXME: Technically this is insufficient since reexports of proc macros are also + // forbidden. Practically nobody does that. + continue; + } + + *vis = Visibility::Module(this_module); + } + } + + pub(crate) fn dump(&self, buf: &mut String) { + let mut entries: Vec<_> = self.resolutions().collect(); + entries.sort_by_key(|(name, _)| name.clone()); + + for (name, def) in entries { + format_to!(buf, "{}:", name.map_or("_".to_string(), |name| name.to_string())); + + if def.types.is_some() { + buf.push_str(" t"); + } + if def.values.is_some() { + buf.push_str(" v"); + } + if def.macros.is_some() { + buf.push_str(" m"); + } + if def.is_none() { + buf.push_str(" _"); + } + + buf.push('\n'); + } + } + + pub(crate) fn shrink_to_fit(&mut self) { + // Exhaustive match to require handling new fields. + let Self { + _c: _, + types, + values, + macros, + unresolved, + declarations, + impls, + unnamed_consts, + unnamed_trait_imports, + legacy_macros, + attr_macros, + derive_macros, + } = self; + types.shrink_to_fit(); + values.shrink_to_fit(); + macros.shrink_to_fit(); + unresolved.shrink_to_fit(); + declarations.shrink_to_fit(); + impls.shrink_to_fit(); + unnamed_consts.shrink_to_fit(); + unnamed_trait_imports.shrink_to_fit(); + legacy_macros.shrink_to_fit(); + attr_macros.shrink_to_fit(); + derive_macros.shrink_to_fit(); + } +} + +impl PerNs { + pub(crate) fn from_def(def: ModuleDefId, v: Visibility, has_constructor: bool) -> PerNs { + match def { + ModuleDefId::ModuleId(_) => PerNs::types(def, v), + ModuleDefId::FunctionId(_) => PerNs::values(def, v), + ModuleDefId::AdtId(adt) => match adt { + AdtId::UnionId(_) => PerNs::types(def, v), + AdtId::EnumId(_) => PerNs::types(def, v), + AdtId::StructId(_) => { + if has_constructor { + PerNs::both(def, def, v) + } else { + PerNs::types(def, v) + } + } + }, + ModuleDefId::EnumVariantId(_) => PerNs::both(def, def, v), + ModuleDefId::ConstId(_) | ModuleDefId::StaticId(_) => PerNs::values(def, v), + ModuleDefId::TraitId(_) => PerNs::types(def, v), + ModuleDefId::TypeAliasId(_) => PerNs::types(def, v), + ModuleDefId::BuiltinType(_) => PerNs::types(def, v), + ModuleDefId::MacroId(mac) => PerNs::macros(mac, v), + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] +pub enum ItemInNs { + Types(ModuleDefId), + Values(ModuleDefId), + Macros(MacroId), +} + +impl ItemInNs { + pub fn as_module_def_id(self) -> Option<ModuleDefId> { + match self { + ItemInNs::Types(id) | ItemInNs::Values(id) => Some(id), + ItemInNs::Macros(_) => None, + } + } + + /// Returns the crate defining this item (or `None` if `self` is built-in). + pub fn krate(&self, db: &dyn DefDatabase) -> Option<CrateId> { + match self { + ItemInNs::Types(did) | ItemInNs::Values(did) => did.module(db).map(|m| m.krate), + ItemInNs::Macros(id) => Some(id.module(db).krate), + } + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs new file mode 100644 index 000000000..375587ee9 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs @@ -0,0 +1,961 @@ +//! 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, Hasher}, + marker::PhantomData, + ops::Index, + sync::Arc, +}; + +use ast::{AstNode, HasName, StructKind}; +use base_db::CrateId; +use either::Either; +use hir_expand::{ + ast_id_map::FileAstId, + hygiene::Hygiene, + name::{name, AsName, Name}, + ExpandTo, HirFileId, InFile, +}; +use la_arena::{Arena, Idx, IdxRange, RawIdx}; +use profile::Count; +use rustc_hash::FxHashMap; +use smallvec::SmallVec; +use stdx::never; +use syntax::{ast, match_ast, SyntaxKind}; + +use crate::{ + attr::{Attrs, RawAttrs}, + db::DefDatabase, + generics::GenericParams, + intern::Interned, + path::{path, AssociatedTypeBinding, GenericArgs, ImportAlias, ModPath, Path, PathKind}, + type_ref::{Mutability, TraitRef, TypeBound, TypeRef}, + visibility::RawVisibility, + BlockId, +}; + +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct RawVisibilityId(u32); + +impl RawVisibilityId { + pub const PUB: Self = RawVisibilityId(u32::max_value()); + pub const PRIV: Self = RawVisibilityId(u32::max_value() - 1); + pub const PUB_CRATE: Self = RawVisibilityId(u32::max_value() - 2); +} + +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 => f.field(&"pub(self)"), + Self::PUB_CRATE => f.field(&"pub(crate)"), + _ => f.field(&self.0), + }; + f.finish() + } +} + +/// The item tree of a source file. +#[derive(Debug, Default, Eq, PartialEq)] +pub struct ItemTree { + _c: Count<Self>, + + top_level: SmallVec<[ModItem; 1]>, + attrs: FxHashMap<AttrOwner, RawAttrs>, + + data: Option<Box<ItemTreeData>>, +} + +impl ItemTree { + pub(crate) fn file_item_tree_query(db: &dyn DefDatabase, file_id: HirFileId) -> Arc<ItemTree> { + let _p = profile::span("file_item_tree_query").detail(|| format!("{:?}", file_id)); + let syntax = match db.parse_or_expand(file_id) { + Some(node) => node, + None => return Default::default(), + }; + if never!(syntax.kind() == SyntaxKind::ERROR) { + // FIXME: not 100% sure why these crop up, but return an empty tree to avoid a panic + return Default::default(); + } + + let ctx = lower::Ctx::new(db, file_id); + let mut top_attrs = None; + let mut item_tree = match_ast! { + match syntax { + ast::SourceFile(file) => { + top_attrs = Some(RawAttrs::new(db, &file, ctx.hygiene())); + ctx.lower_module_items(&file) + }, + 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) + }, + _ => { + panic!("cannot create item tree from {:?} {}", syntax, syntax); + }, + } + }; + + if let Some(attrs) = top_attrs { + item_tree.attrs.insert(AttrOwner::TopLevel, attrs); + } + item_tree.shrink_to_fit(); + Arc::new(item_tree) + } + + /// Returns an iterator over all items located at the top level of the `HirFileId` this + /// `ItemTree` was created from. + pub fn top_level_items(&self) -> &[ModItem] { + &self.top_level + } + + /// Returns the inner attributes of the source file. + pub fn top_level_attrs(&self, db: &dyn DefDatabase, krate: CrateId) -> Attrs { + self.attrs.get(&AttrOwner::TopLevel).unwrap_or(&RawAttrs::EMPTY).clone().filter(db, krate) + } + + pub(crate) fn raw_attrs(&self, of: AttrOwner) -> &RawAttrs { + self.attrs.get(&of).unwrap_or(&RawAttrs::EMPTY) + } + + pub(crate) fn attrs(&self, db: &dyn DefDatabase, krate: CrateId, of: AttrOwner) -> Attrs { + self.raw_attrs(of).clone().filter(db, krate) + } + + pub fn pretty_print(&self) -> String { + pretty::print_item_tree(self) + } + + fn data(&self) -> &ItemTreeData { + self.data.as_ref().expect("attempted to access data of empty ItemTree") + } + + fn data_mut(&mut self) -> &mut ItemTreeData { + self.data.get_or_insert_with(Box::default) + } + + fn block_item_tree(db: &dyn DefDatabase, block: BlockId) -> Arc<ItemTree> { + let loc = db.lookup_intern_block(block); + let block = loc.ast_id.to_node(db.upcast()); + let ctx = lower::Ctx::new(db, loc.ast_id.file_id); + Arc::new(ctx.lower_block(&block)) + } + + fn shrink_to_fit(&mut self) { + if let Some(data) = &mut self.data { + let ItemTreeData { + imports, + extern_crates, + extern_blocks, + functions, + params, + structs, + fields, + unions, + enums, + variants, + consts, + statics, + traits, + impls, + type_aliases, + mods, + macro_calls, + macro_rules, + macro_defs, + vis, + } = &mut **data; + + imports.shrink_to_fit(); + extern_crates.shrink_to_fit(); + extern_blocks.shrink_to_fit(); + functions.shrink_to_fit(); + params.shrink_to_fit(); + structs.shrink_to_fit(); + fields.shrink_to_fit(); + unions.shrink_to_fit(); + enums.shrink_to_fit(); + variants.shrink_to_fit(); + consts.shrink_to_fit(); + statics.shrink_to_fit(); + traits.shrink_to_fit(); + impls.shrink_to_fit(); + type_aliases.shrink_to_fit(); + mods.shrink_to_fit(); + macro_calls.shrink_to_fit(); + macro_rules.shrink_to_fit(); + macro_defs.shrink_to_fit(); + + vis.arena.shrink_to_fit(); + } + } +} + +#[derive(Default, Debug, Eq, PartialEq)] +struct ItemVisibilities { + arena: Arena<RawVisibility>, +} + +impl ItemVisibilities { + fn alloc(&mut self, vis: RawVisibility) -> RawVisibilityId { + match &vis { + RawVisibility::Public => RawVisibilityId::PUB, + RawVisibility::Module(path) if path.segments().is_empty() => match &path.kind { + PathKind::Super(0) => RawVisibilityId::PRIV, + PathKind::Crate => RawVisibilityId::PUB_CRATE, + _ => RawVisibilityId(self.arena.alloc(vis).into_raw().into()), + }, + _ => RawVisibilityId(self.arena.alloc(vis).into_raw().into()), + } + } +} + +static VIS_PUB: RawVisibility = RawVisibility::Public; +static VIS_PRIV: RawVisibility = RawVisibility::Module(ModPath::from_kind(PathKind::Super(0))); +static VIS_PUB_CRATE: RawVisibility = RawVisibility::Module(ModPath::from_kind(PathKind::Crate)); + +#[derive(Default, Debug, Eq, PartialEq)] +struct ItemTreeData { + imports: Arena<Import>, + extern_crates: Arena<ExternCrate>, + extern_blocks: Arena<ExternBlock>, + functions: Arena<Function>, + params: Arena<Param>, + structs: Arena<Struct>, + fields: Arena<Field>, + unions: Arena<Union>, + enums: Arena<Enum>, + variants: Arena<Variant>, + consts: Arena<Const>, + statics: Arena<Static>, + traits: Arena<Trait>, + impls: Arena<Impl>, + type_aliases: Arena<TypeAlias>, + mods: Arena<Mod>, + macro_calls: Arena<MacroCall>, + macro_rules: Arena<MacroRules>, + macro_defs: Arena<MacroDef>, + + vis: ItemVisibilities, +} + +#[derive(Debug, Eq, PartialEq, Hash)] +pub enum AttrOwner { + /// Attributes on an item. + ModItem(ModItem), + /// Inner attributes of the source file. + TopLevel, + + Variant(Idx<Variant>), + Field(Idx<Field>), + Param(Idx<Param>), +} + +macro_rules! from_attrs { + ( $( $var:ident($t:ty) ),+ ) => { + $( + impl From<$t> for AttrOwner { + fn from(t: $t) -> AttrOwner { + AttrOwner::$var(t) + } + } + )+ + }; +} + +from_attrs!(ModItem(ModItem), Variant(Idx<Variant>), Field(Idx<Field>), Param(Idx<Param>)); + +/// Trait implemented by all item nodes in the item tree. +pub trait ItemTreeNode: Clone { + type Source: AstNode + Into<ast::Item>; + + fn ast_id(&self) -> FileAstId<Self::Source>; + + /// Looks up an instance of `Self` in an item tree. + fn lookup(tree: &ItemTree, index: Idx<Self>) -> &Self; + + /// Downcasts a `ModItem` to a `FileItemTreeId` specific to this type. + fn id_from_mod_item(mod_item: ModItem) -> Option<FileItemTreeId<Self>>; + + /// Upcasts a `FileItemTreeId` to a generic `ModItem`. + fn id_to_mod_item(id: FileItemTreeId<Self>) -> ModItem; +} + +pub struct FileItemTreeId<N: ItemTreeNode> { + index: Idx<N>, + _p: PhantomData<N>, +} + +impl<N: ItemTreeNode> Clone for FileItemTreeId<N> { + fn clone(&self) -> Self { + Self { index: self.index, _p: PhantomData } + } +} +impl<N: ItemTreeNode> Copy for FileItemTreeId<N> {} + +impl<N: ItemTreeNode> PartialEq for FileItemTreeId<N> { + fn eq(&self, other: &FileItemTreeId<N>) -> bool { + self.index == other.index + } +} +impl<N: ItemTreeNode> Eq for FileItemTreeId<N> {} + +impl<N: ItemTreeNode> Hash for FileItemTreeId<N> { + fn hash<H: Hasher>(&self, state: &mut H) { + self.index.hash(state) + } +} + +impl<N: ItemTreeNode> fmt::Debug for FileItemTreeId<N> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.index.fmt(f) + } +} + +/// 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(&self, db: &dyn DefDatabase) -> Arc<ItemTree> { + match self.block { + Some(block) => ItemTree::block_item_tree(db, block), + None => db.file_item_tree(self.file), + } + } + + pub(crate) fn file_id(self) -> HirFileId { + self.file + } + + pub(crate) fn is_block(self) -> bool { + self.block.is_some() + } +} + +#[derive(Debug)] +pub struct ItemTreeId<N: ItemTreeNode> { + tree: TreeId, + pub value: FileItemTreeId<N>, +} + +impl<N: ItemTreeNode> ItemTreeId<N> { + pub fn new(tree: TreeId, idx: FileItemTreeId<N>) -> Self { + Self { tree, value: idx } + } + + pub fn file_id(self) -> HirFileId { + self.tree.file + } + + pub fn tree_id(self) -> TreeId { + self.tree + } + + pub fn item_tree(self, db: &dyn DefDatabase) -> Arc<ItemTree> { + self.tree.item_tree(db) + } +} + +impl<N: ItemTreeNode> Copy for ItemTreeId<N> {} +impl<N: ItemTreeNode> Clone for ItemTreeId<N> { + fn clone(&self) -> Self { + *self + } +} + +impl<N: ItemTreeNode> PartialEq for ItemTreeId<N> { + fn eq(&self, other: &Self) -> bool { + self.tree == other.tree && self.value == other.value + } +} + +impl<N: ItemTreeNode> Eq for ItemTreeId<N> {} + +impl<N: ItemTreeNode> Hash for ItemTreeId<N> { + fn hash<H: Hasher>(&self, state: &mut H) { + self.tree.hash(state); + self.value.hash(state); + } +} + +macro_rules! mod_items { + ( $( $typ:ident in $fld:ident -> $ast:ty ),+ $(,)? ) => { + #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] + pub enum ModItem { + $( + $typ(FileItemTreeId<$typ>), + )+ + } + + $( + impl From<FileItemTreeId<$typ>> for ModItem { + fn from(id: FileItemTreeId<$typ>) -> ModItem { + ModItem::$typ(id) + } + } + )+ + + $( + impl ItemTreeNode for $typ { + type Source = $ast; + + fn ast_id(&self) -> FileAstId<Self::Source> { + self.ast_id + } + + fn lookup(tree: &ItemTree, index: Idx<Self>) -> &Self { + &tree.data().$fld[index] + } + + fn id_from_mod_item(mod_item: ModItem) -> Option<FileItemTreeId<Self>> { + match mod_item { + ModItem::$typ(id) => Some(id), + _ => None, + } + } + + fn id_to_mod_item(id: FileItemTreeId<Self>) -> ModItem { + ModItem::$typ(id) + } + } + + impl Index<Idx<$typ>> for ItemTree { + type Output = $typ; + + fn index(&self, index: Idx<$typ>) -> &Self::Output { + &self.data().$fld[index] + } + } + )+ + }; +} + +mod_items! { + Import in imports -> ast::Use, + ExternCrate in extern_crates -> ast::ExternCrate, + ExternBlock in extern_blocks -> ast::ExternBlock, + Function in functions -> ast::Fn, + Struct in structs -> ast::Struct, + Union in unions -> ast::Union, + Enum in enums -> ast::Enum, + Const in consts -> ast::Const, + Static in statics -> ast::Static, + Trait in traits -> ast::Trait, + Impl in impls -> ast::Impl, + TypeAlias in type_aliases -> ast::TypeAlias, + Mod in mods -> ast::Module, + MacroCall in macro_calls -> ast::MacroCall, + MacroRules in macro_rules -> ast::MacroRules, + MacroDef in macro_defs -> ast::MacroDef, +} + +macro_rules! impl_index { + ( $($fld:ident: $t:ty),+ $(,)? ) => { + $( + impl Index<Idx<$t>> for ItemTree { + type Output = $t; + + fn index(&self, index: Idx<$t>) -> &Self::Output { + &self.data().$fld[index] + } + } + )+ + }; +} + +impl_index!(fields: Field, variants: Variant, params: Param); + +impl Index<RawVisibilityId> for ItemTree { + type Output = RawVisibility; + fn index(&self, index: RawVisibilityId) -> &Self::Output { + match index { + RawVisibilityId::PRIV => &VIS_PRIV, + RawVisibilityId::PUB => &VIS_PUB, + RawVisibilityId::PUB_CRATE => &VIS_PUB_CRATE, + _ => &self.data().vis.arena[Idx::from_raw(index.0.into())], + } + } +} + +impl<N: ItemTreeNode> Index<FileItemTreeId<N>> for ItemTree { + type Output = N; + fn index(&self, id: FileItemTreeId<N>) -> &N { + N::lookup(self, id.index) + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Import { + pub visibility: RawVisibilityId, + pub ast_id: FileAstId<ast::Use>, + pub use_tree: UseTree, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct UseTree { + pub index: Idx<ast::UseTree>, + kind: UseTreeKind, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum UseTreeKind { + /// ``` + /// use path::to::Item; + /// use path::to::Item as Renamed; + /// use path::to::Trait as _; + /// ``` + Single { path: Interned<ModPath>, alias: Option<ImportAlias> }, + + /// ``` + /// use *; // (invalid, but can occur in nested tree) + /// use path::*; + /// ``` + Glob { path: Option<Interned<ModPath>> }, + + /// ``` + /// 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 visibility: RawVisibilityId, + pub ast_id: FileAstId<ast::ExternCrate>, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct ExternBlock { + pub abi: Option<Interned<str>>, + pub ast_id: FileAstId<ast::ExternBlock>, + pub children: Box<[ModItem]>, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Function { + pub name: Name, + pub visibility: RawVisibilityId, + pub explicit_generic_params: Interned<GenericParams>, + pub abi: Option<Interned<str>>, + pub params: IdxRange<Param>, + pub ret_type: Interned<TypeRef>, + pub async_ret_type: Option<Interned<TypeRef>>, + pub ast_id: FileAstId<ast::Fn>, + pub(crate) flags: FnFlags, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum Param { + Normal(Option<Name>, Interned<TypeRef>), + Varargs, +} + +bitflags::bitflags! { + #[derive(Default)] + pub(crate) struct FnFlags: u8 { + const HAS_SELF_PARAM = 1 << 0; + const HAS_BODY = 1 << 1; + const HAS_DEFAULT_KW = 1 << 2; + const HAS_CONST_KW = 1 << 3; + const HAS_ASYNC_KW = 1 << 4; + const HAS_UNSAFE_KW = 1 << 5; + const IS_VARARGS = 1 << 6; + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Struct { + pub name: Name, + pub visibility: RawVisibilityId, + pub generic_params: Interned<GenericParams>, + pub fields: Fields, + pub ast_id: FileAstId<ast::Struct>, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Union { + pub name: Name, + pub visibility: RawVisibilityId, + pub generic_params: Interned<GenericParams>, + pub fields: Fields, + pub ast_id: FileAstId<ast::Union>, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Enum { + pub name: Name, + pub visibility: RawVisibilityId, + pub generic_params: Interned<GenericParams>, + pub variants: IdxRange<Variant>, + pub ast_id: FileAstId<ast::Enum>, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Const { + /// `None` for `const _: () = ();` + pub name: Option<Name>, + pub visibility: RawVisibilityId, + pub type_ref: Interned<TypeRef>, + pub ast_id: FileAstId<ast::Const>, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Static { + pub name: Name, + pub visibility: RawVisibilityId, + pub mutable: bool, + pub type_ref: Interned<TypeRef>, + pub ast_id: FileAstId<ast::Static>, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Trait { + pub name: Name, + pub visibility: RawVisibilityId, + pub generic_params: Interned<GenericParams>, + pub is_auto: bool, + pub is_unsafe: bool, + pub items: Box<[AssocItem]>, + pub ast_id: FileAstId<ast::Trait>, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Impl { + pub generic_params: Interned<GenericParams>, + pub target_trait: Option<Interned<TraitRef>>, + pub self_ty: Interned<TypeRef>, + pub is_negative: bool, + pub items: Box<[AssocItem]>, + pub ast_id: FileAstId<ast::Impl>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TypeAlias { + pub name: Name, + pub visibility: RawVisibilityId, + /// Bounds on the type alias itself. Only valid in trait declarations, eg. `type Assoc: Copy;`. + pub bounds: Box<[Interned<TypeBound>]>, + pub generic_params: Interned<GenericParams>, + pub type_ref: Option<Interned<TypeRef>>, + pub ast_id: FileAstId<ast::TypeAlias>, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Mod { + pub name: Name, + pub visibility: RawVisibilityId, + pub kind: ModKind, + pub ast_id: FileAstId<ast::Module>, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum ModKind { + /// `mod m { ... }` + Inline { items: Box<[ModItem]> }, + + /// `mod m;` + Outline, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct MacroCall { + /// Path to the called macro. + pub path: Interned<ModPath>, + pub ast_id: FileAstId<ast::MacroCall>, + pub expand_to: ExpandTo, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct MacroRules { + /// The name of the declared macro. + pub name: Name, + pub ast_id: FileAstId<ast::MacroRules>, +} + +/// "Macros 2.0" macro definition. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct MacroDef { + pub name: Name, + pub visibility: RawVisibilityId, + pub ast_id: FileAstId<ast::MacroDef>, +} + +impl Import { + /// Maps a `UseTree` contained in this import back to its AST node. + pub fn use_tree_to_ast( + &self, + db: &dyn DefDatabase, + file_id: HirFileId, + index: Idx<ast::UseTree>, + ) -> ast::UseTree { + // Re-lower the AST item and get the source map. + // Note: The AST unwraps are fine, since if they fail we should have never obtained `index`. + let ast = InFile::new(file_id, self.ast_id).to_node(db.upcast()); + let ast_use_tree = ast.use_tree().expect("missing `use_tree`"); + let hygiene = Hygiene::new(db.upcast(), file_id); + let (_, source_map) = + lower::lower_use_tree(db, &hygiene, ast_use_tree).expect("failed to lower use tree"); + source_map[index].clone() + } +} + +#[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 UseTree { + /// Expands the `UseTree` into individually imported `ModPath`s. + pub fn expand( + &self, + mut cb: impl FnMut(Idx<ast::UseTree>, ModPath, ImportKind, Option<ImportAlias>), + ) { + self.expand_impl(None, &mut cb) + } + + fn expand_impl( + &self, + prefix: Option<ModPath>, + cb: &mut dyn 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) => { + for segment in path.segments() { + prefix.push_segment(segment.clone()); + } + 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; + for segment in path.segments() { + prefix.push_segment(segment.clone()); + } + Some((prefix, ImportKind::Plain)) + } + _ => None, + } + } + (Some(prefix), PathKind::Super(0)) 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(self.index, path, kind, alias.clone()); + } + } + UseTreeKind::Glob { path: Some(path) } => { + if let Some((path, _)) = concat_mod_paths(prefix, path) { + cb(self.index, path, ImportKind::Glob, None); + } + } + UseTreeKind::Glob { path: None } => { + if let Some(prefix) = prefix { + cb(self.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 { + tree.expand_impl(prefix.clone(), cb); + } + } + } + } +} + +macro_rules! impl_froms { + ($e:ident { $($v:ident ($t:ty)),* $(,)? }) => { + $( + impl From<$t> for $e { + fn from(it: $t) -> $e { + $e::$v(it) + } + } + )* + } +} + +impl ModItem { + pub fn as_assoc_item(&self) -> Option<AssocItem> { + match self { + ModItem::Import(_) + | ModItem::ExternCrate(_) + | ModItem::ExternBlock(_) + | ModItem::Struct(_) + | ModItem::Union(_) + | ModItem::Enum(_) + | ModItem::Static(_) + | ModItem::Trait(_) + | ModItem::Impl(_) + | ModItem::Mod(_) + | ModItem::MacroRules(_) + | ModItem::MacroDef(_) => None, + ModItem::MacroCall(call) => Some(AssocItem::MacroCall(*call)), + ModItem::Const(konst) => Some(AssocItem::Const(*konst)), + ModItem::TypeAlias(alias) => Some(AssocItem::TypeAlias(*alias)), + ModItem::Function(func) => Some(AssocItem::Function(*func)), + } + } + + pub fn downcast<N: ItemTreeNode>(self) -> Option<FileItemTreeId<N>> { + N::id_from_mod_item(self) + } + + pub fn ast_id(&self, tree: &ItemTree) -> FileAstId<ast::Item> { + match self { + ModItem::Import(it) => tree[it.index].ast_id().upcast(), + ModItem::ExternCrate(it) => tree[it.index].ast_id().upcast(), + ModItem::ExternBlock(it) => tree[it.index].ast_id().upcast(), + ModItem::Function(it) => tree[it.index].ast_id().upcast(), + ModItem::Struct(it) => tree[it.index].ast_id().upcast(), + ModItem::Union(it) => tree[it.index].ast_id().upcast(), + ModItem::Enum(it) => tree[it.index].ast_id().upcast(), + ModItem::Const(it) => tree[it.index].ast_id().upcast(), + ModItem::Static(it) => tree[it.index].ast_id().upcast(), + ModItem::Trait(it) => tree[it.index].ast_id().upcast(), + ModItem::Impl(it) => tree[it.index].ast_id().upcast(), + ModItem::TypeAlias(it) => tree[it.index].ast_id().upcast(), + ModItem::Mod(it) => tree[it.index].ast_id().upcast(), + ModItem::MacroCall(it) => tree[it.index].ast_id().upcast(), + ModItem::MacroRules(it) => tree[it.index].ast_id().upcast(), + ModItem::MacroDef(it) => tree[it.index].ast_id().upcast(), + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum AssocItem { + Function(FileItemTreeId<Function>), + TypeAlias(FileItemTreeId<TypeAlias>), + Const(FileItemTreeId<Const>), + MacroCall(FileItemTreeId<MacroCall>), +} + +impl_froms!(AssocItem { + Function(FileItemTreeId<Function>), + TypeAlias(FileItemTreeId<TypeAlias>), + Const(FileItemTreeId<Const>), + MacroCall(FileItemTreeId<MacroCall>), +}); + +impl From<AssocItem> for ModItem { + fn from(item: AssocItem) -> Self { + match item { + AssocItem::Function(it) => it.into(), + AssocItem::TypeAlias(it) => it.into(), + AssocItem::Const(it) => it.into(), + AssocItem::MacroCall(it) => it.into(), + } + } +} + +impl AssocItem { + pub fn ast_id(self, tree: &ItemTree) -> FileAstId<ast::AssocItem> { + match self { + AssocItem::Function(id) => tree[id].ast_id.upcast(), + AssocItem::TypeAlias(id) => tree[id].ast_id.upcast(), + AssocItem::Const(id) => tree[id].ast_id.upcast(), + AssocItem::MacroCall(id) => tree[id].ast_id.upcast(), + } + } +} + +#[derive(Debug, Eq, PartialEq)] +pub struct Variant { + pub name: Name, + pub fields: Fields, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Fields { + Record(IdxRange<Field>), + Tuple(IdxRange<Field>), + Unit, +} + +/// A single field of an enum variant or struct +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Field { + pub name: Name, + pub type_ref: Interned<TypeRef>, + pub visibility: RawVisibilityId, +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs new file mode 100644 index 000000000..7f2551e94 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs @@ -0,0 +1,773 @@ +//! AST -> `ItemTree` lowering code. + +use std::{collections::hash_map::Entry, sync::Arc}; + +use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, HirFileId}; +use syntax::ast::{self, HasModuleItem}; + +use crate::{ + generics::{GenericParams, TypeParamData, TypeParamProvenance}, + type_ref::{LifetimeRef, TraitBoundModifier, TraitRef}, +}; + +use super::*; + +fn id<N: ItemTreeNode>(index: Idx<N>) -> FileItemTreeId<N> { + FileItemTreeId { index, _p: PhantomData } +} + +pub(super) struct Ctx<'a> { + db: &'a dyn DefDatabase, + tree: ItemTree, + source_ast_id_map: Arc<AstIdMap>, + body_ctx: crate::body::LowerCtx<'a>, +} + +impl<'a> Ctx<'a> { + pub(super) fn new(db: &'a dyn DefDatabase, file: HirFileId) -> Self { + Self { + db, + tree: ItemTree::default(), + source_ast_id_map: db.ast_id_map(file), + body_ctx: crate::body::LowerCtx::new(db, file), + } + } + + pub(super) fn hygiene(&self) -> &Hygiene { + self.body_ctx.hygiene() + } + + pub(super) fn lower_module_items(mut self, item_owner: &dyn HasModuleItem) -> ItemTree { + self.tree.top_level = + item_owner.items().flat_map(|item| self.lower_mod_item(&item)).collect(); + self.tree + } + + pub(super) fn lower_macro_stmts(mut self, stmts: ast::MacroStmts) -> ItemTree { + self.tree.top_level = stmts + .statements() + .filter_map(|stmt| { + match stmt { + ast::Stmt::Item(item) => Some(item), + // Macro calls can be both items and expressions. The syntax library always treats + // them as expressions here, so we undo that. + ast::Stmt::ExprStmt(es) => match es.expr()? { + ast::Expr::MacroExpr(expr) => { + cov_mark::hit!(macro_call_in_macro_stmts_is_added_to_item_tree); + Some(expr.macro_call()?.into()) + } + _ => None, + }, + _ => None, + } + }) + .flat_map(|item| self.lower_mod_item(&item)) + .collect(); + + if let Some(ast::Expr::MacroExpr(tail_macro)) = stmts.expr() { + if let Some(call) = tail_macro.macro_call() { + cov_mark::hit!(macro_stmt_with_trailing_macro_expr); + if let Some(mod_item) = self.lower_mod_item(&call.into()) { + self.tree.top_level.push(mod_item); + } + } + } + + self.tree + } + + pub(super) fn lower_block(mut self, block: &ast::BlockExpr) -> ItemTree { + self.tree.top_level = block + .statements() + .filter_map(|stmt| match stmt { + ast::Stmt::Item(item) => self.lower_mod_item(&item), + // Macro calls can be both items and expressions. The syntax library always treats + // them as expressions here, so we undo that. + ast::Stmt::ExprStmt(es) => match es.expr()? { + ast::Expr::MacroExpr(expr) => self.lower_mod_item(&expr.macro_call()?.into()), + _ => None, + }, + _ => None, + }) + .collect(); + + self.tree + } + + fn data(&mut self) -> &mut ItemTreeData { + self.tree.data_mut() + } + + fn lower_mod_item(&mut self, item: &ast::Item) -> Option<ModItem> { + let attrs = RawAttrs::new(self.db, item, self.hygiene()); + let item: ModItem = match item { + ast::Item::Struct(ast) => self.lower_struct(ast)?.into(), + ast::Item::Union(ast) => self.lower_union(ast)?.into(), + ast::Item::Enum(ast) => self.lower_enum(ast)?.into(), + ast::Item::Fn(ast) => self.lower_function(ast)?.into(), + ast::Item::TypeAlias(ast) => self.lower_type_alias(ast)?.into(), + ast::Item::Static(ast) => self.lower_static(ast)?.into(), + ast::Item::Const(ast) => self.lower_const(ast).into(), + ast::Item::Module(ast) => self.lower_module(ast)?.into(), + ast::Item::Trait(ast) => self.lower_trait(ast)?.into(), + ast::Item::Impl(ast) => self.lower_impl(ast)?.into(), + ast::Item::Use(ast) => self.lower_use(ast)?.into(), + ast::Item::ExternCrate(ast) => self.lower_extern_crate(ast)?.into(), + ast::Item::MacroCall(ast) => self.lower_macro_call(ast)?.into(), + ast::Item::MacroRules(ast) => self.lower_macro_rules(ast)?.into(), + ast::Item::MacroDef(ast) => self.lower_macro_def(ast)?.into(), + ast::Item::ExternBlock(ast) => self.lower_extern_block(ast).into(), + }; + + self.add_attrs(item.into(), attrs); + + Some(item) + } + + fn add_attrs(&mut self, item: AttrOwner, attrs: RawAttrs) { + match self.tree.attrs.entry(item) { + Entry::Occupied(mut entry) => { + *entry.get_mut() = entry.get().merge(attrs); + } + Entry::Vacant(entry) => { + entry.insert(attrs); + } + } + } + + fn lower_assoc_item(&mut self, item: &ast::AssocItem) -> Option<AssocItem> { + match item { + ast::AssocItem::Fn(ast) => self.lower_function(ast).map(Into::into), + ast::AssocItem::TypeAlias(ast) => self.lower_type_alias(ast).map(Into::into), + ast::AssocItem::Const(ast) => Some(self.lower_const(ast).into()), + ast::AssocItem::MacroCall(ast) => self.lower_macro_call(ast).map(Into::into), + } + } + + fn lower_struct(&mut self, strukt: &ast::Struct) -> Option<FileItemTreeId<Struct>> { + let visibility = self.lower_visibility(strukt); + let name = strukt.name()?.as_name(); + let generic_params = self.lower_generic_params(GenericsOwner::Struct, strukt); + let fields = self.lower_fields(&strukt.kind()); + let ast_id = self.source_ast_id_map.ast_id(strukt); + let res = Struct { name, visibility, generic_params, fields, ast_id }; + Some(id(self.data().structs.alloc(res))) + } + + fn lower_fields(&mut self, strukt_kind: &ast::StructKind) -> Fields { + match strukt_kind { + ast::StructKind::Record(it) => { + let range = self.lower_record_fields(it); + Fields::Record(range) + } + ast::StructKind::Tuple(it) => { + let range = self.lower_tuple_fields(it); + Fields::Tuple(range) + } + ast::StructKind::Unit => Fields::Unit, + } + } + + fn lower_record_fields(&mut self, fields: &ast::RecordFieldList) -> IdxRange<Field> { + let start = self.next_field_idx(); + for field in fields.fields() { + if let Some(data) = self.lower_record_field(&field) { + let idx = self.data().fields.alloc(data); + self.add_attrs(idx.into(), RawAttrs::new(self.db, &field, self.hygiene())); + } + } + let end = self.next_field_idx(); + IdxRange::new(start..end) + } + + fn lower_record_field(&mut self, field: &ast::RecordField) -> Option<Field> { + let name = field.name()?.as_name(); + let visibility = self.lower_visibility(field); + let type_ref = self.lower_type_ref_opt(field.ty()); + let res = Field { name, type_ref, visibility }; + Some(res) + } + + fn lower_tuple_fields(&mut self, fields: &ast::TupleFieldList) -> IdxRange<Field> { + let start = self.next_field_idx(); + for (i, field) in fields.fields().enumerate() { + let data = self.lower_tuple_field(i, &field); + let idx = self.data().fields.alloc(data); + self.add_attrs(idx.into(), RawAttrs::new(self.db, &field, self.hygiene())); + } + let end = self.next_field_idx(); + IdxRange::new(start..end) + } + + fn lower_tuple_field(&mut self, idx: usize, field: &ast::TupleField) -> Field { + let name = Name::new_tuple_field(idx); + let visibility = self.lower_visibility(field); + let type_ref = self.lower_type_ref_opt(field.ty()); + Field { name, type_ref, visibility } + } + + fn lower_union(&mut self, union: &ast::Union) -> Option<FileItemTreeId<Union>> { + let visibility = self.lower_visibility(union); + let name = union.name()?.as_name(); + let generic_params = self.lower_generic_params(GenericsOwner::Union, union); + let fields = match union.record_field_list() { + Some(record_field_list) => self.lower_fields(&StructKind::Record(record_field_list)), + None => Fields::Record(IdxRange::new(self.next_field_idx()..self.next_field_idx())), + }; + let ast_id = self.source_ast_id_map.ast_id(union); + let res = Union { name, visibility, generic_params, fields, ast_id }; + Some(id(self.data().unions.alloc(res))) + } + + fn lower_enum(&mut self, enum_: &ast::Enum) -> Option<FileItemTreeId<Enum>> { + let visibility = self.lower_visibility(enum_); + let name = enum_.name()?.as_name(); + let generic_params = self.lower_generic_params(GenericsOwner::Enum, enum_); + let variants = match &enum_.variant_list() { + Some(variant_list) => self.lower_variants(variant_list), + None => IdxRange::new(self.next_variant_idx()..self.next_variant_idx()), + }; + let ast_id = self.source_ast_id_map.ast_id(enum_); + let res = Enum { name, visibility, generic_params, variants, ast_id }; + Some(id(self.data().enums.alloc(res))) + } + + fn lower_variants(&mut self, variants: &ast::VariantList) -> IdxRange<Variant> { + let start = self.next_variant_idx(); + for variant in variants.variants() { + if let Some(data) = self.lower_variant(&variant) { + let idx = self.data().variants.alloc(data); + self.add_attrs(idx.into(), RawAttrs::new(self.db, &variant, self.hygiene())); + } + } + let end = self.next_variant_idx(); + IdxRange::new(start..end) + } + + fn lower_variant(&mut self, variant: &ast::Variant) -> Option<Variant> { + let name = variant.name()?.as_name(); + let fields = self.lower_fields(&variant.kind()); + let res = Variant { name, fields }; + Some(res) + } + + fn lower_function(&mut self, func: &ast::Fn) -> Option<FileItemTreeId<Function>> { + let visibility = self.lower_visibility(func); + let name = func.name()?.as_name(); + + let mut has_self_param = false; + let start_param = self.next_param_idx(); + if let Some(param_list) = func.param_list() { + if let Some(self_param) = param_list.self_param() { + let self_type = match self_param.ty() { + Some(type_ref) => TypeRef::from_ast(&self.body_ctx, type_ref), + None => { + let self_type = TypeRef::Path(name![Self].into()); + match self_param.kind() { + ast::SelfParamKind::Owned => self_type, + ast::SelfParamKind::Ref => TypeRef::Reference( + Box::new(self_type), + self_param.lifetime().as_ref().map(LifetimeRef::new), + Mutability::Shared, + ), + ast::SelfParamKind::MutRef => TypeRef::Reference( + Box::new(self_type), + self_param.lifetime().as_ref().map(LifetimeRef::new), + Mutability::Mut, + ), + } + } + }; + let ty = Interned::new(self_type); + let idx = self.data().params.alloc(Param::Normal(None, ty)); + self.add_attrs(idx.into(), RawAttrs::new(self.db, &self_param, self.hygiene())); + has_self_param = true; + } + for param in param_list.params() { + let idx = match param.dotdotdot_token() { + Some(_) => self.data().params.alloc(Param::Varargs), + None => { + let type_ref = TypeRef::from_ast_opt(&self.body_ctx, param.ty()); + let ty = Interned::new(type_ref); + let mut pat = param.pat(); + // FIXME: This really shouldn't be here, in fact FunctionData/ItemTree's function shouldn't know about + // pattern names at all + let name = 'name: loop { + match pat { + Some(ast::Pat::RefPat(ref_pat)) => pat = ref_pat.pat(), + Some(ast::Pat::IdentPat(ident)) => { + break 'name ident.name().map(|it| it.as_name()) + } + _ => break 'name None, + } + }; + self.data().params.alloc(Param::Normal(name, ty)) + } + }; + self.add_attrs(idx.into(), RawAttrs::new(self.db, ¶m, self.hygiene())); + } + } + let end_param = self.next_param_idx(); + let params = IdxRange::new(start_param..end_param); + + let ret_type = match func.ret_type() { + Some(rt) => match rt.ty() { + Some(type_ref) => TypeRef::from_ast(&self.body_ctx, type_ref), + None if rt.thin_arrow_token().is_some() => TypeRef::Error, + None => TypeRef::unit(), + }, + None => TypeRef::unit(), + }; + + let (ret_type, async_ret_type) = if func.async_token().is_some() { + let async_ret_type = ret_type.clone(); + let future_impl = desugar_future_path(ret_type); + let ty_bound = Interned::new(TypeBound::Path(future_impl, TraitBoundModifier::None)); + (TypeRef::ImplTrait(vec![ty_bound]), Some(async_ret_type)) + } else { + (ret_type, None) + }; + + let abi = func.abi().map(lower_abi); + + let ast_id = self.source_ast_id_map.ast_id(func); + + let mut flags = FnFlags::default(); + if func.body().is_some() { + flags |= FnFlags::HAS_BODY; + } + if has_self_param { + flags |= FnFlags::HAS_SELF_PARAM; + } + if func.default_token().is_some() { + flags |= FnFlags::HAS_DEFAULT_KW; + } + if func.const_token().is_some() { + flags |= FnFlags::HAS_CONST_KW; + } + if func.async_token().is_some() { + flags |= FnFlags::HAS_ASYNC_KW; + } + if func.unsafe_token().is_some() { + flags |= FnFlags::HAS_UNSAFE_KW; + } + + let mut res = Function { + name, + visibility, + explicit_generic_params: Interned::new(GenericParams::default()), + abi, + params, + ret_type: Interned::new(ret_type), + async_ret_type: async_ret_type.map(Interned::new), + ast_id, + flags, + }; + res.explicit_generic_params = + self.lower_generic_params(GenericsOwner::Function(&res), func); + + Some(id(self.data().functions.alloc(res))) + } + + fn lower_type_alias( + &mut self, + type_alias: &ast::TypeAlias, + ) -> Option<FileItemTreeId<TypeAlias>> { + let name = type_alias.name()?.as_name(); + let type_ref = type_alias.ty().map(|it| self.lower_type_ref(&it)); + let visibility = self.lower_visibility(type_alias); + let bounds = self.lower_type_bounds(type_alias); + let generic_params = self.lower_generic_params(GenericsOwner::TypeAlias, type_alias); + let ast_id = self.source_ast_id_map.ast_id(type_alias); + let res = TypeAlias { + name, + visibility, + bounds: bounds.into_boxed_slice(), + generic_params, + type_ref, + ast_id, + }; + Some(id(self.data().type_aliases.alloc(res))) + } + + fn lower_static(&mut self, static_: &ast::Static) -> Option<FileItemTreeId<Static>> { + let name = static_.name()?.as_name(); + let type_ref = self.lower_type_ref_opt(static_.ty()); + let visibility = self.lower_visibility(static_); + let mutable = static_.mut_token().is_some(); + let ast_id = self.source_ast_id_map.ast_id(static_); + let res = Static { name, visibility, mutable, type_ref, ast_id }; + Some(id(self.data().statics.alloc(res))) + } + + fn lower_const(&mut self, konst: &ast::Const) -> FileItemTreeId<Const> { + let name = konst.name().map(|it| it.as_name()); + let type_ref = self.lower_type_ref_opt(konst.ty()); + let visibility = self.lower_visibility(konst); + let ast_id = self.source_ast_id_map.ast_id(konst); + let res = Const { name, visibility, type_ref, ast_id }; + id(self.data().consts.alloc(res)) + } + + fn lower_module(&mut self, module: &ast::Module) -> Option<FileItemTreeId<Mod>> { + let name = module.name()?.as_name(); + let visibility = self.lower_visibility(module); + let kind = if module.semicolon_token().is_some() { + ModKind::Outline + } else { + ModKind::Inline { + items: module + .item_list() + .map(|list| list.items().flat_map(|item| self.lower_mod_item(&item)).collect()) + .unwrap_or_else(|| { + cov_mark::hit!(name_res_works_for_broken_modules); + Box::new([]) as Box<[_]> + }), + } + }; + let ast_id = self.source_ast_id_map.ast_id(module); + let res = Mod { name, visibility, kind, ast_id }; + Some(id(self.data().mods.alloc(res))) + } + + fn lower_trait(&mut self, trait_def: &ast::Trait) -> Option<FileItemTreeId<Trait>> { + let name = trait_def.name()?.as_name(); + let visibility = self.lower_visibility(trait_def); + let generic_params = self.lower_generic_params(GenericsOwner::Trait(trait_def), trait_def); + let is_auto = trait_def.auto_token().is_some(); + let is_unsafe = trait_def.unsafe_token().is_some(); + let items = trait_def.assoc_item_list().map(|list| { + list.assoc_items() + .filter_map(|item| { + let attrs = RawAttrs::new(self.db, &item, self.hygiene()); + self.lower_assoc_item(&item).map(|item| { + self.add_attrs(ModItem::from(item).into(), attrs); + item + }) + }) + .collect() + }); + let ast_id = self.source_ast_id_map.ast_id(trait_def); + let res = Trait { + name, + visibility, + generic_params, + is_auto, + is_unsafe, + items: items.unwrap_or_default(), + ast_id, + }; + Some(id(self.data().traits.alloc(res))) + } + + fn lower_impl(&mut self, impl_def: &ast::Impl) -> Option<FileItemTreeId<Impl>> { + let generic_params = self.lower_generic_params(GenericsOwner::Impl, impl_def); + // FIXME: If trait lowering fails, due to a non PathType for example, we treat this impl + // as if it was an non-trait impl. Ideally we want to create a unique missing ref that only + // equals itself. + let target_trait = impl_def.trait_().and_then(|tr| self.lower_trait_ref(&tr)); + let self_ty = self.lower_type_ref(&impl_def.self_ty()?); + let is_negative = impl_def.excl_token().is_some(); + + // We cannot use `assoc_items()` here as that does not include macro calls. + let items = impl_def + .assoc_item_list() + .into_iter() + .flat_map(|it| it.assoc_items()) + .filter_map(|item| { + let assoc = self.lower_assoc_item(&item)?; + let attrs = RawAttrs::new(self.db, &item, self.hygiene()); + self.add_attrs(ModItem::from(assoc).into(), attrs); + Some(assoc) + }) + .collect(); + let ast_id = self.source_ast_id_map.ast_id(impl_def); + let res = Impl { generic_params, target_trait, self_ty, is_negative, items, ast_id }; + Some(id(self.data().impls.alloc(res))) + } + + fn lower_use(&mut self, use_item: &ast::Use) -> Option<FileItemTreeId<Import>> { + let visibility = self.lower_visibility(use_item); + let ast_id = self.source_ast_id_map.ast_id(use_item); + let (use_tree, _) = lower_use_tree(self.db, self.hygiene(), use_item.use_tree()?)?; + + let res = Import { visibility, ast_id, use_tree }; + Some(id(self.data().imports.alloc(res))) + } + + fn lower_extern_crate( + &mut self, + extern_crate: &ast::ExternCrate, + ) -> Option<FileItemTreeId<ExternCrate>> { + let name = extern_crate.name_ref()?.as_name(); + let alias = extern_crate.rename().map(|a| { + a.name().map(|it| it.as_name()).map_or(ImportAlias::Underscore, ImportAlias::Alias) + }); + let visibility = self.lower_visibility(extern_crate); + let ast_id = self.source_ast_id_map.ast_id(extern_crate); + + let res = ExternCrate { name, alias, visibility, ast_id }; + Some(id(self.data().extern_crates.alloc(res))) + } + + fn lower_macro_call(&mut self, m: &ast::MacroCall) -> Option<FileItemTreeId<MacroCall>> { + let path = Interned::new(ModPath::from_src(self.db.upcast(), m.path()?, self.hygiene())?); + let ast_id = self.source_ast_id_map.ast_id(m); + let expand_to = hir_expand::ExpandTo::from_call_site(m); + let res = MacroCall { path, ast_id, expand_to }; + Some(id(self.data().macro_calls.alloc(res))) + } + + fn lower_macro_rules(&mut self, m: &ast::MacroRules) -> Option<FileItemTreeId<MacroRules>> { + let name = m.name().map(|it| it.as_name())?; + let ast_id = self.source_ast_id_map.ast_id(m); + + let res = MacroRules { name, ast_id }; + Some(id(self.data().macro_rules.alloc(res))) + } + + fn lower_macro_def(&mut self, m: &ast::MacroDef) -> Option<FileItemTreeId<MacroDef>> { + let name = m.name().map(|it| it.as_name())?; + + let ast_id = self.source_ast_id_map.ast_id(m); + let visibility = self.lower_visibility(m); + + let res = MacroDef { name, ast_id, visibility }; + Some(id(self.data().macro_defs.alloc(res))) + } + + fn lower_extern_block(&mut self, block: &ast::ExternBlock) -> FileItemTreeId<ExternBlock> { + let ast_id = self.source_ast_id_map.ast_id(block); + let abi = block.abi().map(lower_abi); + let children: Box<[_]> = block.extern_item_list().map_or(Box::new([]), |list| { + list.extern_items() + .filter_map(|item| { + // Note: All items in an `extern` block need to be lowered as if they're outside of one + // (in other words, the knowledge that they're in an extern block must not be used). + // This is because an extern block can contain macros whose ItemTree's top-level items + // should be considered to be in an extern block too. + let attrs = RawAttrs::new(self.db, &item, self.hygiene()); + let id: ModItem = match item { + ast::ExternItem::Fn(ast) => self.lower_function(&ast)?.into(), + ast::ExternItem::Static(ast) => self.lower_static(&ast)?.into(), + ast::ExternItem::TypeAlias(ty) => self.lower_type_alias(&ty)?.into(), + ast::ExternItem::MacroCall(call) => self.lower_macro_call(&call)?.into(), + }; + self.add_attrs(id.into(), attrs); + Some(id) + }) + .collect() + }); + + let res = ExternBlock { abi, ast_id, children }; + id(self.data().extern_blocks.alloc(res)) + } + + fn lower_generic_params( + &mut self, + owner: GenericsOwner<'_>, + node: &dyn ast::HasGenericParams, + ) -> Interned<GenericParams> { + let mut generics = GenericParams::default(); + match owner { + GenericsOwner::Function(_) + | GenericsOwner::Struct + | GenericsOwner::Enum + | GenericsOwner::Union + | GenericsOwner::TypeAlias => { + generics.fill(&self.body_ctx, node); + } + GenericsOwner::Trait(trait_def) => { + // traits get the Self type as an implicit first type parameter + generics.type_or_consts.alloc( + TypeParamData { + name: Some(name![Self]), + default: None, + provenance: TypeParamProvenance::TraitSelf, + } + .into(), + ); + // add super traits as bounds on Self + // i.e., trait Foo: Bar is equivalent to trait Foo where Self: Bar + let self_param = TypeRef::Path(name![Self].into()); + generics.fill_bounds(&self.body_ctx, trait_def, Either::Left(self_param)); + generics.fill(&self.body_ctx, node); + } + GenericsOwner::Impl => { + // Note that we don't add `Self` here: in `impl`s, `Self` is not a + // type-parameter, but rather is a type-alias for impl's target + // type, so this is handled by the resolver. + generics.fill(&self.body_ctx, node); + } + } + + generics.shrink_to_fit(); + Interned::new(generics) + } + + fn lower_type_bounds(&mut self, node: &dyn ast::HasTypeBounds) -> Vec<Interned<TypeBound>> { + match node.type_bound_list() { + Some(bound_list) => bound_list + .bounds() + .map(|it| Interned::new(TypeBound::from_ast(&self.body_ctx, it))) + .collect(), + None => Vec::new(), + } + } + + fn lower_visibility(&mut self, item: &dyn ast::HasVisibility) -> RawVisibilityId { + let vis = RawVisibility::from_ast_with_hygiene(self.db, item.visibility(), self.hygiene()); + self.data().vis.alloc(vis) + } + + fn lower_trait_ref(&mut self, trait_ref: &ast::Type) -> Option<Interned<TraitRef>> { + let trait_ref = TraitRef::from_ast(&self.body_ctx, trait_ref.clone())?; + Some(Interned::new(trait_ref)) + } + + fn lower_type_ref(&mut self, type_ref: &ast::Type) -> Interned<TypeRef> { + let tyref = TypeRef::from_ast(&self.body_ctx, type_ref.clone()); + Interned::new(tyref) + } + + fn lower_type_ref_opt(&mut self, type_ref: Option<ast::Type>) -> Interned<TypeRef> { + match type_ref.map(|ty| self.lower_type_ref(&ty)) { + Some(it) => it, + None => Interned::new(TypeRef::Error), + } + } + + fn next_field_idx(&self) -> Idx<Field> { + Idx::from_raw(RawIdx::from( + self.tree.data.as_ref().map_or(0, |data| data.fields.len() as u32), + )) + } + fn next_variant_idx(&self) -> Idx<Variant> { + Idx::from_raw(RawIdx::from( + self.tree.data.as_ref().map_or(0, |data| data.variants.len() as u32), + )) + } + fn next_param_idx(&self) -> Idx<Param> { + Idx::from_raw(RawIdx::from( + self.tree.data.as_ref().map_or(0, |data| data.params.len() as u32), + )) + } +} + +fn desugar_future_path(orig: TypeRef) -> Path { + let path = path![core::future::Future]; + let mut generic_args: Vec<_> = + std::iter::repeat(None).take(path.segments().len() - 1).collect(); + let mut last = GenericArgs::empty(); + let binding = + AssociatedTypeBinding { name: name![Output], type_ref: Some(orig), bounds: Vec::new() }; + last.bindings.push(binding); + generic_args.push(Some(Interned::new(last))); + + Path::from_known_path(path, generic_args) +} + +enum GenericsOwner<'a> { + /// We need access to the partially-lowered `Function` for lowering `impl Trait` in argument + /// position. + Function(&'a Function), + Struct, + Enum, + Union, + /// The `TraitDef` is needed to fill the source map for the implicit `Self` parameter. + Trait(&'a ast::Trait), + TypeAlias, + Impl, +} + +fn lower_abi(abi: ast::Abi) -> Interned<str> { + // FIXME: Abi::abi() -> Option<SyntaxToken>? + match abi.syntax().last_token() { + Some(tok) if tok.kind() == SyntaxKind::STRING => { + // FIXME: Better way to unescape? + Interned::new_str(tok.text().trim_matches('"')) + } + _ => { + // `extern` default to be `extern "C"`. + Interned::new_str("C") + } + } +} + +struct UseTreeLowering<'a> { + db: &'a dyn DefDatabase, + hygiene: &'a Hygiene, + mapping: Arena<ast::UseTree>, +} + +impl UseTreeLowering<'_> { + fn lower_use_tree(&mut self, tree: ast::UseTree) -> Option<UseTree> { + if let Some(use_tree_list) = tree.use_tree_list() { + let prefix = match tree.path() { + // E.g. use something::{{{inner}}}; + None => None, + // E.g. `use something::{inner}` (prefix is `None`, path is `something`) + // or `use something::{path::{inner::{innerer}}}` (prefix is `something::path`, path is `inner`) + Some(path) => { + match ModPath::from_src(self.db.upcast(), path, self.hygiene) { + Some(it) => Some(it), + None => return None, // FIXME: report errors somewhere + } + } + }; + + let list = + use_tree_list.use_trees().filter_map(|tree| self.lower_use_tree(tree)).collect(); + + Some( + self.use_tree( + UseTreeKind::Prefixed { prefix: prefix.map(Interned::new), list }, + tree, + ), + ) + } else { + let is_glob = tree.star_token().is_some(); + let path = match tree.path() { + Some(path) => Some(ModPath::from_src(self.db.upcast(), path, self.hygiene)?), + None => None, + }; + let alias = tree.rename().map(|a| { + a.name().map(|it| it.as_name()).map_or(ImportAlias::Underscore, ImportAlias::Alias) + }); + if alias.is_some() && is_glob { + return None; + } + + match (path, alias, is_glob) { + (path, None, true) => { + if path.is_none() { + cov_mark::hit!(glob_enum_group); + } + Some(self.use_tree(UseTreeKind::Glob { path: path.map(Interned::new) }, tree)) + } + // Globs can't be renamed + (_, Some(_), true) | (None, None, false) => None, + // `bla::{ as Name}` is invalid + (None, Some(_), false) => None, + (Some(path), alias, false) => Some( + self.use_tree(UseTreeKind::Single { path: Interned::new(path), alias }, tree), + ), + } + } + } + + fn use_tree(&mut self, kind: UseTreeKind, ast: ast::UseTree) -> UseTree { + let index = self.mapping.alloc(ast); + UseTree { index, kind } + } +} + +pub(super) fn lower_use_tree( + db: &dyn DefDatabase, + hygiene: &Hygiene, + tree: ast::UseTree, +) -> Option<(UseTree, Arena<ast::UseTree>)> { + let mut lowering = UseTreeLowering { db, hygiene, mapping: Arena::new() }; + let tree = lowering.lower_use_tree(tree)?; + Some((tree, lowering.mapping)) +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs new file mode 100644 index 000000000..f12d9a127 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs @@ -0,0 +1,754 @@ +//! `ItemTree` debug printer. + +use std::fmt::{self, Write}; + +use itertools::Itertools; + +use crate::{ + attr::RawAttrs, + generics::{TypeOrConstParamData, WherePredicate, WherePredicateTypeTarget}, + path::GenericArg, + type_ref::TraitBoundModifier, + visibility::RawVisibility, +}; + +use super::*; + +pub(super) fn print_item_tree(tree: &ItemTree) -> String { + let mut p = Printer { tree, buf: String::new(), indent_level: 0, needs_indent: true }; + + if let Some(attrs) = tree.attrs.get(&AttrOwner::TopLevel) { + p.print_attrs(attrs, true); + } + p.blank(); + + for item in tree.top_level_items() { + p.print_mod_item(*item); + } + + let mut s = p.buf.trim_end_matches('\n').to_string(); + s.push('\n'); + s +} + +macro_rules! w { + ($dst:expr, $($arg:tt)*) => { + { let _ = write!($dst, $($arg)*); } + }; +} + +macro_rules! wln { + ($dst:expr) => { + { let _ = writeln!($dst); } + }; + ($dst:expr, $($arg:tt)*) => { + { let _ = writeln!($dst, $($arg)*); } + }; +} + +struct Printer<'a> { + tree: &'a ItemTree, + buf: String, + indent_level: usize, + needs_indent: bool, +} + +impl<'a> Printer<'a> { + fn indented(&mut self, f: impl FnOnce(&mut Self)) { + self.indent_level += 1; + wln!(self); + f(self); + self.indent_level -= 1; + self.buf = self.buf.trim_end_matches('\n').to_string(); + } + + /// Ensures that a blank line is output before the next text. + fn blank(&mut self) { + let mut iter = self.buf.chars().rev().fuse(); + match (iter.next(), iter.next()) { + (Some('\n'), Some('\n') | None) | (None, None) => {} + (Some('\n'), Some(_)) => { + self.buf.push('\n'); + } + (Some(_), _) => { + self.buf.push('\n'); + self.buf.push('\n'); + } + (None, Some(_)) => unreachable!(), + } + } + + fn whitespace(&mut self) { + match self.buf.chars().next_back() { + None | Some('\n' | ' ') => {} + _ => self.buf.push(' '), + } + } + + fn print_attrs(&mut self, attrs: &RawAttrs, inner: bool) { + let inner = if inner { "!" } else { "" }; + for attr in &**attrs { + wln!( + self, + "#{}[{}{}]", + inner, + attr.path, + attr.input.as_ref().map(|it| it.to_string()).unwrap_or_default(), + ); + } + } + + fn print_attrs_of(&mut self, of: impl Into<AttrOwner>) { + if let Some(attrs) = self.tree.attrs.get(&of.into()) { + self.print_attrs(attrs, false); + } + } + + fn print_visibility(&mut self, vis: RawVisibilityId) { + match &self.tree[vis] { + RawVisibility::Module(path) => w!(self, "pub({}) ", path), + RawVisibility::Public => w!(self, "pub "), + }; + } + + fn print_fields(&mut self, fields: &Fields) { + match fields { + Fields::Record(fields) => { + self.whitespace(); + w!(self, "{{"); + self.indented(|this| { + for field in fields.clone() { + let Field { visibility, name, type_ref } = &this.tree[field]; + this.print_attrs_of(field); + this.print_visibility(*visibility); + w!(this, "{}: ", name); + this.print_type_ref(type_ref); + wln!(this, ","); + } + }); + w!(self, "}}"); + } + Fields::Tuple(fields) => { + w!(self, "("); + self.indented(|this| { + for field in fields.clone() { + let Field { visibility, name, type_ref } = &this.tree[field]; + this.print_attrs_of(field); + this.print_visibility(*visibility); + w!(this, "{}: ", name); + this.print_type_ref(type_ref); + wln!(this, ","); + } + }); + w!(self, ")"); + } + Fields::Unit => {} + } + } + + fn print_fields_and_where_clause(&mut self, fields: &Fields, params: &GenericParams) { + match fields { + Fields::Record(_) => { + if self.print_where_clause(params) { + wln!(self); + } + self.print_fields(fields); + } + Fields::Unit => { + self.print_where_clause(params); + self.print_fields(fields); + } + Fields::Tuple(_) => { + self.print_fields(fields); + self.print_where_clause(params); + } + } + } + + fn print_use_tree(&mut self, use_tree: &UseTree) { + match &use_tree.kind { + UseTreeKind::Single { path, alias } => { + w!(self, "{}", path); + if let Some(alias) = alias { + w!(self, " as {}", alias); + } + } + UseTreeKind::Glob { path } => { + if let Some(path) = path { + w!(self, "{}::", path); + } + w!(self, "*"); + } + UseTreeKind::Prefixed { prefix, list } => { + if let Some(prefix) = prefix { + w!(self, "{}::", prefix); + } + w!(self, "{{"); + for (i, tree) in list.iter().enumerate() { + if i != 0 { + w!(self, ", "); + } + self.print_use_tree(tree); + } + w!(self, "}}"); + } + } + } + + fn print_mod_item(&mut self, item: ModItem) { + self.print_attrs_of(item); + + match item { + ModItem::Import(it) => { + let Import { visibility, use_tree, ast_id: _ } = &self.tree[it]; + self.print_visibility(*visibility); + w!(self, "use "); + self.print_use_tree(use_tree); + wln!(self, ";"); + } + ModItem::ExternCrate(it) => { + let ExternCrate { name, alias, visibility, ast_id: _ } = &self.tree[it]; + self.print_visibility(*visibility); + w!(self, "extern crate {}", name); + if let Some(alias) = alias { + w!(self, " as {}", alias); + } + wln!(self, ";"); + } + ModItem::ExternBlock(it) => { + let ExternBlock { abi, ast_id: _, children } = &self.tree[it]; + w!(self, "extern "); + if let Some(abi) = abi { + w!(self, "\"{}\" ", abi); + } + w!(self, "{{"); + self.indented(|this| { + for child in &**children { + this.print_mod_item(*child); + } + }); + wln!(self, "}}"); + } + ModItem::Function(it) => { + let Function { + name, + visibility, + explicit_generic_params, + abi, + params, + ret_type, + async_ret_type: _, + ast_id: _, + flags, + } = &self.tree[it]; + self.print_visibility(*visibility); + if flags.contains(FnFlags::HAS_DEFAULT_KW) { + w!(self, "default "); + } + if flags.contains(FnFlags::HAS_CONST_KW) { + w!(self, "const "); + } + if flags.contains(FnFlags::HAS_ASYNC_KW) { + w!(self, "async "); + } + if flags.contains(FnFlags::HAS_UNSAFE_KW) { + w!(self, "unsafe "); + } + if let Some(abi) = abi { + w!(self, "extern \"{}\" ", abi); + } + w!(self, "fn {}", name); + self.print_generic_params(explicit_generic_params); + w!(self, "("); + if !params.is_empty() { + self.indented(|this| { + for (i, param) in params.clone().enumerate() { + this.print_attrs_of(param); + match &this.tree[param] { + Param::Normal(name, ty) => { + match name { + Some(name) => w!(this, "{}: ", name), + None => w!(this, "_: "), + } + this.print_type_ref(ty); + w!(this, ","); + if flags.contains(FnFlags::HAS_SELF_PARAM) && i == 0 { + wln!(this, " // self"); + } else { + wln!(this); + } + } + Param::Varargs => { + wln!(this, "..."); + } + }; + } + }); + } + w!(self, ") -> "); + self.print_type_ref(ret_type); + self.print_where_clause(explicit_generic_params); + if flags.contains(FnFlags::HAS_BODY) { + wln!(self, " {{ ... }}"); + } else { + wln!(self, ";"); + } + } + ModItem::Struct(it) => { + let Struct { visibility, name, fields, generic_params, ast_id: _ } = &self.tree[it]; + self.print_visibility(*visibility); + w!(self, "struct {}", name); + self.print_generic_params(generic_params); + self.print_fields_and_where_clause(fields, generic_params); + if matches!(fields, Fields::Record(_)) { + wln!(self); + } else { + wln!(self, ";"); + } + } + ModItem::Union(it) => { + let Union { name, visibility, fields, generic_params, ast_id: _ } = &self.tree[it]; + self.print_visibility(*visibility); + w!(self, "union {}", name); + self.print_generic_params(generic_params); + self.print_fields_and_where_clause(fields, generic_params); + if matches!(fields, Fields::Record(_)) { + wln!(self); + } else { + wln!(self, ";"); + } + } + ModItem::Enum(it) => { + let Enum { name, visibility, variants, generic_params, ast_id: _ } = &self.tree[it]; + self.print_visibility(*visibility); + w!(self, "enum {}", name); + self.print_generic_params(generic_params); + self.print_where_clause_and_opening_brace(generic_params); + self.indented(|this| { + for variant in variants.clone() { + let Variant { name, fields } = &this.tree[variant]; + this.print_attrs_of(variant); + w!(this, "{}", name); + this.print_fields(fields); + wln!(this, ","); + } + }); + wln!(self, "}}"); + } + ModItem::Const(it) => { + let Const { name, visibility, type_ref, ast_id: _ } = &self.tree[it]; + self.print_visibility(*visibility); + w!(self, "const "); + match name { + Some(name) => w!(self, "{}", name), + None => w!(self, "_"), + } + w!(self, ": "); + self.print_type_ref(type_ref); + wln!(self, " = _;"); + } + ModItem::Static(it) => { + let Static { name, visibility, mutable, type_ref, ast_id: _ } = &self.tree[it]; + self.print_visibility(*visibility); + w!(self, "static "); + if *mutable { + w!(self, "mut "); + } + w!(self, "{}: ", name); + self.print_type_ref(type_ref); + w!(self, " = _;"); + wln!(self); + } + ModItem::Trait(it) => { + let Trait { + name, + visibility, + is_auto, + is_unsafe, + items, + generic_params, + ast_id: _, + } = &self.tree[it]; + self.print_visibility(*visibility); + if *is_unsafe { + w!(self, "unsafe "); + } + if *is_auto { + w!(self, "auto "); + } + w!(self, "trait {}", name); + self.print_generic_params(generic_params); + self.print_where_clause_and_opening_brace(generic_params); + self.indented(|this| { + for item in &**items { + this.print_mod_item((*item).into()); + } + }); + wln!(self, "}}"); + } + ModItem::Impl(it) => { + let Impl { target_trait, self_ty, is_negative, items, generic_params, ast_id: _ } = + &self.tree[it]; + w!(self, "impl"); + self.print_generic_params(generic_params); + w!(self, " "); + if *is_negative { + w!(self, "!"); + } + if let Some(tr) = target_trait { + self.print_path(&tr.path); + w!(self, " for "); + } + self.print_type_ref(self_ty); + self.print_where_clause_and_opening_brace(generic_params); + self.indented(|this| { + for item in &**items { + this.print_mod_item((*item).into()); + } + }); + wln!(self, "}}"); + } + ModItem::TypeAlias(it) => { + let TypeAlias { name, visibility, bounds, type_ref, generic_params, ast_id: _ } = + &self.tree[it]; + self.print_visibility(*visibility); + w!(self, "type {}", name); + self.print_generic_params(generic_params); + if !bounds.is_empty() { + w!(self, ": "); + self.print_type_bounds(bounds); + } + if let Some(ty) = type_ref { + w!(self, " = "); + self.print_type_ref(ty); + } + self.print_where_clause(generic_params); + w!(self, ";"); + wln!(self); + } + ModItem::Mod(it) => { + let Mod { name, visibility, kind, ast_id: _ } = &self.tree[it]; + self.print_visibility(*visibility); + w!(self, "mod {}", name); + match kind { + ModKind::Inline { items } => { + w!(self, " {{"); + self.indented(|this| { + for item in &**items { + this.print_mod_item(*item); + } + }); + wln!(self, "}}"); + } + ModKind::Outline => { + wln!(self, ";"); + } + } + } + ModItem::MacroCall(it) => { + let MacroCall { path, ast_id: _, expand_to: _ } = &self.tree[it]; + wln!(self, "{}!(...);", path); + } + ModItem::MacroRules(it) => { + let MacroRules { name, ast_id: _ } = &self.tree[it]; + wln!(self, "macro_rules! {} {{ ... }}", name); + } + ModItem::MacroDef(it) => { + let MacroDef { name, visibility, ast_id: _ } = &self.tree[it]; + self.print_visibility(*visibility); + wln!(self, "macro {} {{ ... }}", name); + } + } + + self.blank(); + } + + fn print_type_ref(&mut self, type_ref: &TypeRef) { + // FIXME: deduplicate with `HirDisplay` impl + match type_ref { + TypeRef::Never => w!(self, "!"), + TypeRef::Placeholder => w!(self, "_"), + TypeRef::Tuple(fields) => { + w!(self, "("); + for (i, field) in fields.iter().enumerate() { + if i != 0 { + w!(self, ", "); + } + self.print_type_ref(field); + } + w!(self, ")"); + } + TypeRef::Path(path) => self.print_path(path), + TypeRef::RawPtr(pointee, mtbl) => { + let mtbl = match mtbl { + Mutability::Shared => "*const", + Mutability::Mut => "*mut", + }; + w!(self, "{} ", mtbl); + self.print_type_ref(pointee); + } + TypeRef::Reference(pointee, lt, mtbl) => { + let mtbl = match mtbl { + Mutability::Shared => "", + Mutability::Mut => "mut ", + }; + w!(self, "&"); + if let Some(lt) = lt { + w!(self, "{} ", lt.name); + } + w!(self, "{}", mtbl); + self.print_type_ref(pointee); + } + TypeRef::Array(elem, len) => { + w!(self, "["); + self.print_type_ref(elem); + w!(self, "; {}]", len); + } + TypeRef::Slice(elem) => { + w!(self, "["); + self.print_type_ref(elem); + w!(self, "]"); + } + TypeRef::Fn(args_and_ret, varargs) => { + let ((_, return_type), args) = + args_and_ret.split_last().expect("TypeRef::Fn is missing return type"); + w!(self, "fn("); + for (i, (_, typeref)) in args.iter().enumerate() { + if i != 0 { + w!(self, ", "); + } + self.print_type_ref(typeref); + } + if *varargs { + if !args.is_empty() { + w!(self, ", "); + } + w!(self, "..."); + } + w!(self, ") -> "); + self.print_type_ref(return_type); + } + TypeRef::Macro(_ast_id) => { + w!(self, "<macro>"); + } + TypeRef::Error => w!(self, "{{unknown}}"), + TypeRef::ImplTrait(bounds) => { + w!(self, "impl "); + self.print_type_bounds(bounds); + } + TypeRef::DynTrait(bounds) => { + w!(self, "dyn "); + self.print_type_bounds(bounds); + } + } + } + + fn print_type_bounds(&mut self, bounds: &[Interned<TypeBound>]) { + for (i, bound) in bounds.iter().enumerate() { + if i != 0 { + w!(self, " + "); + } + + match bound.as_ref() { + TypeBound::Path(path, modifier) => { + match modifier { + TraitBoundModifier::None => (), + TraitBoundModifier::Maybe => w!(self, "?"), + } + self.print_path(path) + } + TypeBound::ForLifetime(lifetimes, path) => { + w!(self, "for<{}> ", lifetimes.iter().format(", ")); + self.print_path(path); + } + TypeBound::Lifetime(lt) => w!(self, "{}", lt.name), + TypeBound::Error => w!(self, "{{unknown}}"), + } + } + } + + fn print_path(&mut self, path: &Path) { + match path.type_anchor() { + Some(anchor) => { + w!(self, "<"); + self.print_type_ref(anchor); + w!(self, ">::"); + } + None => match path.kind() { + PathKind::Plain => {} + PathKind::Super(0) => w!(self, "self::"), + PathKind::Super(n) => { + for _ in 0..*n { + w!(self, "super::"); + } + } + PathKind::Crate => w!(self, "crate::"), + PathKind::Abs => w!(self, "::"), + PathKind::DollarCrate(_) => w!(self, "$crate::"), + }, + } + + for (i, segment) in path.segments().iter().enumerate() { + if i != 0 { + w!(self, "::"); + } + + w!(self, "{}", segment.name); + if let Some(generics) = segment.args_and_bindings { + // NB: these are all in type position, so `::<` turbofish syntax is not necessary + w!(self, "<"); + let mut first = true; + let args = if generics.has_self_type { + let (self_ty, args) = generics.args.split_first().unwrap(); + w!(self, "Self="); + self.print_generic_arg(self_ty); + first = false; + args + } else { + &generics.args + }; + for arg in args { + if !first { + w!(self, ", "); + } + first = false; + self.print_generic_arg(arg); + } + for binding in &generics.bindings { + if !first { + w!(self, ", "); + } + first = false; + w!(self, "{}", binding.name); + if !binding.bounds.is_empty() { + w!(self, ": "); + self.print_type_bounds(&binding.bounds); + } + if let Some(ty) = &binding.type_ref { + w!(self, " = "); + self.print_type_ref(ty); + } + } + + w!(self, ">"); + } + } + } + + fn print_generic_arg(&mut self, arg: &GenericArg) { + match arg { + GenericArg::Type(ty) => self.print_type_ref(ty), + GenericArg::Const(c) => w!(self, "{}", c), + GenericArg::Lifetime(lt) => w!(self, "{}", lt.name), + } + } + + fn print_generic_params(&mut self, params: &GenericParams) { + if params.type_or_consts.is_empty() && params.lifetimes.is_empty() { + return; + } + + w!(self, "<"); + let mut first = true; + for (_, lt) in params.lifetimes.iter() { + if !first { + w!(self, ", "); + } + first = false; + w!(self, "{}", lt.name); + } + for (idx, x) in params.type_or_consts.iter() { + if !first { + w!(self, ", "); + } + first = false; + match x { + TypeOrConstParamData::TypeParamData(ty) => match &ty.name { + Some(name) => w!(self, "{}", name), + None => w!(self, "_anon_{}", idx.into_raw()), + }, + TypeOrConstParamData::ConstParamData(konst) => { + w!(self, "const {}: ", konst.name); + self.print_type_ref(&konst.ty); + } + } + } + w!(self, ">"); + } + + fn print_where_clause_and_opening_brace(&mut self, params: &GenericParams) { + if self.print_where_clause(params) { + w!(self, "\n{{"); + } else { + self.whitespace(); + w!(self, "{{"); + } + } + + fn print_where_clause(&mut self, params: &GenericParams) -> bool { + if params.where_predicates.is_empty() { + return false; + } + + w!(self, "\nwhere"); + self.indented(|this| { + for (i, pred) in params.where_predicates.iter().enumerate() { + if i != 0 { + wln!(this, ","); + } + + let (target, bound) = match pred { + WherePredicate::TypeBound { target, bound } => (target, bound), + WherePredicate::Lifetime { target, bound } => { + wln!(this, "{}: {},", target.name, bound.name); + continue; + } + WherePredicate::ForLifetime { lifetimes, target, bound } => { + w!(this, "for<"); + for (i, lt) in lifetimes.iter().enumerate() { + if i != 0 { + w!(this, ", "); + } + w!(this, "{}", lt); + } + w!(this, "> "); + (target, bound) + } + }; + + match target { + WherePredicateTypeTarget::TypeRef(ty) => this.print_type_ref(ty), + WherePredicateTypeTarget::TypeOrConstParam(id) => { + match ¶ms.type_or_consts[*id].name() { + Some(name) => w!(this, "{}", name), + None => w!(this, "_anon_{}", id.into_raw()), + } + } + } + w!(this, ": "); + this.print_type_bounds(std::slice::from_ref(bound)); + } + }); + true + } +} + +impl<'a> Write for Printer<'a> { + fn write_str(&mut self, s: &str) -> fmt::Result { + for line in s.split_inclusive('\n') { + if self.needs_indent { + match self.buf.chars().last() { + Some('\n') | None => {} + _ => self.buf.push('\n'), + } + self.buf.push_str(&" ".repeat(self.indent_level)); + self.needs_indent = false; + } + + self.buf.push_str(line); + self.needs_indent = line.ends_with('\n'); + } + + Ok(()) + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/tests.rs new file mode 100644 index 000000000..5cdf36cc6 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/tests.rs @@ -0,0 +1,360 @@ +use base_db::fixture::WithFixture; +use expect_test::{expect, Expect}; + +use crate::{db::DefDatabase, test_db::TestDB}; + +fn check(ra_fixture: &str, expect: Expect) { + let (db, file_id) = TestDB::with_single_file(ra_fixture); + let item_tree = db.file_item_tree(file_id.into()); + let pretty = item_tree.pretty_print(); + expect.assert_eq(&pretty); +} + +#[test] +fn imports() { + check( + r#" +//! file comment +#![no_std] +//! another file comment + +extern crate self as renamed; +pub(super) extern crate bli; + +pub use crate::path::{nested, items as renamed, Trait as _}; +use globs::*; + +/// docs on import +use crate::{A, B}; + +use a::{c, d::{e}}; + "#, + expect![[r##" + #![doc = " file comment"] + #![no_std] + #![doc = " another file comment"] + + pub(self) extern crate self as renamed; + + pub(super) extern crate bli; + + pub use crate::path::{nested, items as renamed, Trait as _}; + + pub(self) use globs::*; + + #[doc = " docs on import"] + pub(self) use crate::{A, B}; + + pub(self) use a::{c, d::{e}}; + "##]], + ); +} + +#[test] +fn extern_blocks() { + check( + r#" +#[on_extern_block] +extern "C" { + #[on_extern_type] + type ExType; + + #[on_extern_static] + static EX_STATIC: u8; + + #[on_extern_fn] + fn ex_fn(); +} + "#, + expect![[r##" + #[on_extern_block] + extern "C" { + #[on_extern_type] + pub(self) type ExType; + + #[on_extern_static] + pub(self) static EX_STATIC: u8 = _; + + #[on_extern_fn] + pub(self) fn ex_fn() -> (); + } + "##]], + ); +} + +#[test] +fn adts() { + check( + r#" +struct Unit; + +#[derive(Debug)] +struct Struct { + /// fld docs + fld: (), +} + +struct Tuple(#[attr] u8); + +union Ize { + a: (), + b: (), +} + +enum E { + /// comment on Unit + Unit, + /// comment on Tuple + Tuple(u8), + Struct { + /// comment on a: u8 + a: u8, + } +} + "#, + expect![[r##" + pub(self) struct Unit; + + #[derive(Debug)] + pub(self) struct Struct { + #[doc = " fld docs"] + pub(self) fld: (), + } + + pub(self) struct Tuple( + #[attr] + pub(self) 0: u8, + ); + + pub(self) union Ize { + pub(self) a: (), + pub(self) b: (), + } + + pub(self) enum E { + #[doc = " comment on Unit"] + Unit, + #[doc = " comment on Tuple"] + Tuple( + pub(self) 0: u8, + ), + Struct { + #[doc = " comment on a: u8"] + pub(self) a: u8, + }, + } + "##]], + ); +} + +#[test] +fn misc() { + check( + r#" +pub static mut ST: () = (); + +const _: Anon = (); + +#[attr] +fn f(#[attr] arg: u8, _: ()) { + #![inner_attr_in_fn] +} + +trait Tr: SuperTrait + 'lifetime { + type Assoc: AssocBound = Default; + fn method(&self); +} + "#, + expect![[r##" + pub static mut ST: () = _; + + pub(self) const _: Anon = _; + + #[attr] + #[inner_attr_in_fn] + pub(self) fn f( + #[attr] + arg: u8, + _: (), + ) -> () { ... } + + pub(self) trait Tr<Self> + where + Self: SuperTrait, + Self: 'lifetime + { + pub(self) type Assoc: AssocBound = Default; + + pub(self) fn method( + _: &Self, // self + ) -> (); + } + "##]], + ); +} + +#[test] +fn modules() { + check( + r#" +/// outer +mod inline { + //! inner + + use super::*; + + fn fn_in_module() {} +} + +mod outline; + "#, + expect![[r##" + #[doc = " outer"] + #[doc = " inner"] + pub(self) mod inline { + pub(self) use super::*; + + pub(self) fn fn_in_module() -> () { ... } + } + + pub(self) mod outline; + "##]], + ); +} + +#[test] +fn macros() { + check( + r#" +macro_rules! m { + () => {}; +} + +pub macro m2() {} + +m!(); + "#, + expect![[r#" + macro_rules! m { ... } + + pub macro m2 { ... } + + m!(...); + "#]], + ); +} + +#[test] +fn mod_paths() { + check( + r#" +struct S { + a: self::Ty, + b: super::SuperTy, + c: super::super::SuperSuperTy, + d: ::abs::Path, + e: crate::Crate, + f: plain::path::Ty, +} + "#, + expect![[r#" + pub(self) struct S { + pub(self) a: self::Ty, + pub(self) b: super::SuperTy, + pub(self) c: super::super::SuperSuperTy, + pub(self) d: ::abs::Path, + pub(self) e: crate::Crate, + pub(self) f: plain::path::Ty, + } + "#]], + ) +} + +#[test] +fn types() { + check( + r#" +struct S { + a: Mixed<'a, T, Item=(), OtherItem=u8>, + b: <Fully as Qualified>::Syntax, + c: <TypeAnchored>::Path::<'a>, + d: dyn for<'a> Trait<'a>, +} + "#, + expect![[r#" + pub(self) struct S { + pub(self) a: Mixed<'a, T, Item = (), OtherItem = u8>, + pub(self) b: Qualified<Self=Fully>::Syntax, + pub(self) c: <TypeAnchored>::Path<'a>, + pub(self) d: dyn for<'a> Trait<'a>, + } + "#]], + ) +} + +#[test] +fn generics() { + check( + r#" +struct S<'a, 'b: 'a, T: Copy + 'a + 'b, const K: u8 = 0> { + field: &'a &'b T, +} + +struct Tuple<T: Copy, U: ?Sized>(T, U); + +impl<'a, 'b: 'a, T: Copy + 'a + 'b, const K: u8 = 0> S<'a, 'b, T, K> { + fn f<G: 'a>(arg: impl Copy) -> impl Copy {} +} + +enum Enum<'a, T, const U: u8> {} +union Union<'a, T, const U: u8> {} + +trait Tr<'a, T: 'a>: Super where Self: for<'a> Tr<'a, T> {} + "#, + expect![[r#" + pub(self) struct S<'a, 'b, T, const K: u8> + where + T: Copy, + T: 'a, + T: 'b + { + pub(self) field: &'a &'b T, + } + + pub(self) struct Tuple<T, U>( + pub(self) 0: T, + pub(self) 1: U, + ) + where + T: Copy, + U: ?Sized; + + impl<'a, 'b, T, const K: u8> S<'a, 'b, T, K> + where + T: Copy, + T: 'a, + T: 'b + { + pub(self) fn f<G>( + arg: impl Copy, + ) -> impl Copy + where + G: 'a { ... } + } + + pub(self) enum Enum<'a, T, const U: u8> { + } + + pub(self) union Union<'a, T, const U: u8> { + } + + pub(self) trait Tr<'a, Self, T> + where + Self: Super, + T: 'a, + Self: for<'a> Tr<'a, T> + { + } + "#]], + ) +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/keys.rs b/src/tools/rust-analyzer/crates/hir-def/src/keys.rs new file mode 100644 index 000000000..c5cb9a2af --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/keys.rs @@ -0,0 +1,70 @@ +//! keys to be used with `DynMap` + +use std::marker::PhantomData; + +use hir_expand::MacroCallId; +use rustc_hash::FxHashMap; +use syntax::{ast, AstNode, AstPtr}; + +use crate::{ + attr::AttrId, + dyn_map::{DynMap, Policy}, + ConstId, EnumId, EnumVariantId, FieldId, FunctionId, ImplId, LifetimeParamId, Macro2Id, + MacroRulesId, ProcMacroId, StaticId, StructId, TraitId, TypeAliasId, TypeOrConstParamId, + UnionId, +}; + +pub type Key<K, V> = crate::dyn_map::Key<K, V, AstPtrPolicy<K, V>>; + +pub const FUNCTION: Key<ast::Fn, FunctionId> = Key::new(); +pub const CONST: Key<ast::Const, ConstId> = Key::new(); +pub const STATIC: Key<ast::Static, StaticId> = Key::new(); +pub const TYPE_ALIAS: Key<ast::TypeAlias, TypeAliasId> = Key::new(); +pub const IMPL: Key<ast::Impl, ImplId> = Key::new(); +pub const TRAIT: Key<ast::Trait, TraitId> = Key::new(); +pub const STRUCT: Key<ast::Struct, StructId> = Key::new(); +pub const UNION: Key<ast::Union, UnionId> = Key::new(); +pub const ENUM: Key<ast::Enum, EnumId> = Key::new(); + +pub const VARIANT: Key<ast::Variant, EnumVariantId> = Key::new(); +pub const TUPLE_FIELD: Key<ast::TupleField, FieldId> = Key::new(); +pub const RECORD_FIELD: Key<ast::RecordField, FieldId> = Key::new(); +pub const TYPE_PARAM: Key<ast::TypeParam, TypeOrConstParamId> = Key::new(); +pub const CONST_PARAM: Key<ast::ConstParam, TypeOrConstParamId> = Key::new(); +pub const LIFETIME_PARAM: Key<ast::LifetimeParam, LifetimeParamId> = Key::new(); + +pub const MACRO_RULES: Key<ast::MacroRules, MacroRulesId> = Key::new(); +pub const MACRO2: Key<ast::MacroDef, Macro2Id> = Key::new(); +pub const PROC_MACRO: Key<ast::Fn, ProcMacroId> = Key::new(); +pub const ATTR_MACRO_CALL: Key<ast::Item, MacroCallId> = Key::new(); +pub const DERIVE_MACRO_CALL: Key<ast::Attr, (AttrId, MacroCallId, Box<[Option<MacroCallId>]>)> = + Key::new(); + +/// XXX: AST Nodes and SyntaxNodes have identity equality semantics: nodes are +/// equal if they point to exactly the same object. +/// +/// In general, we do not guarantee that we have exactly one instance of a +/// syntax tree for each file. We probably should add such guarantee, but, for +/// the time being, we will use identity-less AstPtr comparison. +pub struct AstPtrPolicy<AST, ID> { + _phantom: PhantomData<(AST, ID)>, +} + +impl<AST: AstNode + 'static, ID: 'static> Policy for AstPtrPolicy<AST, ID> { + type K = AST; + type V = ID; + fn insert(map: &mut DynMap, key: AST, value: ID) { + let key = AstPtr::new(&key); + map.map + .entry::<FxHashMap<AstPtr<AST>, ID>>() + .or_insert_with(Default::default) + .insert(key, value); + } + fn get<'a>(map: &'a DynMap, key: &AST) -> Option<&'a ID> { + let key = AstPtr::new(key); + map.map.get::<FxHashMap<AstPtr<AST>, ID>>()?.get(&key) + } + fn is_empty(map: &DynMap) -> bool { + map.map.get::<FxHashMap<AstPtr<AST>, ID>>().map_or(true, |it| it.is_empty()) + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs b/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs new file mode 100644 index 000000000..877850184 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs @@ -0,0 +1,174 @@ +//! Collects lang items: items marked with `#[lang = "..."]` attribute. +//! +//! This attribute to tell the compiler about semi built-in std library +//! features, such as Fn family of traits. +use std::sync::Arc; + +use rustc_hash::FxHashMap; +use syntax::SmolStr; + +use crate::{ + db::DefDatabase, AdtId, AttrDefId, CrateId, EnumId, EnumVariantId, FunctionId, ImplId, + ModuleDefId, StaticId, StructId, TraitId, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum LangItemTarget { + EnumId(EnumId), + FunctionId(FunctionId), + ImplDefId(ImplId), + StaticId(StaticId), + StructId(StructId), + TraitId(TraitId), + EnumVariantId(EnumVariantId), +} + +impl LangItemTarget { + pub fn as_enum(self) -> Option<EnumId> { + match self { + LangItemTarget::EnumId(id) => Some(id), + _ => None, + } + } + + pub fn as_function(self) -> Option<FunctionId> { + match self { + LangItemTarget::FunctionId(id) => Some(id), + _ => None, + } + } + + pub fn as_impl_def(self) -> Option<ImplId> { + match self { + LangItemTarget::ImplDefId(id) => Some(id), + _ => None, + } + } + + pub fn as_static(self) -> Option<StaticId> { + match self { + LangItemTarget::StaticId(id) => Some(id), + _ => None, + } + } + + pub fn as_struct(self) -> Option<StructId> { + match self { + LangItemTarget::StructId(id) => Some(id), + _ => None, + } + } + + pub fn as_trait(self) -> Option<TraitId> { + match self { + LangItemTarget::TraitId(id) => Some(id), + _ => None, + } + } + + pub fn as_enum_variant(self) -> Option<EnumVariantId> { + match self { + LangItemTarget::EnumVariantId(id) => Some(id), + _ => None, + } + } +} + +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct LangItems { + items: FxHashMap<SmolStr, LangItemTarget>, +} + +impl LangItems { + pub fn target(&self, item: &str) -> Option<LangItemTarget> { + self.items.get(item).copied() + } + + /// Salsa query. This will look for lang items in a specific crate. + pub(crate) fn crate_lang_items_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<LangItems> { + let _p = profile::span("crate_lang_items_query"); + + let mut lang_items = LangItems::default(); + + let crate_def_map = db.crate_def_map(krate); + + for (_, module_data) in crate_def_map.modules() { + for impl_def in module_data.scope.impls() { + lang_items.collect_lang_item(db, impl_def, LangItemTarget::ImplDefId) + } + + for def in module_data.scope.declarations() { + match def { + ModuleDefId::TraitId(trait_) => { + lang_items.collect_lang_item(db, trait_, LangItemTarget::TraitId); + db.trait_data(trait_).items.iter().for_each(|&(_, assoc_id)| { + if let crate::AssocItemId::FunctionId(f) = assoc_id { + lang_items.collect_lang_item(db, f, LangItemTarget::FunctionId); + } + }); + } + ModuleDefId::AdtId(AdtId::EnumId(e)) => { + lang_items.collect_lang_item(db, e, LangItemTarget::EnumId); + db.enum_data(e).variants.iter().for_each(|(local_id, _)| { + lang_items.collect_lang_item( + db, + EnumVariantId { parent: e, local_id }, + LangItemTarget::EnumVariantId, + ); + }); + } + ModuleDefId::AdtId(AdtId::StructId(s)) => { + lang_items.collect_lang_item(db, s, LangItemTarget::StructId); + } + ModuleDefId::FunctionId(f) => { + lang_items.collect_lang_item(db, f, LangItemTarget::FunctionId); + } + ModuleDefId::StaticId(s) => { + lang_items.collect_lang_item(db, s, LangItemTarget::StaticId); + } + _ => {} + } + } + } + + Arc::new(lang_items) + } + + /// Salsa query. Look for a lang item, starting from the specified crate and recursively + /// traversing its dependencies. + pub(crate) fn lang_item_query( + db: &dyn DefDatabase, + start_crate: CrateId, + item: SmolStr, + ) -> Option<LangItemTarget> { + let _p = profile::span("lang_item_query"); + let lang_items = db.crate_lang_items(start_crate); + let start_crate_target = lang_items.items.get(&item); + if let Some(&target) = start_crate_target { + return Some(target); + } + db.crate_graph()[start_crate] + .dependencies + .iter() + .find_map(|dep| db.lang_item(dep.crate_id, item.clone())) + } + + fn collect_lang_item<T>( + &mut self, + db: &dyn DefDatabase, + item: T, + constructor: fn(T) -> LangItemTarget, + ) where + T: Into<AttrDefId> + Copy, + { + let _p = profile::span("collect_lang_item"); + if let Some(lang_item_name) = lang_attr(db, item) { + self.items.entry(lang_item_name).or_insert_with(|| constructor(item)); + } + } +} + +pub fn lang_attr(db: &dyn DefDatabase, item: impl Into<AttrDefId> + Copy) -> Option<SmolStr> { + let attrs = db.attrs(item.into()); + attrs.by_key("lang").string_value().cloned() +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs new file mode 100644 index 000000000..56603f4b1 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs @@ -0,0 +1,980 @@ +//! `hir_def` crate contains everything between macro expansion and type +//! inference. +//! +//! It defines various items (structs, enums, traits) which comprises Rust code, +//! as well as an algorithm for resolving paths to such entities. +//! +//! Note that `hir_def` is a work in progress, so not all of the above is +//! actually true. + +#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)] + +#[allow(unused)] +macro_rules! eprintln { + ($($tt:tt)*) => { stdx::eprintln!($($tt)*) }; +} + +pub mod db; + +pub mod attr; +pub mod path; +pub mod type_ref; +pub mod builtin_type; +pub mod builtin_attr; +pub mod per_ns; +pub mod item_scope; + +pub mod dyn_map; +pub mod keys; + +pub mod item_tree; +pub mod intern; + +pub mod adt; +pub mod data; +pub mod generics; +pub mod lang_item; + +pub mod expr; +pub mod body; +pub mod resolver; + +mod trace; +pub mod nameres; + +pub mod src; +pub mod child_by_source; + +pub mod visibility; +pub mod find_path; +pub mod import_map; + +#[cfg(test)] +mod test_db; +#[cfg(test)] +mod macro_expansion_tests; + +use std::{ + hash::{Hash, Hasher}, + sync::Arc, +}; + +use attr::Attr; +use base_db::{impl_intern_key, salsa, CrateId, ProcMacroKind}; +use hir_expand::{ + ast_id_map::FileAstId, + builtin_attr_macro::BuiltinAttrExpander, + builtin_derive_macro::BuiltinDeriveExpander, + builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander}, + eager::{expand_eager_macro, ErrorEmitted, ErrorSink}, + hygiene::Hygiene, + proc_macro::ProcMacroExpander, + AstId, ExpandError, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefId, + MacroDefKind, UnresolvedMacro, +}; +use item_tree::ExternBlock; +use la_arena::Idx; +use nameres::DefMap; +use stdx::impl_from; +use syntax::ast; + +use crate::{ + adt::VariantData, + attr::AttrId, + builtin_type::BuiltinType, + item_tree::{ + Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, MacroDef, MacroRules, ModItem, + Static, Struct, Trait, TypeAlias, Union, + }, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ModuleId { + krate: CrateId, + /// If this `ModuleId` was derived from a `DefMap` for a block expression, this stores the + /// `BlockId` of that block expression. If `None`, this module is part of the crate-level + /// `DefMap` of `krate`. + block: Option<BlockId>, + /// The module's ID in its originating `DefMap`. + pub local_id: LocalModuleId, +} + +impl ModuleId { + pub fn def_map(&self, db: &dyn db::DefDatabase) -> Arc<DefMap> { + match self.block { + Some(block) => { + db.block_def_map(block).unwrap_or_else(|| { + // NOTE: This should be unreachable - all `ModuleId`s come from their `DefMap`s, + // so the `DefMap` here must exist. + unreachable!("no `block_def_map` for `ModuleId` {:?}", self); + }) + } + None => db.crate_def_map(self.krate), + } + } + + pub fn krate(&self) -> CrateId { + self.krate + } + + pub fn containing_module(&self, db: &dyn db::DefDatabase) -> Option<ModuleId> { + self.def_map(db).containing_module(self.local_id) + } + + pub fn containing_block(&self) -> Option<BlockId> { + self.block + } +} + +/// An ID of a module, **local** to a specific crate +pub type LocalModuleId = Idx<nameres::ModuleData>; + +#[derive(Debug)] +pub struct ItemLoc<N: ItemTreeNode> { + pub container: ModuleId, + pub id: ItemTreeId<N>, +} + +impl<N: ItemTreeNode> Clone for ItemLoc<N> { + fn clone(&self) -> Self { + Self { container: self.container, id: self.id } + } +} + +impl<N: ItemTreeNode> Copy for ItemLoc<N> {} + +impl<N: ItemTreeNode> PartialEq for ItemLoc<N> { + fn eq(&self, other: &Self) -> bool { + self.container == other.container && self.id == other.id + } +} + +impl<N: ItemTreeNode> Eq for ItemLoc<N> {} + +impl<N: ItemTreeNode> Hash for ItemLoc<N> { + fn hash<H: Hasher>(&self, state: &mut H) { + self.container.hash(state); + self.id.hash(state); + } +} + +#[derive(Debug)] +pub struct AssocItemLoc<N: ItemTreeNode> { + pub container: ItemContainerId, + pub id: ItemTreeId<N>, +} + +impl<N: ItemTreeNode> Clone for AssocItemLoc<N> { + fn clone(&self) -> Self { + Self { container: self.container, id: self.id } + } +} + +impl<N: ItemTreeNode> Copy for AssocItemLoc<N> {} + +impl<N: ItemTreeNode> PartialEq for AssocItemLoc<N> { + fn eq(&self, other: &Self) -> bool { + self.container == other.container && self.id == other.id + } +} + +impl<N: ItemTreeNode> Eq for AssocItemLoc<N> {} + +impl<N: ItemTreeNode> Hash for AssocItemLoc<N> { + fn hash<H: Hasher>(&self, state: &mut H) { + self.container.hash(state); + self.id.hash(state); + } +} + +macro_rules! impl_intern { + ($id:ident, $loc:ident, $intern:ident, $lookup:ident) => { + impl_intern_key!($id); + + impl Intern for $loc { + type ID = $id; + fn intern(self, db: &dyn db::DefDatabase) -> $id { + db.$intern(self) + } + } + + impl Lookup for $id { + type Data = $loc; + fn lookup(&self, db: &dyn db::DefDatabase) -> $loc { + db.$lookup(*self) + } + } + }; +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct FunctionId(salsa::InternId); +type FunctionLoc = AssocItemLoc<Function>; +impl_intern!(FunctionId, FunctionLoc, intern_function, lookup_intern_function); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct StructId(salsa::InternId); +type StructLoc = ItemLoc<Struct>; +impl_intern!(StructId, StructLoc, intern_struct, lookup_intern_struct); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct UnionId(salsa::InternId); +pub type UnionLoc = ItemLoc<Union>; +impl_intern!(UnionId, UnionLoc, intern_union, lookup_intern_union); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct EnumId(salsa::InternId); +pub type EnumLoc = ItemLoc<Enum>; +impl_intern!(EnumId, EnumLoc, intern_enum, lookup_intern_enum); + +// FIXME: rename to `VariantId`, only enums can ave variants +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct EnumVariantId { + pub parent: EnumId, + pub local_id: LocalEnumVariantId, +} + +pub type LocalEnumVariantId = Idx<adt::EnumVariantData>; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct FieldId { + pub parent: VariantId, + pub local_id: LocalFieldId, +} + +pub type LocalFieldId = Idx<adt::FieldData>; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ConstId(salsa::InternId); +type ConstLoc = AssocItemLoc<Const>; +impl_intern!(ConstId, ConstLoc, intern_const, lookup_intern_const); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct StaticId(salsa::InternId); +pub type StaticLoc = AssocItemLoc<Static>; +impl_intern!(StaticId, StaticLoc, intern_static, lookup_intern_static); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TraitId(salsa::InternId); +pub type TraitLoc = ItemLoc<Trait>; +impl_intern!(TraitId, TraitLoc, intern_trait, lookup_intern_trait); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TypeAliasId(salsa::InternId); +type TypeAliasLoc = AssocItemLoc<TypeAlias>; +impl_intern!(TypeAliasId, TypeAliasLoc, intern_type_alias, lookup_intern_type_alias); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub struct ImplId(salsa::InternId); +type ImplLoc = ItemLoc<Impl>; +impl_intern!(ImplId, ImplLoc, intern_impl, lookup_intern_impl); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub struct ExternBlockId(salsa::InternId); +type ExternBlockLoc = ItemLoc<ExternBlock>; +impl_intern!(ExternBlockId, ExternBlockLoc, intern_extern_block, lookup_intern_extern_block); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum MacroExpander { + Declarative, + BuiltIn(BuiltinFnLikeExpander), + BuiltInAttr(BuiltinAttrExpander), + BuiltInDerive(BuiltinDeriveExpander), + BuiltInEager(EagerExpander), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub struct Macro2Id(salsa::InternId); +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Macro2Loc { + pub container: ModuleId, + pub id: ItemTreeId<MacroDef>, + pub expander: MacroExpander, +} +impl_intern!(Macro2Id, Macro2Loc, intern_macro2, lookup_intern_macro2); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub struct MacroRulesId(salsa::InternId); +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct MacroRulesLoc { + pub container: ModuleId, + pub id: ItemTreeId<MacroRules>, + pub local_inner: bool, + pub expander: MacroExpander, +} +impl_intern!(MacroRulesId, MacroRulesLoc, intern_macro_rules, lookup_intern_macro_rules); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub struct ProcMacroId(salsa::InternId); +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ProcMacroLoc { + // FIXME: this should be a crate? or just a crate-root module + pub container: ModuleId, + pub id: ItemTreeId<Function>, + pub expander: ProcMacroExpander, + pub kind: ProcMacroKind, +} +impl_intern!(ProcMacroId, ProcMacroLoc, intern_proc_macro, lookup_intern_proc_macro); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub struct BlockId(salsa::InternId); +#[derive(Debug, Hash, PartialEq, Eq, Clone)] +pub struct BlockLoc { + ast_id: AstId<ast::BlockExpr>, + /// The containing module. + module: ModuleId, +} +impl_intern!(BlockId, BlockLoc, intern_block, lookup_intern_block); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TypeOrConstParamId { + pub parent: GenericDefId, + pub local_id: LocalTypeOrConstParamId, +} + +/// A TypeOrConstParamId with an invariant that it actually belongs to a type +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TypeParamId(TypeOrConstParamId); + +impl TypeParamId { + pub fn parent(&self) -> GenericDefId { + self.0.parent + } + pub fn local_id(&self) -> LocalTypeOrConstParamId { + self.0.local_id + } +} + +impl TypeParamId { + /// Caller should check if this toc id really belongs to a type + pub fn from_unchecked(x: TypeOrConstParamId) -> Self { + Self(x) + } +} + +impl From<TypeParamId> for TypeOrConstParamId { + fn from(x: TypeParamId) -> Self { + x.0 + } +} + +/// A TypeOrConstParamId with an invariant that it actually belongs to a const +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ConstParamId(TypeOrConstParamId); + +impl ConstParamId { + pub fn parent(&self) -> GenericDefId { + self.0.parent + } + pub fn local_id(&self) -> LocalTypeOrConstParamId { + self.0.local_id + } +} + +impl ConstParamId { + /// Caller should check if this toc id really belongs to a const + pub fn from_unchecked(x: TypeOrConstParamId) -> Self { + Self(x) + } +} + +impl From<ConstParamId> for TypeOrConstParamId { + fn from(x: ConstParamId) -> Self { + x.0 + } +} + +pub type LocalTypeOrConstParamId = Idx<generics::TypeOrConstParamData>; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct LifetimeParamId { + pub parent: GenericDefId, + pub local_id: LocalLifetimeParamId, +} +pub type LocalLifetimeParamId = Idx<generics::LifetimeParamData>; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ItemContainerId { + ExternBlockId(ExternBlockId), + ModuleId(ModuleId), + ImplId(ImplId), + TraitId(TraitId), +} +impl_from!(ModuleId for ItemContainerId); + +/// A Data Type +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum AdtId { + StructId(StructId), + UnionId(UnionId), + EnumId(EnumId), +} +impl_from!(StructId, UnionId, EnumId for AdtId); + +/// A macro +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum MacroId { + Macro2Id(Macro2Id), + MacroRulesId(MacroRulesId), + ProcMacroId(ProcMacroId), +} +impl_from!(Macro2Id, MacroRulesId, ProcMacroId for MacroId); + +impl MacroId { + pub fn is_attribute(self, db: &dyn db::DefDatabase) -> bool { + match self { + MacroId::ProcMacroId(it) => it.lookup(db).kind == ProcMacroKind::Attr, + _ => false, + } + } +} + +/// A generic param +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum GenericParamId { + TypeParamId(TypeParamId), + ConstParamId(ConstParamId), + LifetimeParamId(LifetimeParamId), +} +impl_from!(TypeParamId, LifetimeParamId, ConstParamId for GenericParamId); + +/// The defs which can be visible in the module. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ModuleDefId { + ModuleId(ModuleId), + FunctionId(FunctionId), + AdtId(AdtId), + // Can't be directly declared, but can be imported. + EnumVariantId(EnumVariantId), + ConstId(ConstId), + StaticId(StaticId), + TraitId(TraitId), + TypeAliasId(TypeAliasId), + BuiltinType(BuiltinType), + MacroId(MacroId), +} +impl_from!( + MacroId(Macro2Id, MacroRulesId, ProcMacroId), + ModuleId, + FunctionId, + AdtId(StructId, EnumId, UnionId), + EnumVariantId, + ConstId, + StaticId, + TraitId, + TypeAliasId, + BuiltinType + for ModuleDefId +); + +/// The defs which have a body. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum DefWithBodyId { + FunctionId(FunctionId), + StaticId(StaticId), + ConstId(ConstId), +} + +impl_from!(FunctionId, ConstId, StaticId for DefWithBodyId); + +impl DefWithBodyId { + pub fn as_generic_def_id(self) -> Option<GenericDefId> { + match self { + DefWithBodyId::FunctionId(f) => Some(f.into()), + DefWithBodyId::StaticId(_) => None, + DefWithBodyId::ConstId(c) => Some(c.into()), + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum AssocItemId { + FunctionId(FunctionId), + ConstId(ConstId), + TypeAliasId(TypeAliasId), +} +// FIXME: not every function, ... is actually an assoc item. maybe we should make +// sure that you can only turn actual assoc items into AssocItemIds. This would +// require not implementing From, and instead having some checked way of +// casting them, and somehow making the constructors private, which would be annoying. +impl_from!(FunctionId, ConstId, TypeAliasId for AssocItemId); + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] +pub enum GenericDefId { + FunctionId(FunctionId), + AdtId(AdtId), + TraitId(TraitId), + TypeAliasId(TypeAliasId), + ImplId(ImplId), + // enum variants cannot have generics themselves, but their parent enums + // can, and this makes some code easier to write + EnumVariantId(EnumVariantId), + // consts can have type parameters from their parents (i.e. associated consts of traits) + ConstId(ConstId), +} +impl_from!( + FunctionId, + AdtId(StructId, EnumId, UnionId), + TraitId, + TypeAliasId, + ImplId, + EnumVariantId, + ConstId + for GenericDefId +); + +impl From<AssocItemId> for GenericDefId { + fn from(item: AssocItemId) -> Self { + match item { + AssocItemId::FunctionId(f) => f.into(), + AssocItemId::ConstId(c) => c.into(), + AssocItemId::TypeAliasId(t) => t.into(), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum AttrDefId { + ModuleId(ModuleId), + FieldId(FieldId), + AdtId(AdtId), + FunctionId(FunctionId), + EnumVariantId(EnumVariantId), + StaticId(StaticId), + ConstId(ConstId), + TraitId(TraitId), + TypeAliasId(TypeAliasId), + MacroId(MacroId), + ImplId(ImplId), + GenericParamId(GenericParamId), + ExternBlockId(ExternBlockId), +} + +impl_from!( + ModuleId, + FieldId, + AdtId(StructId, EnumId, UnionId), + EnumVariantId, + StaticId, + ConstId, + FunctionId, + TraitId, + TypeAliasId, + MacroId(Macro2Id, MacroRulesId, ProcMacroId), + ImplId, + GenericParamId + for AttrDefId +); + +impl From<ItemContainerId> for AttrDefId { + fn from(acid: ItemContainerId) -> Self { + match acid { + ItemContainerId::ModuleId(mid) => AttrDefId::ModuleId(mid), + ItemContainerId::ImplId(iid) => AttrDefId::ImplId(iid), + ItemContainerId::TraitId(tid) => AttrDefId::TraitId(tid), + ItemContainerId::ExternBlockId(id) => AttrDefId::ExternBlockId(id), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum VariantId { + EnumVariantId(EnumVariantId), + StructId(StructId), + UnionId(UnionId), +} +impl_from!(EnumVariantId, StructId, UnionId for VariantId); + +impl VariantId { + pub fn variant_data(self, db: &dyn db::DefDatabase) -> Arc<VariantData> { + match self { + VariantId::StructId(it) => db.struct_data(it).variant_data.clone(), + VariantId::UnionId(it) => db.union_data(it).variant_data.clone(), + VariantId::EnumVariantId(it) => { + db.enum_data(it.parent).variants[it.local_id].variant_data.clone() + } + } + } + + pub fn file_id(self, db: &dyn db::DefDatabase) -> HirFileId { + match self { + VariantId::EnumVariantId(it) => it.parent.lookup(db).id.file_id(), + VariantId::StructId(it) => it.lookup(db).id.file_id(), + VariantId::UnionId(it) => it.lookup(db).id.file_id(), + } + } + + pub fn adt_id(self) -> AdtId { + match self { + VariantId::EnumVariantId(it) => it.parent.into(), + VariantId::StructId(it) => it.into(), + VariantId::UnionId(it) => it.into(), + } + } +} + +trait Intern { + type ID; + fn intern(self, db: &dyn db::DefDatabase) -> Self::ID; +} + +pub trait Lookup { + type Data; + fn lookup(&self, db: &dyn db::DefDatabase) -> Self::Data; +} + +pub trait HasModule { + fn module(&self, db: &dyn db::DefDatabase) -> ModuleId; +} + +impl HasModule for ItemContainerId { + fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { + match *self { + ItemContainerId::ModuleId(it) => it, + ItemContainerId::ImplId(it) => it.lookup(db).container, + ItemContainerId::TraitId(it) => it.lookup(db).container, + ItemContainerId::ExternBlockId(it) => it.lookup(db).container, + } + } +} + +impl<N: ItemTreeNode> HasModule for AssocItemLoc<N> { + fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { + self.container.module(db) + } +} + +impl HasModule for AdtId { + fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { + match self { + AdtId::StructId(it) => it.lookup(db).container, + AdtId::UnionId(it) => it.lookup(db).container, + AdtId::EnumId(it) => it.lookup(db).container, + } + } +} + +impl HasModule for VariantId { + fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { + match self { + VariantId::EnumVariantId(it) => it.parent.lookup(db).container, + VariantId::StructId(it) => it.lookup(db).container, + VariantId::UnionId(it) => it.lookup(db).container, + } + } +} + +impl HasModule for MacroId { + fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { + match self { + MacroId::MacroRulesId(it) => it.lookup(db).container, + MacroId::Macro2Id(it) => it.lookup(db).container, + MacroId::ProcMacroId(it) => it.lookup(db).container, + } + } +} + +impl HasModule for DefWithBodyId { + fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { + match self { + DefWithBodyId::FunctionId(it) => it.lookup(db).module(db), + DefWithBodyId::StaticId(it) => it.lookup(db).module(db), + DefWithBodyId::ConstId(it) => it.lookup(db).module(db), + } + } +} + +impl DefWithBodyId { + pub fn as_mod_item(self, db: &dyn db::DefDatabase) -> ModItem { + match self { + DefWithBodyId::FunctionId(it) => it.lookup(db).id.value.into(), + DefWithBodyId::StaticId(it) => it.lookup(db).id.value.into(), + DefWithBodyId::ConstId(it) => it.lookup(db).id.value.into(), + } + } +} + +impl HasModule for GenericDefId { + fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { + match self { + GenericDefId::FunctionId(it) => it.lookup(db).module(db), + GenericDefId::AdtId(it) => it.module(db), + GenericDefId::TraitId(it) => it.lookup(db).container, + GenericDefId::TypeAliasId(it) => it.lookup(db).module(db), + GenericDefId::ImplId(it) => it.lookup(db).container, + GenericDefId::EnumVariantId(it) => it.parent.lookup(db).container, + GenericDefId::ConstId(it) => it.lookup(db).module(db), + } + } +} + +impl HasModule for TypeAliasId { + fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { + self.lookup(db).module(db) + } +} + +impl HasModule for TraitId { + fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { + self.lookup(db).container + } +} + +impl ModuleDefId { + /// Returns the module containing `self` (or `self`, if `self` is itself a module). + /// + /// Returns `None` if `self` refers to a primitive type. + pub fn module(&self, db: &dyn db::DefDatabase) -> Option<ModuleId> { + Some(match self { + ModuleDefId::ModuleId(id) => *id, + ModuleDefId::FunctionId(id) => id.lookup(db).module(db), + ModuleDefId::AdtId(id) => id.module(db), + ModuleDefId::EnumVariantId(id) => id.parent.lookup(db).container, + ModuleDefId::ConstId(id) => id.lookup(db).container.module(db), + ModuleDefId::StaticId(id) => id.lookup(db).module(db), + ModuleDefId::TraitId(id) => id.lookup(db).container, + ModuleDefId::TypeAliasId(id) => id.lookup(db).module(db), + ModuleDefId::MacroId(id) => id.module(db), + ModuleDefId::BuiltinType(_) => return None, + }) + } +} + +impl AttrDefId { + pub fn krate(&self, db: &dyn db::DefDatabase) -> CrateId { + match self { + AttrDefId::ModuleId(it) => it.krate, + AttrDefId::FieldId(it) => it.parent.module(db).krate, + AttrDefId::AdtId(it) => it.module(db).krate, + AttrDefId::FunctionId(it) => it.lookup(db).module(db).krate, + AttrDefId::EnumVariantId(it) => it.parent.lookup(db).container.krate, + AttrDefId::StaticId(it) => it.lookup(db).module(db).krate, + AttrDefId::ConstId(it) => it.lookup(db).module(db).krate, + AttrDefId::TraitId(it) => it.lookup(db).container.krate, + AttrDefId::TypeAliasId(it) => it.lookup(db).module(db).krate, + AttrDefId::ImplId(it) => it.lookup(db).container.krate, + AttrDefId::ExternBlockId(it) => it.lookup(db).container.krate, + AttrDefId::GenericParamId(it) => { + match it { + GenericParamId::TypeParamId(it) => it.parent(), + GenericParamId::ConstParamId(it) => it.parent(), + GenericParamId::LifetimeParamId(it) => it.parent, + } + .module(db) + .krate + } + AttrDefId::MacroId(it) => it.module(db).krate, + } + } +} + +/// A helper trait for converting to MacroCallId +pub trait AsMacroCall { + fn as_call_id( + &self, + db: &dyn db::DefDatabase, + krate: CrateId, + resolver: impl Fn(path::ModPath) -> Option<MacroDefId>, + ) -> Option<MacroCallId> { + self.as_call_id_with_errors(db, krate, resolver, &mut |_| ()).ok()?.ok() + } + + fn as_call_id_with_errors( + &self, + db: &dyn db::DefDatabase, + krate: CrateId, + resolver: impl Fn(path::ModPath) -> Option<MacroDefId>, + error_sink: &mut dyn FnMut(ExpandError), + ) -> Result<Result<MacroCallId, ErrorEmitted>, UnresolvedMacro>; +} + +impl AsMacroCall for InFile<&ast::MacroCall> { + fn as_call_id_with_errors( + &self, + db: &dyn db::DefDatabase, + krate: CrateId, + resolver: impl Fn(path::ModPath) -> Option<MacroDefId>, + mut error_sink: &mut dyn FnMut(ExpandError), + ) -> Result<Result<MacroCallId, ErrorEmitted>, UnresolvedMacro> { + let expands_to = hir_expand::ExpandTo::from_call_site(self.value); + let ast_id = AstId::new(self.file_id, db.ast_id_map(self.file_id).ast_id(self.value)); + let h = Hygiene::new(db.upcast(), self.file_id); + let path = + self.value.path().and_then(|path| path::ModPath::from_src(db.upcast(), path, &h)); + + let path = match error_sink + .option(path, || ExpandError::Other("malformed macro invocation".into())) + { + Ok(path) => path, + Err(error) => { + return Ok(Err(error)); + } + }; + + macro_call_as_call_id( + db, + &AstIdWithPath::new(ast_id.file_id, ast_id.value, path), + expands_to, + krate, + resolver, + error_sink, + ) + } +} + +/// Helper wrapper for `AstId` with `ModPath` +#[derive(Clone, Debug, Eq, PartialEq)] +struct AstIdWithPath<T: ast::AstNode> { + ast_id: AstId<T>, + path: path::ModPath, +} + +impl<T: ast::AstNode> AstIdWithPath<T> { + fn new(file_id: HirFileId, ast_id: FileAstId<T>, path: path::ModPath) -> AstIdWithPath<T> { + AstIdWithPath { ast_id: AstId::new(file_id, ast_id), path } + } +} + +fn macro_call_as_call_id( + db: &dyn db::DefDatabase, + call: &AstIdWithPath<ast::MacroCall>, + expand_to: ExpandTo, + krate: CrateId, + resolver: impl Fn(path::ModPath) -> Option<MacroDefId>, + error_sink: &mut dyn FnMut(ExpandError), +) -> Result<Result<MacroCallId, ErrorEmitted>, UnresolvedMacro> { + let def = + resolver(call.path.clone()).ok_or_else(|| UnresolvedMacro { path: call.path.clone() })?; + + let res = if let MacroDefKind::BuiltInEager(..) = def.kind { + let macro_call = InFile::new(call.ast_id.file_id, call.ast_id.to_node(db.upcast())); + + expand_eager_macro(db.upcast(), krate, macro_call, def, &resolver, error_sink)? + } else { + Ok(def.as_lazy_macro( + db.upcast(), + krate, + MacroCallKind::FnLike { ast_id: call.ast_id, expand_to }, + )) + }; + Ok(res) +} + +pub fn macro_id_to_def_id(db: &dyn db::DefDatabase, id: MacroId) -> MacroDefId { + match id { + MacroId::Macro2Id(it) => { + let loc = it.lookup(db); + + let item_tree = loc.id.item_tree(db); + let makro = &item_tree[loc.id.value]; + let in_file = |m: FileAstId<ast::MacroDef>| InFile::new(loc.id.file_id(), m.upcast()); + MacroDefId { + krate: loc.container.krate, + kind: match loc.expander { + MacroExpander::Declarative => MacroDefKind::Declarative(in_file(makro.ast_id)), + MacroExpander::BuiltIn(it) => MacroDefKind::BuiltIn(it, in_file(makro.ast_id)), + MacroExpander::BuiltInAttr(it) => { + MacroDefKind::BuiltInAttr(it, in_file(makro.ast_id)) + } + MacroExpander::BuiltInDerive(it) => { + MacroDefKind::BuiltInDerive(it, in_file(makro.ast_id)) + } + MacroExpander::BuiltInEager(it) => { + MacroDefKind::BuiltInEager(it, in_file(makro.ast_id)) + } + }, + local_inner: false, + } + } + MacroId::MacroRulesId(it) => { + let loc = it.lookup(db); + + let item_tree = loc.id.item_tree(db); + let makro = &item_tree[loc.id.value]; + let in_file = |m: FileAstId<ast::MacroRules>| InFile::new(loc.id.file_id(), m.upcast()); + MacroDefId { + krate: loc.container.krate, + kind: match loc.expander { + MacroExpander::Declarative => MacroDefKind::Declarative(in_file(makro.ast_id)), + MacroExpander::BuiltIn(it) => MacroDefKind::BuiltIn(it, in_file(makro.ast_id)), + MacroExpander::BuiltInAttr(it) => { + MacroDefKind::BuiltInAttr(it, in_file(makro.ast_id)) + } + MacroExpander::BuiltInDerive(it) => { + MacroDefKind::BuiltInDerive(it, in_file(makro.ast_id)) + } + MacroExpander::BuiltInEager(it) => { + MacroDefKind::BuiltInEager(it, in_file(makro.ast_id)) + } + }, + local_inner: loc.local_inner, + } + } + MacroId::ProcMacroId(it) => { + let loc = it.lookup(db); + + let item_tree = loc.id.item_tree(db); + let makro = &item_tree[loc.id.value]; + MacroDefId { + krate: loc.container.krate, + kind: MacroDefKind::ProcMacro( + loc.expander, + loc.kind, + InFile::new(loc.id.file_id(), makro.ast_id), + ), + local_inner: false, + } + } + } +} + +fn derive_macro_as_call_id( + db: &dyn db::DefDatabase, + item_attr: &AstIdWithPath<ast::Adt>, + derive_attr: AttrId, + derive_pos: u32, + krate: CrateId, + resolver: impl Fn(path::ModPath) -> Option<(MacroId, MacroDefId)>, +) -> Result<(MacroId, MacroDefId, MacroCallId), UnresolvedMacro> { + let (macro_id, def_id) = resolver(item_attr.path.clone()) + .ok_or_else(|| UnresolvedMacro { path: item_attr.path.clone() })?; + let call_id = def_id.as_lazy_macro( + db.upcast(), + krate, + MacroCallKind::Derive { + ast_id: item_attr.ast_id, + derive_index: derive_pos, + derive_attr_index: derive_attr.ast_index, + }, + ); + Ok((macro_id, def_id, call_id)) +} + +fn attr_macro_as_call_id( + db: &dyn db::DefDatabase, + item_attr: &AstIdWithPath<ast::Item>, + macro_attr: &Attr, + krate: CrateId, + def: MacroDefId, + is_derive: bool, +) -> MacroCallId { + let mut arg = match macro_attr.input.as_deref() { + Some(attr::AttrInput::TokenTree(tt, map)) => (tt.clone(), map.clone()), + _ => Default::default(), + }; + + // The parentheses are always disposed here. + arg.0.delimiter = None; + + let res = def.as_lazy_macro( + db.upcast(), + krate, + MacroCallKind::Attr { + ast_id: item_attr.ast_id, + attr_args: Arc::new(arg), + invoc_attr_index: macro_attr.id.ast_index, + is_derive, + }, + ); + res +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests.rs new file mode 100644 index 000000000..81b9c5c4b --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests.rs @@ -0,0 +1,354 @@ +//! This module contains tests for macro expansion. Effectively, it covers `tt`, +//! `mbe`, `proc_macro_api` and `hir_expand` crates. This might seem like a +//! wrong architecture at the first glance, but is intentional. +//! +//! Physically, macro expansion process is intertwined with name resolution. You +//! can not expand *just* the syntax. So, to be able to write integration tests +//! of the "expand this code please" form, we have to do it after name +//! resolution. That is, in this crate. We *could* fake some dependencies and +//! write unit-tests (in fact, we used to do that), but that makes tests brittle +//! and harder to understand. + +mod mbe; +mod builtin_fn_macro; +mod builtin_derive_macro; +mod proc_macros; + +use std::{iter, ops::Range, sync::Arc}; + +use ::mbe::TokenMap; +use base_db::{fixture::WithFixture, ProcMacro, SourceDatabase}; +use expect_test::Expect; +use hir_expand::{ + db::{AstDatabase, TokenExpander}, + AstId, InFile, MacroDefId, MacroDefKind, MacroFile, +}; +use stdx::format_to; +use syntax::{ + ast::{self, edit::IndentLevel}, + AstNode, SyntaxElement, + SyntaxKind::{self, COMMENT, EOF, IDENT, LIFETIME_IDENT}, + SyntaxNode, TextRange, T, +}; +use tt::{Subtree, TokenId}; + +use crate::{ + db::DefDatabase, macro_id_to_def_id, nameres::ModuleSource, resolver::HasResolver, + src::HasSource, test_db::TestDB, AdtId, AsMacroCall, Lookup, ModuleDefId, +}; + +#[track_caller] +fn check(ra_fixture: &str, mut expect: Expect) { + let extra_proc_macros = vec![( + r#" +#[proc_macro_attribute] +pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream { + item +} +"# + .into(), + ProcMacro { + name: "identity_when_valid".into(), + kind: base_db::ProcMacroKind::Attr, + expander: Arc::new(IdentityWhenValidProcMacroExpander), + }, + )]; + let db = TestDB::with_files_extra_proc_macros(ra_fixture, extra_proc_macros); + let krate = db.crate_graph().iter().next().unwrap(); + let def_map = db.crate_def_map(krate); + let local_id = def_map.root(); + let module = def_map.module_id(local_id); + let resolver = module.resolver(&db); + let source = def_map[local_id].definition_source(&db); + let source_file = match source.value { + ModuleSource::SourceFile(it) => it, + ModuleSource::Module(_) | ModuleSource::BlockExpr(_) => panic!(), + }; + + // What we want to do is to replace all macros (fn-like, derive, attr) with + // their expansions. Turns out, we don't actually store enough information + // to do this precisely though! Specifically, if a macro expands to nothing, + // it leaves zero traces in def-map, so we can't get its expansion after the + // fact. + // + // This is the usual + // <https://github.com/rust-lang/rust-analyzer/issues/3407> + // resolve/record tension! + // + // So here we try to do a resolve, which is necessary a heuristic. For macro + // calls, we use `as_call_id_with_errors`. For derives, we look at the impls + // in the module and assume that, if impls's source is a different + // `HirFileId`, than it came from macro expansion. + + let mut text_edits = Vec::new(); + let mut expansions = Vec::new(); + + for macro_ in source_file.syntax().descendants().filter_map(ast::Macro::cast) { + let mut show_token_ids = false; + for comment in macro_.syntax().children_with_tokens().filter(|it| it.kind() == COMMENT) { + show_token_ids |= comment.to_string().contains("+tokenids"); + } + if !show_token_ids { + continue; + } + + let call_offset = macro_.syntax().text_range().start().into(); + let file_ast_id = db.ast_id_map(source.file_id).ast_id(¯o_); + let ast_id = AstId::new(source.file_id, file_ast_id.upcast()); + let kind = MacroDefKind::Declarative(ast_id); + + let macro_def = db.macro_def(MacroDefId { krate, kind, local_inner: false }).unwrap(); + if let TokenExpander::DeclarativeMacro { mac, def_site_token_map } = &*macro_def { + let tt = match ¯o_ { + ast::Macro::MacroRules(mac) => mac.token_tree().unwrap(), + ast::Macro::MacroDef(_) => unimplemented!(""), + }; + + let tt_start = tt.syntax().text_range().start(); + tt.syntax().descendants_with_tokens().filter_map(SyntaxElement::into_token).for_each( + |token| { + let range = token.text_range().checked_sub(tt_start).unwrap(); + if let Some(id) = def_site_token_map.token_by_range(range) { + let offset = (range.end() + tt_start).into(); + text_edits.push((offset..offset, format!("#{}", id.0))); + } + }, + ); + text_edits.push(( + call_offset..call_offset, + format!("// call ids will be shifted by {:?}\n", mac.shift()), + )); + } + } + + for macro_call in source_file.syntax().descendants().filter_map(ast::MacroCall::cast) { + let macro_call = InFile::new(source.file_id, ¯o_call); + let mut error = None; + let macro_call_id = macro_call + .as_call_id_with_errors( + &db, + krate, + |path| { + resolver.resolve_path_as_macro(&db, &path).map(|it| macro_id_to_def_id(&db, it)) + }, + &mut |err| error = Some(err), + ) + .unwrap() + .unwrap(); + let macro_file = MacroFile { macro_call_id }; + let mut expansion_result = db.parse_macro_expansion(macro_file); + expansion_result.err = expansion_result.err.or(error); + expansions.push((macro_call.value.clone(), expansion_result, db.macro_arg(macro_call_id))); + } + + for (call, exp, arg) in expansions.into_iter().rev() { + let mut tree = false; + let mut expect_errors = false; + let mut show_token_ids = false; + for comment in call.syntax().children_with_tokens().filter(|it| it.kind() == COMMENT) { + tree |= comment.to_string().contains("+tree"); + expect_errors |= comment.to_string().contains("+errors"); + show_token_ids |= comment.to_string().contains("+tokenids"); + } + + let mut expn_text = String::new(); + if let Some(err) = exp.err { + format_to!(expn_text, "/* error: {} */", err); + } + if let Some((parse, token_map)) = exp.value { + if expect_errors { + assert!(!parse.errors().is_empty(), "no parse errors in expansion"); + for e in parse.errors() { + format_to!(expn_text, "/* parse error: {} */\n", e); + } + } else { + assert!( + parse.errors().is_empty(), + "parse errors in expansion: \n{:#?}", + parse.errors() + ); + } + let pp = pretty_print_macro_expansion( + parse.syntax_node(), + show_token_ids.then(|| &*token_map), + ); + let indent = IndentLevel::from_node(call.syntax()); + let pp = reindent(indent, pp); + format_to!(expn_text, "{}", pp); + + if tree { + let tree = format!("{:#?}", parse.syntax_node()) + .split_inclusive('\n') + .map(|line| format!("// {}", line)) + .collect::<String>(); + format_to!(expn_text, "\n{}", tree) + } + } + let range = call.syntax().text_range(); + let range: Range<usize> = range.into(); + + if show_token_ids { + if let Some((tree, map, _)) = arg.as_deref() { + let tt_range = call.token_tree().unwrap().syntax().text_range(); + let mut ranges = Vec::new(); + extract_id_ranges(&mut ranges, map, tree); + for (range, id) in ranges { + let idx = (tt_range.start() + range.end()).into(); + text_edits.push((idx..idx, format!("#{}", id.0))); + } + } + text_edits.push((range.start..range.start, "// ".into())); + call.to_string().match_indices('\n').for_each(|(offset, _)| { + let offset = offset + 1 + range.start; + text_edits.push((offset..offset, "// ".into())); + }); + text_edits.push((range.end..range.end, "\n".into())); + text_edits.push((range.end..range.end, expn_text)); + } else { + text_edits.push((range, expn_text)); + } + } + + text_edits.sort_by_key(|(range, _)| range.start); + text_edits.reverse(); + let mut expanded_text = source_file.to_string(); + for (range, text) in text_edits { + expanded_text.replace_range(range, &text); + } + + for decl_id in def_map[local_id].scope.declarations() { + // FIXME: I'm sure there's already better way to do this + let src = match decl_id { + ModuleDefId::AdtId(AdtId::StructId(struct_id)) => { + Some(struct_id.lookup(&db).source(&db).syntax().cloned()) + } + ModuleDefId::FunctionId(function_id) => { + Some(function_id.lookup(&db).source(&db).syntax().cloned()) + } + _ => None, + }; + if let Some(src) = src { + if src.file_id.is_attr_macro(&db) || src.file_id.is_custom_derive(&db) { + let pp = pretty_print_macro_expansion(src.value, None); + format_to!(expanded_text, "\n{}", pp) + } + } + } + + for impl_id in def_map[local_id].scope.impls() { + let src = impl_id.lookup(&db).source(&db); + if src.file_id.is_builtin_derive(&db).is_some() { + let pp = pretty_print_macro_expansion(src.value.syntax().clone(), None); + format_to!(expanded_text, "\n{}", pp) + } + } + + expect.indent(false); + expect.assert_eq(&expanded_text); +} + +fn extract_id_ranges(ranges: &mut Vec<(TextRange, TokenId)>, map: &TokenMap, tree: &Subtree) { + tree.token_trees.iter().for_each(|tree| match tree { + tt::TokenTree::Leaf(leaf) => { + let id = match leaf { + tt::Leaf::Literal(it) => it.id, + tt::Leaf::Punct(it) => it.id, + tt::Leaf::Ident(it) => it.id, + }; + ranges.extend(map.ranges_by_token(id, SyntaxKind::ERROR).map(|range| (range, id))); + } + tt::TokenTree::Subtree(tree) => extract_id_ranges(ranges, map, tree), + }); +} + +fn reindent(indent: IndentLevel, pp: String) -> String { + if !pp.contains('\n') { + return pp; + } + let mut lines = pp.split_inclusive('\n'); + let mut res = lines.next().unwrap().to_string(); + for line in lines { + if line.trim().is_empty() { + res.push_str(line) + } else { + format_to!(res, "{}{}", indent, line) + } + } + res +} + +fn pretty_print_macro_expansion(expn: SyntaxNode, map: Option<&TokenMap>) -> String { + let mut res = String::new(); + let mut prev_kind = EOF; + let mut indent_level = 0; + for token in iter::successors(expn.first_token(), |t| t.next_token()) { + let curr_kind = token.kind(); + let space = match (prev_kind, curr_kind) { + _ if prev_kind.is_trivia() || curr_kind.is_trivia() => "", + (T!['{'], T!['}']) => "", + (T![=], _) | (_, T![=]) => " ", + (_, T!['{']) => " ", + (T![;] | T!['{'] | T!['}'], _) => "\n", + (_, T!['}']) => "\n", + (IDENT | LIFETIME_IDENT, IDENT | LIFETIME_IDENT) => " ", + _ if prev_kind.is_keyword() && curr_kind.is_keyword() => " ", + (IDENT, _) if curr_kind.is_keyword() => " ", + (_, IDENT) if prev_kind.is_keyword() => " ", + (T![>], IDENT) => " ", + (T![>], _) if curr_kind.is_keyword() => " ", + (T![->], _) | (_, T![->]) => " ", + (T![&&], _) | (_, T![&&]) => " ", + (T![,], _) => " ", + (T![:], IDENT | T!['(']) => " ", + (T![:], _) if curr_kind.is_keyword() => " ", + (T![fn], T!['(']) => "", + (T![']'], _) if curr_kind.is_keyword() => " ", + (T![']'], T![#]) => "\n", + (T![Self], T![::]) => "", + _ if prev_kind.is_keyword() => " ", + _ => "", + }; + + match prev_kind { + T!['{'] => indent_level += 1, + T!['}'] => indent_level -= 1, + _ => (), + } + + res.push_str(space); + if space == "\n" { + let level = if curr_kind == T!['}'] { indent_level - 1 } else { indent_level }; + res.push_str(&" ".repeat(level)); + } + prev_kind = curr_kind; + format_to!(res, "{}", token); + if let Some(map) = map { + if let Some(id) = map.token_by_range(token.text_range()) { + format_to!(res, "#{}", id.0); + } + } + } + res +} + +// Identity mapping, but only works when the input is syntactically valid. This +// simulates common proc macros that unnecessarily parse their input and return +// compile errors. +#[derive(Debug)] +struct IdentityWhenValidProcMacroExpander; +impl base_db::ProcMacroExpander for IdentityWhenValidProcMacroExpander { + fn expand( + &self, + subtree: &Subtree, + _: Option<&Subtree>, + _: &base_db::Env, + ) -> Result<Subtree, base_db::ProcMacroExpansionError> { + let (parse, _) = + ::mbe::token_tree_to_syntax_node(subtree, ::mbe::TopEntryPoint::MacroItems); + if parse.errors().is_empty() { + Ok(subtree.clone()) + } else { + panic!("got invalid macro input: {:?}", parse.errors()); + } + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs new file mode 100644 index 000000000..6819e9114 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs @@ -0,0 +1,95 @@ +//! Tests for `builtin_derive_macro.rs` from `hir_expand`. + +use expect_test::expect; + +use crate::macro_expansion_tests::check; + +#[test] +fn test_copy_expand_simple() { + check( + r#" +//- minicore: derive, copy +#[derive(Copy)] +struct Foo; +"#, + expect![[r##" +#[derive(Copy)] +struct Foo; + +impl < > core::marker::Copy for Foo< > {}"##]], + ); +} + +#[test] +fn test_copy_expand_in_core() { + cov_mark::check!(test_copy_expand_in_core); + check( + r#" +//- /lib.rs crate:core +#[rustc_builtin_macro] +macro derive {} +#[rustc_builtin_macro] +macro Copy {} +#[derive(Copy)] +struct Foo; +"#, + expect![[r##" +#[rustc_builtin_macro] +macro derive {} +#[rustc_builtin_macro] +macro Copy {} +#[derive(Copy)] +struct Foo; + +impl < > crate ::marker::Copy for Foo< > {}"##]], + ); +} + +#[test] +fn test_copy_expand_with_type_params() { + check( + r#" +//- minicore: derive, copy +#[derive(Copy)] +struct Foo<A, B>; +"#, + expect![[r##" +#[derive(Copy)] +struct Foo<A, B>; + +impl <T0: core::marker::Copy, T1: core::marker::Copy> core::marker::Copy for Foo<T0, T1> {}"##]], + ); +} + +#[test] +fn test_copy_expand_with_lifetimes() { + // We currently just ignore lifetimes + check( + r#" +//- minicore: derive, copy +#[derive(Copy)] +struct Foo<A, B, 'a, 'b>; +"#, + expect![[r##" +#[derive(Copy)] +struct Foo<A, B, 'a, 'b>; + +impl <T0: core::marker::Copy, T1: core::marker::Copy> core::marker::Copy for Foo<T0, T1> {}"##]], + ); +} + +#[test] +fn test_clone_expand() { + check( + r#" +//- minicore: derive, clone +#[derive(Clone)] +struct Foo<A, B>; +"#, + expect![[r##" +#[derive(Clone)] +struct Foo<A, B>; + +impl <T0: core::clone::Clone, T1: core::clone::Clone> core::clone::Clone for Foo<T0, T1> {}"##]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs new file mode 100644 index 000000000..92dffa7f3 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs @@ -0,0 +1,377 @@ +//! Tests for `builtin_fn_macro.rs` from `hir_expand`. + +use expect_test::expect; + +use crate::macro_expansion_tests::check; + +#[test] +fn test_column_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! column {() => {}} + +fn main() { column!(); } +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! column {() => {}} + +fn main() { 0; } +"##]], + ); +} + +#[test] +fn test_line_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! line {() => {}} + +fn main() { line!() } +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! line {() => {}} + +fn main() { 0 } +"##]], + ); +} + +#[test] +fn test_stringify_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! stringify {() => {}} + +fn main() { + stringify!( + a + b + c + ); +} +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! stringify {() => {}} + +fn main() { + "a b c"; +} +"##]], + ); +} + +#[test] +fn test_env_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! env {() => {}} + +fn main() { env!("TEST_ENV_VAR"); } +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! env {() => {}} + +fn main() { "__RA_UNIMPLEMENTED__"; } +"##]], + ); +} + +#[test] +fn test_option_env_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! option_env {() => {}} + +fn main() { option_env!("TEST_ENV_VAR"); } +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! option_env {() => {}} + +fn main() { std::option::Option::None:: < &str>; } +"##]], + ); +} + +#[test] +fn test_file_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! file {() => {}} + +fn main() { file!(); } +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! file {() => {}} + +fn main() { ""; } +"##]], + ); +} + +#[test] +fn test_assert_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! assert { + ($cond:expr) => ({ /* compiler built-in */ }); + ($cond:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + assert!(true, "{} {:?}", arg1(a, b, c), arg2); +} +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! assert { + ($cond:expr) => ({ /* compiler built-in */ }); + ($cond:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + { + if !true { + $crate::panic!("{} {:?}", arg1(a, b, c), arg2); + } + }; +} +"##]], + ); +} + +#[test] +fn test_compile_error_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! compile_error { + ($msg:expr) => ({ /* compiler built-in */ }); + ($msg:expr,) => ({ /* compiler built-in */ }) +} + +// This expands to nothing (since it's in item position), but emits an error. +compile_error!("error!"); +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! compile_error { + ($msg:expr) => ({ /* compiler built-in */ }); + ($msg:expr,) => ({ /* compiler built-in */ }) +} + +/* error: error! */ +"##]], + ); +} + +#[test] +fn test_format_args_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + format_args!("{} {:?}", arg1(a, b, c), arg2); +} +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + std::fmt::Arguments::new_v1(&[], &[std::fmt::ArgumentV1::new(&(arg1(a, b, c)), std::fmt::Display::fmt), std::fmt::ArgumentV1::new(&(arg2), std::fmt::Display::fmt), ]); +} +"##]], + ); +} + +#[test] +fn test_format_args_expand_with_comma_exprs() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + format_args!("{} {:?}", a::<A,B>(), b); +} +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + std::fmt::Arguments::new_v1(&[], &[std::fmt::ArgumentV1::new(&(a::<A, B>()), std::fmt::Display::fmt), std::fmt::ArgumentV1::new(&(b), std::fmt::Display::fmt), ]); +} +"##]], + ); +} + +#[test] +fn test_format_args_expand_with_broken_member_access() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + let _ = + format_args!/*+errors*/("{} {:?}", a.); +} +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + let _ = + /* parse error: expected field name or number */ +std::fmt::Arguments::new_v1(&[], &[std::fmt::ArgumentV1::new(&(a.), std::fmt::Display::fmt), ]); +} +"##]], + ); +} + +#[test] +fn test_include_bytes_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! include_bytes { + ($file:expr) => {{ /* compiler built-in */ }}; + ($file:expr,) => {{ /* compiler built-in */ }}; +} + +fn main() { include_bytes("foo"); } +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! include_bytes { + ($file:expr) => {{ /* compiler built-in */ }}; + ($file:expr,) => {{ /* compiler built-in */ }}; +} + +fn main() { include_bytes("foo"); } +"##]], + ); +} + +#[test] +fn test_concat_expand() { + check( + r##" +#[rustc_builtin_macro] +macro_rules! concat {} + +fn main() { concat!("foo", "r", 0, r#"bar"#, "\n", false); } +"##, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! concat {} + +fn main() { "foor0bar\nfalse"; } +"##]], + ); +} + +#[test] +fn test_concat_bytes_expand() { + check( + r##" +#[rustc_builtin_macro] +macro_rules! concat_bytes {} + +fn main() { concat_bytes!(b'A', b"BC", [68, b'E', 70]); } +"##, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! concat_bytes {} + +fn main() { [b'A', 66, 67, 68, b'E', 70]; } +"##]], + ); +} + +#[test] +fn test_concat_with_captured_expr() { + check( + r##" +#[rustc_builtin_macro] +macro_rules! concat {} + +macro_rules! surprise { + () => { "s" }; +} + +macro_rules! stuff { + ($string:expr) => { concat!($string) }; +} + +fn main() { concat!(surprise!()); } +"##, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! concat {} + +macro_rules! surprise { + () => { "s" }; +} + +macro_rules! stuff { + ($string:expr) => { concat!($string) }; +} + +fn main() { "s"; } +"##]], + ); +} + +#[test] +fn test_concat_idents_expand() { + check( + r##" +#[rustc_builtin_macro] +macro_rules! concat_idents {} + +fn main() { concat_idents!(foo, bar); } +"##, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! concat_idents {} + +fn main() { foobar; } +"##]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs new file mode 100644 index 000000000..30d39d52f --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs @@ -0,0 +1,1632 @@ +//! Tests specific to declarative macros, aka macros by example. This covers +//! both stable `macro_rules!` macros as well as unstable `macro` macros. + +mod tt_conversion; +mod matching; +mod meta_syntax; +mod regression; + +use expect_test::expect; + +use crate::macro_expansion_tests::check; + +#[test] +fn token_mapping_smoke_test() { + check( + r#" +// +tokenids +macro_rules! f { + ( struct $ident:ident ) => { + struct $ident { + map: ::std::collections::HashSet<()>, + } + }; +} + +// +tokenids +f!(struct MyTraitMap2); +"#, + expect![[r##" +// call ids will be shifted by Shift(30) +// +tokenids +macro_rules! f {#0 + (#1 struct#2 $#3ident#4:#5ident#6 )#1 =#7>#8 {#9 + struct#10 $#11ident#12 {#13 + map#14:#15 :#16:#17std#18:#19:#20collections#21:#22:#23HashSet#24<#25(#26)#26>#27,#28 + }#13 + }#9;#29 +}#0 + +// // +tokenids +// f!(struct#1 MyTraitMap2#2); +struct#10 MyTraitMap2#32 {#13 + map#14:#15 ::std#18::collections#21::HashSet#24<#25(#26)#26>#27,#28 +}#13 +"##]], + ); +} + +#[test] +fn token_mapping_floats() { + // Regression test for https://github.com/rust-lang/rust-analyzer/issues/12216 + // (and related issues) + check( + r#" +// +tokenids +macro_rules! f { + ($($tt:tt)*) => { + $($tt)* + }; +} + +// +tokenids +f! { + fn main() { + 1; + 1.0; + let x = 1; + } +} + + +"#, + expect![[r##" +// call ids will be shifted by Shift(18) +// +tokenids +macro_rules! f {#0 + (#1$#2(#3$#4tt#5:#6tt#7)#3*#8)#1 =#9>#10 {#11 + $#12(#13$#14tt#15)#13*#16 + }#11;#17 +}#0 + +// // +tokenids +// f! { +// fn#1 main#2() { +// 1#5;#6 +// 1.0#7;#8 +// let#9 x#10 =#11 1#12;#13 +// } +// } +fn#19 main#20(#21)#21 {#22 + 1#23;#24 + 1.0#25;#26 + let#27 x#28 =#29 1#30;#31 +}#22 + + +"##]], + ); +} + +#[test] +fn mbe_smoke_test() { + check( + r#" +macro_rules! impl_froms { + ($e:ident: $($v:ident),*) => { + $( + impl From<$v> for $e { + fn from(it: $v) -> $e { $e::$v(it) } + } + )* + } +} +impl_froms!(TokenTree: Leaf, Subtree); +"#, + expect![[r#" +macro_rules! impl_froms { + ($e:ident: $($v:ident),*) => { + $( + impl From<$v> for $e { + fn from(it: $v) -> $e { $e::$v(it) } + } + )* + } +} +impl From<Leaf> for TokenTree { + fn from(it: Leaf) -> TokenTree { + TokenTree::Leaf(it) + } +} +impl From<Subtree> for TokenTree { + fn from(it: Subtree) -> TokenTree { + TokenTree::Subtree(it) + } +} +"#]], + ); +} + +#[test] +fn wrong_nesting_level() { + check( + r#" +macro_rules! m { + ($($i:ident);*) => ($i) +} +m!{a} +"#, + expect![[r#" +macro_rules! m { + ($($i:ident);*) => ($i) +} +/* error: expected simple binding, found nested binding `i` */ +"#]], + ); +} + +#[test] +fn match_by_first_token_literally() { + check( + r#" +macro_rules! m { + ($i:ident) => ( mod $i {} ); + (= $i:ident) => ( fn $i() {} ); + (+ $i:ident) => ( struct $i; ) +} +m! { foo } +m! { = bar } +m! { + Baz } +"#, + expect![[r#" +macro_rules! m { + ($i:ident) => ( mod $i {} ); + (= $i:ident) => ( fn $i() {} ); + (+ $i:ident) => ( struct $i; ) +} +mod foo {} +fn bar() {} +struct Baz; +"#]], + ); +} + +#[test] +fn match_by_last_token_literally() { + check( + r#" +macro_rules! m { + ($i:ident) => ( mod $i {} ); + ($i:ident =) => ( fn $i() {} ); + ($i:ident +) => ( struct $i; ) +} +m! { foo } +m! { bar = } +m! { Baz + } +"#, + expect![[r#" +macro_rules! m { + ($i:ident) => ( mod $i {} ); + ($i:ident =) => ( fn $i() {} ); + ($i:ident +) => ( struct $i; ) +} +mod foo {} +fn bar() {} +struct Baz; +"#]], + ); +} + +#[test] +fn match_by_ident() { + check( + r#" +macro_rules! m { + ($i:ident) => ( mod $i {} ); + (spam $i:ident) => ( fn $i() {} ); + (eggs $i:ident) => ( struct $i; ) +} +m! { foo } +m! { spam bar } +m! { eggs Baz } +"#, + expect![[r#" +macro_rules! m { + ($i:ident) => ( mod $i {} ); + (spam $i:ident) => ( fn $i() {} ); + (eggs $i:ident) => ( struct $i; ) +} +mod foo {} +fn bar() {} +struct Baz; +"#]], + ); +} + +#[test] +fn match_by_separator_token() { + check( + r#" +macro_rules! m { + ($($i:ident),*) => ($(mod $i {} )*); + ($($i:ident)#*) => ($(fn $i() {} )*); + ($i:ident ,# $ j:ident) => ( struct $i; struct $ j; ) +} + +m! { foo, bar } + +m! { foo# bar } + +m! { Foo,# Bar } +"#, + expect![[r##" +macro_rules! m { + ($($i:ident),*) => ($(mod $i {} )*); + ($($i:ident)#*) => ($(fn $i() {} )*); + ($i:ident ,# $ j:ident) => ( struct $i; struct $ j; ) +} + +mod foo {} +mod bar {} + +fn foo() {} +fn bar() {} + +struct Foo; +struct Bar; +"##]], + ); +} + +#[test] +fn test_match_group_pattern_with_multiple_defs() { + check( + r#" +macro_rules! m { + ($($i:ident),*) => ( impl Bar { $(fn $i() {})* } ); +} +m! { foo, bar } +"#, + expect![[r#" +macro_rules! m { + ($($i:ident),*) => ( impl Bar { $(fn $i() {})* } ); +} +impl Bar { + fn foo() {} + fn bar() {} +} +"#]], + ); +} + +#[test] +fn test_match_group_pattern_with_multiple_statement() { + check( + r#" +macro_rules! m { + ($($i:ident),*) => ( fn baz() { $($i ();)* } ); +} +m! { foo, bar } +"#, + expect![[r#" +macro_rules! m { + ($($i:ident),*) => ( fn baz() { $($i ();)* } ); +} +fn baz() { + foo(); + bar(); +} +"#]], + ) +} + +#[test] +fn test_match_group_pattern_with_multiple_statement_without_semi() { + check( + r#" +macro_rules! m { + ($($i:ident),*) => ( fn baz() { $($i() );* } ); +} +m! { foo, bar } +"#, + expect![[r#" +macro_rules! m { + ($($i:ident),*) => ( fn baz() { $($i() );* } ); +} +fn baz() { + foo(); + bar() +} +"#]], + ) +} + +#[test] +fn test_match_group_empty_fixed_token() { + check( + r#" +macro_rules! m { + ($($i:ident)* #abc) => ( fn baz() { $($i ();)* } ); +} +m!{#abc} +"#, + expect![[r##" +macro_rules! m { + ($($i:ident)* #abc) => ( fn baz() { $($i ();)* } ); +} +fn baz() {} +"##]], + ) +} + +#[test] +fn test_match_group_in_subtree() { + check( + r#" +macro_rules! m { + (fn $name:ident { $($i:ident)* } ) => ( fn $name() { $($i ();)* } ); +} +m! { fn baz { a b } } +"#, + expect![[r#" +macro_rules! m { + (fn $name:ident { $($i:ident)* } ) => ( fn $name() { $($i ();)* } ); +} +fn baz() { + a(); + b(); +} +"#]], + ) +} + +#[test] +fn test_expr_order() { + check( + r#" +macro_rules! m { + ($ i:expr) => { fn bar() { $ i * 3; } } +} +// +tree +m! { 1 + 2 } +"#, + expect![[r#" +macro_rules! m { + ($ i:expr) => { fn bar() { $ i * 3; } } +} +fn bar() { + (1+2)*3; +} +// MACRO_ITEMS@0..17 +// FN@0..17 +// FN_KW@0..2 "fn" +// NAME@2..5 +// IDENT@2..5 "bar" +// PARAM_LIST@5..7 +// L_PAREN@5..6 "(" +// R_PAREN@6..7 ")" +// BLOCK_EXPR@7..17 +// STMT_LIST@7..17 +// L_CURLY@7..8 "{" +// EXPR_STMT@8..16 +// BIN_EXPR@8..15 +// PAREN_EXPR@8..13 +// L_PAREN@8..9 "(" +// BIN_EXPR@9..12 +// LITERAL@9..10 +// INT_NUMBER@9..10 "1" +// PLUS@10..11 "+" +// LITERAL@11..12 +// INT_NUMBER@11..12 "2" +// R_PAREN@12..13 ")" +// STAR@13..14 "*" +// LITERAL@14..15 +// INT_NUMBER@14..15 "3" +// SEMICOLON@15..16 ";" +// R_CURLY@16..17 "}" + +"#]], + ) +} + +#[test] +fn test_match_group_with_multichar_sep() { + check( + r#" +macro_rules! m { + (fn $name:ident { $($i:literal)* }) => ( fn $name() -> bool { $($i)&&* } ); +} +m! (fn baz { true false } ); +"#, + expect![[r#" +macro_rules! m { + (fn $name:ident { $($i:literal)* }) => ( fn $name() -> bool { $($i)&&* } ); +} +fn baz() -> bool { + true && false +} +"#]], + ); + + check( + r#" +macro_rules! m { + (fn $name:ident { $($i:literal)&&* }) => ( fn $name() -> bool { $($i)&&* } ); +} +m! (fn baz { true && false } ); +"#, + expect![[r#" +macro_rules! m { + (fn $name:ident { $($i:literal)&&* }) => ( fn $name() -> bool { $($i)&&* } ); +} +fn baz() -> bool { + true && false +} +"#]], + ); +} + +#[test] +fn test_match_group_zero_match() { + check( + r#" +macro_rules! m { ( $($i:ident)* ) => (); } +m!(); +"#, + expect![[r#" +macro_rules! m { ( $($i:ident)* ) => (); } + +"#]], + ); +} + +#[test] +fn test_match_group_in_group() { + check( + r#" +macro_rules! m { + [ $( ( $($i:ident)* ) )* ] => [ ok![$( ( $($i)* ) )*]; ] +} +m! ( (a b) ); +"#, + expect![[r#" +macro_rules! m { + [ $( ( $($i:ident)* ) )* ] => [ ok![$( ( $($i)* ) )*]; ] +} +ok![(a b)]; +"#]], + ) +} + +#[test] +fn test_expand_to_item_list() { + check( + r#" +macro_rules! structs { + ($($i:ident),*) => { $(struct $i { field: u32 } )* } +} + +// +tree +structs!(Foo, Bar); + "#, + expect![[r#" +macro_rules! structs { + ($($i:ident),*) => { $(struct $i { field: u32 } )* } +} + +struct Foo { + field: u32 +} +struct Bar { + field: u32 +} +// MACRO_ITEMS@0..40 +// STRUCT@0..20 +// STRUCT_KW@0..6 "struct" +// NAME@6..9 +// IDENT@6..9 "Foo" +// RECORD_FIELD_LIST@9..20 +// L_CURLY@9..10 "{" +// RECORD_FIELD@10..19 +// NAME@10..15 +// IDENT@10..15 "field" +// COLON@15..16 ":" +// PATH_TYPE@16..19 +// PATH@16..19 +// PATH_SEGMENT@16..19 +// NAME_REF@16..19 +// IDENT@16..19 "u32" +// R_CURLY@19..20 "}" +// STRUCT@20..40 +// STRUCT_KW@20..26 "struct" +// NAME@26..29 +// IDENT@26..29 "Bar" +// RECORD_FIELD_LIST@29..40 +// L_CURLY@29..30 "{" +// RECORD_FIELD@30..39 +// NAME@30..35 +// IDENT@30..35 "field" +// COLON@35..36 ":" +// PATH_TYPE@36..39 +// PATH@36..39 +// PATH_SEGMENT@36..39 +// NAME_REF@36..39 +// IDENT@36..39 "u32" +// R_CURLY@39..40 "}" + + "#]], + ); +} + +#[test] +fn test_two_idents() { + check( + r#" +macro_rules! m { + ($i:ident, $j:ident) => { fn foo() { let a = $i; let b = $j; } } +} +m! { foo, bar } +"#, + expect![[r#" +macro_rules! m { + ($i:ident, $j:ident) => { fn foo() { let a = $i; let b = $j; } } +} +fn foo() { + let a = foo; + let b = bar; +} +"#]], + ); +} + +#[test] +fn test_tt_to_stmts() { + check( + r#" +macro_rules! m { + () => { + let a = 0; + a = 10 + 1; + a + } +} + +fn f() -> i32 { + m!/*+tree*/{} +} +"#, + expect![[r#" +macro_rules! m { + () => { + let a = 0; + a = 10 + 1; + a + } +} + +fn f() -> i32 { + let a = 0; + a = 10+1; + a +// MACRO_STMTS@0..15 +// LET_STMT@0..7 +// LET_KW@0..3 "let" +// IDENT_PAT@3..4 +// NAME@3..4 +// IDENT@3..4 "a" +// EQ@4..5 "=" +// LITERAL@5..6 +// INT_NUMBER@5..6 "0" +// SEMICOLON@6..7 ";" +// EXPR_STMT@7..14 +// BIN_EXPR@7..13 +// PATH_EXPR@7..8 +// PATH@7..8 +// PATH_SEGMENT@7..8 +// NAME_REF@7..8 +// IDENT@7..8 "a" +// EQ@8..9 "=" +// BIN_EXPR@9..13 +// LITERAL@9..11 +// INT_NUMBER@9..11 "10" +// PLUS@11..12 "+" +// LITERAL@12..13 +// INT_NUMBER@12..13 "1" +// SEMICOLON@13..14 ";" +// PATH_EXPR@14..15 +// PATH@14..15 +// PATH_SEGMENT@14..15 +// NAME_REF@14..15 +// IDENT@14..15 "a" + +} +"#]], + ); +} + +#[test] +fn test_match_literal() { + check( + r#" +macro_rules! m { + ('(') => { fn l_paren() {} } +} +m!['(']; +"#, + expect![[r#" +macro_rules! m { + ('(') => { fn l_paren() {} } +} +fn l_paren() {} +"#]], + ); +} + +#[test] +fn test_parse_macro_def_simple() { + cov_mark::check!(parse_macro_def_simple); + check( + r#" +macro m($id:ident) { fn $id() {} } +m!(bar); +"#, + expect![[r#" +macro m($id:ident) { fn $id() {} } +fn bar() {} +"#]], + ); +} + +#[test] +fn test_parse_macro_def_rules() { + cov_mark::check!(parse_macro_def_rules); + + check( + r#" +macro m { + ($id:ident) => { fn $id() {} } +} +m!(bar); +"#, + expect![[r#" +macro m { + ($id:ident) => { fn $id() {} } +} +fn bar() {} +"#]], + ); +} + +#[test] +fn test_macro_2_0_panic_2015() { + check( + r#" +macro panic_2015 { + () => (), + (bar) => (), +} +panic_2015!(bar); +"#, + expect![[r#" +macro panic_2015 { + () => (), + (bar) => (), +} + +"#]], + ); +} + +#[test] +fn test_path() { + check( + r#" +macro_rules! m { + ($p:path) => { fn foo() { let a = $p; } } +} + +m! { foo } + +m! { bar::<u8>::baz::<u8> } +"#, + expect![[r#" +macro_rules! m { + ($p:path) => { fn foo() { let a = $p; } } +} + +fn foo() { + let a = foo; +} + +fn foo() { + let a = bar::<u8>::baz::<u8> ; +} +"#]], + ); +} + +#[test] +fn test_two_paths() { + check( + r#" +macro_rules! m { + ($i:path, $j:path) => { fn foo() { let a = $ i; let b = $j; } } +} +m! { foo, bar } +"#, + expect![[r#" +macro_rules! m { + ($i:path, $j:path) => { fn foo() { let a = $ i; let b = $j; } } +} +fn foo() { + let a = foo; + let b = bar; +} +"#]], + ); +} + +#[test] +fn test_path_with_path() { + check( + r#" +macro_rules! m { + ($p:path) => { fn foo() { let a = $p::bar; } } +} +m! { foo } +"#, + expect![[r#" +macro_rules! m { + ($p:path) => { fn foo() { let a = $p::bar; } } +} +fn foo() { + let a = foo::bar; +} +"#]], + ); +} + +#[test] +fn test_expr() { + check( + r#" +macro_rules! m { + ($e:expr) => { fn bar() { $e; } } +} + +m! { 2 + 2 * baz(3).quux() } +"#, + expect![[r#" +macro_rules! m { + ($e:expr) => { fn bar() { $e; } } +} + +fn bar() { + (2+2*baz(3).quux()); +} +"#]], + ) +} + +#[test] +fn test_last_expr() { + check( + r#" +macro_rules! vec { + ($($item:expr),*) => {{ + let mut v = Vec::new(); + $( v.push($item); )* + v + }}; +} + +fn f() { + vec![1,2,3]; +} +"#, + expect![[r#" +macro_rules! vec { + ($($item:expr),*) => {{ + let mut v = Vec::new(); + $( v.push($item); )* + v + }}; +} + +fn f() { + { + let mut v = Vec::new(); + v.push(1); + v.push(2); + v.push(3); + v + }; +} +"#]], + ); +} + +#[test] +fn test_expr_with_attr() { + check( + r#" +macro_rules! m { ($a:expr) => { ok!(); } } +m!(#[allow(a)]()); +"#, + expect![[r#" +macro_rules! m { ($a:expr) => { ok!(); } } +ok!(); +"#]], + ) +} + +#[test] +fn test_ty() { + check( + r#" +macro_rules! m { + ($t:ty) => ( fn bar() -> $t {} ) +} +m! { Baz<u8> } +"#, + expect![[r#" +macro_rules! m { + ($t:ty) => ( fn bar() -> $t {} ) +} +fn bar() -> Baz<u8> {} +"#]], + ) +} + +#[test] +fn test_ty_with_complex_type() { + check( + r#" +macro_rules! m { + ($t:ty) => ( fn bar() -> $ t {} ) +} + +m! { &'a Baz<u8> } + +m! { extern "Rust" fn() -> Ret } +"#, + expect![[r#" +macro_rules! m { + ($t:ty) => ( fn bar() -> $ t {} ) +} + +fn bar() -> & 'a Baz<u8> {} + +fn bar() -> extern "Rust"fn() -> Ret {} +"#]], + ); +} + +#[test] +fn test_pat_() { + check( + r#" +macro_rules! m { + ($p:pat) => { fn foo() { let $p; } } +} +m! { (a, b) } +"#, + expect![[r#" +macro_rules! m { + ($p:pat) => { fn foo() { let $p; } } +} +fn foo() { + let (a, b); +} +"#]], + ); +} + +#[test] +fn test_stmt() { + check( + r#" +macro_rules! m { + ($s:stmt) => ( fn bar() { $s; } ) +} +m! { 2 } +m! { let a = 0 } +"#, + expect![[r#" +macro_rules! m { + ($s:stmt) => ( fn bar() { $s; } ) +} +fn bar() { + 2; +} +fn bar() { + let a = 0; +} +"#]], + ) +} + +#[test] +fn test_single_item() { + check( + r#" +macro_rules! m { ($i:item) => ( $i ) } +m! { mod c {} } +"#, + expect![[r#" +macro_rules! m { ($i:item) => ( $i ) } +mod c {} +"#]], + ) +} + +#[test] +fn test_all_items() { + check( + r#" +macro_rules! m { ($($i:item)*) => ($($i )*) } +m! { + extern crate a; + mod b; + mod c {} + use d; + const E: i32 = 0; + static F: i32 = 0; + impl G {} + struct H; + enum I { Foo } + trait J {} + fn h() {} + extern {} + type T = u8; +} +"#, + expect![[r#" +macro_rules! m { ($($i:item)*) => ($($i )*) } +extern crate a; +mod b; +mod c {} +use d; +const E: i32 = 0; +static F: i32 = 0; +impl G {} +struct H; +enum I { + Foo +} +trait J {} +fn h() {} +extern {} +type T = u8; +"#]], + ); +} + +#[test] +fn test_block() { + check( + r#" +macro_rules! m { ($b:block) => { fn foo() $b } } +m! { { 1; } } +"#, + expect![[r#" +macro_rules! m { ($b:block) => { fn foo() $b } } +fn foo() { + 1; +} +"#]], + ); +} + +#[test] +fn test_meta() { + check( + r#" +macro_rules! m { + ($m:meta) => ( #[$m] fn bar() {} ) +} +m! { cfg(target_os = "windows") } +m! { hello::world } +"#, + expect![[r##" +macro_rules! m { + ($m:meta) => ( #[$m] fn bar() {} ) +} +#[cfg(target_os = "windows")] fn bar() {} +#[hello::world] fn bar() {} +"##]], + ); +} + +#[test] +fn test_meta_doc_comments() { + cov_mark::check!(test_meta_doc_comments); + check( + r#" +macro_rules! m { + ($(#[$m:meta])+) => ( $(#[$m])+ fn bar() {} ) +} +m! { + /// Single Line Doc 1 + /** + MultiLines Doc + */ +} +"#, + expect![[r##" +macro_rules! m { + ($(#[$m:meta])+) => ( $(#[$m])+ fn bar() {} ) +} +#[doc = " Single Line Doc 1"] +#[doc = "\n MultiLines Doc\n "] fn bar() {} +"##]], + ); +} + +#[test] +fn test_meta_extended_key_value_attributes() { + check( + r#" +macro_rules! m { + (#[$m:meta]) => ( #[$m] fn bar() {} ) +} +m! { #[doc = concat!("The `", "bla", "` lang item.")] } +"#, + expect![[r##" +macro_rules! m { + (#[$m:meta]) => ( #[$m] fn bar() {} ) +} +#[doc = concat!("The `", "bla", "` lang item.")] fn bar() {} +"##]], + ); +} + +#[test] +fn test_meta_doc_comments_non_latin() { + check( + r#" +macro_rules! m { + ($(#[$ m:meta])+) => ( $(#[$m])+ fn bar() {} ) +} +m! { + /// 錦瑟無端五十弦,一弦一柱思華年。 + /** + 莊生曉夢迷蝴蝶,望帝春心託杜鵑。 + */ +} +"#, + expect![[r##" +macro_rules! m { + ($(#[$ m:meta])+) => ( $(#[$m])+ fn bar() {} ) +} +#[doc = " 錦瑟無端五十弦,一弦一柱思華年。"] +#[doc = "\n 莊生曉夢迷蝴蝶,望帝春心託杜鵑。\n "] fn bar() {} +"##]], + ); +} + +#[test] +fn test_meta_doc_comments_escaped_characters() { + check( + r#" +macro_rules! m { + ($(#[$m:meta])+) => ( $(#[$m])+ fn bar() {} ) +} +m! { + /// \ " ' +} +"#, + expect![[r##" +macro_rules! m { + ($(#[$m:meta])+) => ( $(#[$m])+ fn bar() {} ) +} +#[doc = " \\ \" \'"] fn bar() {} +"##]], + ); +} + +#[test] +fn test_tt_block() { + check( + r#" +macro_rules! m { ($tt:tt) => { fn foo() $tt } } +m! { { 1; } } +"#, + expect![[r#" +macro_rules! m { ($tt:tt) => { fn foo() $tt } } +fn foo() { + 1; +} +"#]], + ); +} + +#[test] +fn test_tt_group() { + check( + r#" +macro_rules! m { ($($tt:tt)*) => { $($tt)* } } +m! { fn foo() {} }" +"#, + expect![[r#" +macro_rules! m { ($($tt:tt)*) => { $($tt)* } } +fn foo() {}" +"#]], + ); +} + +#[test] +fn test_tt_composite() { + check( + r#" +macro_rules! m { ($tt:tt) => { ok!(); } } +m! { => } +m! { = > } +"#, + expect![[r#" +macro_rules! m { ($tt:tt) => { ok!(); } } +ok!(); +/* error: leftover tokens */ok!(); +"#]], + ); +} + +#[test] +fn test_tt_composite2() { + check( + r#" +macro_rules! m { ($($tt:tt)*) => { abs!(=> $($tt)*); } } +m! {#} +"#, + expect![[r##" +macro_rules! m { ($($tt:tt)*) => { abs!(=> $($tt)*); } } +abs!( = > #); +"##]], + ); +} + +#[test] +fn test_tt_with_composite_without_space() { + // Test macro input without any spaces + // See https://github.com/rust-lang/rust-analyzer/issues/6692 + check( + r#" +macro_rules! m { ($ op:tt, $j:path) => ( ok!(); ) } +m!(==,Foo::Bool) +"#, + expect![[r#" +macro_rules! m { ($ op:tt, $j:path) => ( ok!(); ) } +ok!(); +"#]], + ); +} + +#[test] +fn test_underscore() { + check( + r#" +macro_rules! m { ($_:tt) => { ok!(); } } +m! { => } +"#, + expect![[r#" +macro_rules! m { ($_:tt) => { ok!(); } } +ok!(); +"#]], + ); +} + +#[test] +fn test_underscore_not_greedily() { + check( + r#" +// `_` overlaps with `$a:ident` but rustc matches it under the `_` token. +macro_rules! m1 { + ($($a:ident)* _) => { ok!(); } +} +m1![a b c d _]; + +// `_ => ou` overlaps with `$a:expr => $b:ident` but rustc matches it under `_ => $c:expr`. +macro_rules! m2 { + ($($a:expr => $b:ident)* _ => $c:expr) => { ok!(); } +} +m2![a => b c => d _ => ou] +"#, + expect![[r#" +// `_` overlaps with `$a:ident` but rustc matches it under the `_` token. +macro_rules! m1 { + ($($a:ident)* _) => { ok!(); } +} +ok!(); + +// `_ => ou` overlaps with `$a:expr => $b:ident` but rustc matches it under `_ => $c:expr`. +macro_rules! m2 { + ($($a:expr => $b:ident)* _ => $c:expr) => { ok!(); } +} +ok!(); +"#]], + ); +} + +#[test] +fn test_underscore_flavors() { + check( + r#" +macro_rules! m1 { ($a:ty) => { ok!(); } } +m1![_]; + +macro_rules! m2 { ($a:lifetime) => { ok!(); } } +m2!['_]; +"#, + expect![[r#" +macro_rules! m1 { ($a:ty) => { ok!(); } } +ok!(); + +macro_rules! m2 { ($a:lifetime) => { ok!(); } } +ok!(); +"#]], + ); +} + +#[test] +fn test_vertical_bar_with_pat() { + check( + r#" +macro_rules! m { (|$pat:pat| ) => { ok!(); } } +m! { |x| } + "#, + expect![[r#" +macro_rules! m { (|$pat:pat| ) => { ok!(); } } +ok!(); + "#]], + ); +} + +#[test] +fn test_dollar_crate_lhs_is_not_meta() { + check( + r#" +macro_rules! m { + ($crate) => { err!(); }; + () => { ok!(); }; +} +m!{} +"#, + expect![[r#" +macro_rules! m { + ($crate) => { err!(); }; + () => { ok!(); }; +} +ok!(); +"#]], + ); +} + +#[test] +fn test_lifetime() { + check( + r#" +macro_rules! m { + ($lt:lifetime) => { struct Ref<$lt>{ s: &$ lt str } } +} +m! {'a} +"#, + expect![[r#" +macro_rules! m { + ($lt:lifetime) => { struct Ref<$lt>{ s: &$ lt str } } +} +struct Ref<'a> { + s: &'a str +} +"#]], + ); +} + +#[test] +fn test_literal() { + check( + r#" +macro_rules! m { + ($type:ty, $lit:literal) => { const VALUE: $type = $ lit; }; +} +m!(u8, 0); +"#, + expect![[r#" +macro_rules! m { + ($type:ty, $lit:literal) => { const VALUE: $type = $ lit; }; +} +const VALUE: u8 = 0; +"#]], + ); + + check( + r#" +macro_rules! m { + ($type:ty, $lit:literal) => { const VALUE: $ type = $ lit; }; +} +m!(i32, -1); +"#, + expect![[r#" +macro_rules! m { + ($type:ty, $lit:literal) => { const VALUE: $ type = $ lit; }; +} +const VALUE: i32 = -1; +"#]], + ); +} + +#[test] +fn test_boolean_is_ident() { + check( + r#" +macro_rules! m { + ($lit0:literal, $lit1:literal) => { const VALUE: (bool, bool) = ($lit0, $lit1); }; +} +m!(true, false); +"#, + expect![[r#" +macro_rules! m { + ($lit0:literal, $lit1:literal) => { const VALUE: (bool, bool) = ($lit0, $lit1); }; +} +const VALUE: (bool, bool) = (true , false ); +"#]], + ); +} + +#[test] +fn test_vis() { + check( + r#" +macro_rules! m { + ($vis:vis $name:ident) => { $vis fn $name() {} } +} +m!(pub foo); +m!(foo); +"#, + expect![[r#" +macro_rules! m { + ($vis:vis $name:ident) => { $vis fn $name() {} } +} +pub fn foo() {} +fn foo() {} +"#]], + ); +} + +#[test] +fn test_inner_macro_rules() { + check( + r#" +macro_rules! m { + ($a:ident, $b:ident, $c:tt) => { + macro_rules! inner { + ($bi:ident) => { fn $bi() -> u8 { $c } } + } + + inner!($a); + fn $b() -> u8 { $c } + } +} +m!(x, y, 1); +"#, + expect![[r#" +macro_rules! m { + ($a:ident, $b:ident, $c:tt) => { + macro_rules! inner { + ($bi:ident) => { fn $bi() -> u8 { $c } } + } + + inner!($a); + fn $b() -> u8 { $c } + } +} +macro_rules !inner { + ($bi: ident) = > { + fn $bi()-> u8 { + 1 + } + } +} +inner!(x); +fn y() -> u8 { + 1 +} +"#]], + ); +} + +#[test] +fn test_expr_after_path_colons() { + check( + r#" +macro_rules! m { + ($k:expr) => { fn f() { K::$k; } } +} +// +tree +errors +m!(C("0")); +"#, + expect![[r#" +macro_rules! m { + ($k:expr) => { fn f() { K::$k; } } +} +/* parse error: expected identifier */ +/* parse error: expected SEMICOLON */ +/* parse error: expected SEMICOLON */ +/* parse error: expected expression */ +fn f() { + K::(C("0")); +} +// MACRO_ITEMS@0..19 +// FN@0..19 +// FN_KW@0..2 "fn" +// NAME@2..3 +// IDENT@2..3 "f" +// PARAM_LIST@3..5 +// L_PAREN@3..4 "(" +// R_PAREN@4..5 ")" +// BLOCK_EXPR@5..19 +// STMT_LIST@5..19 +// L_CURLY@5..6 "{" +// EXPR_STMT@6..10 +// PATH_EXPR@6..10 +// PATH@6..10 +// PATH@6..7 +// PATH_SEGMENT@6..7 +// NAME_REF@6..7 +// IDENT@6..7 "K" +// COLON2@7..9 "::" +// ERROR@9..10 +// L_PAREN@9..10 "(" +// EXPR_STMT@10..16 +// CALL_EXPR@10..16 +// PATH_EXPR@10..11 +// PATH@10..11 +// PATH_SEGMENT@10..11 +// NAME_REF@10..11 +// IDENT@10..11 "C" +// ARG_LIST@11..16 +// L_PAREN@11..12 "(" +// LITERAL@12..15 +// STRING@12..15 "\"0\"" +// R_PAREN@15..16 ")" +// ERROR@16..17 +// R_PAREN@16..17 ")" +// SEMICOLON@17..18 ";" +// R_CURLY@18..19 "}" + +"#]], + ); +} + +#[test] +fn test_match_is_not_greedy() { + check( + r#" +macro_rules! foo { + ($($i:ident $(,)*),*) => {}; +} +foo!(a,b); +"#, + expect![[r#" +macro_rules! foo { + ($($i:ident $(,)*),*) => {}; +} + +"#]], + ); +} + +#[test] +fn expr_interpolation() { + check( + r#" +macro_rules! m { ($expr:expr) => { map($expr) } } +fn f() { + let _ = m!(x + foo); +} +"#, + expect![[r#" +macro_rules! m { ($expr:expr) => { map($expr) } } +fn f() { + let _ = map((x+foo)); +} +"#]], + ) +} + +#[test] +fn mbe_are_not_attributes() { + check( + r#" +macro_rules! error { + () => {struct Bar} +} + +#[error] +struct Foo; +"#, + expect![[r##" +macro_rules! error { + () => {struct Bar} +} + +#[error] +struct Foo; +"##]], + ) +} + +#[test] +fn test_dollar_dollar() { + check( + r#" +macro_rules! register_struct { ($Struct:ident) => { + macro_rules! register_methods { ($$($method:ident),*) => { + macro_rules! implement_methods { ($$$$($$val:expr),*) => { + struct $Struct; + impl $Struct { $$(fn $method() -> &'static [u32] { &[$$$$($$$$val),*] })*} + }} + }} +}} + +register_struct!(Foo); +register_methods!(alpha, beta); +implement_methods!(1, 2, 3); +"#, + expect![[r#" +macro_rules! register_struct { ($Struct:ident) => { + macro_rules! register_methods { ($$($method:ident),*) => { + macro_rules! implement_methods { ($$$$($$val:expr),*) => { + struct $Struct; + impl $Struct { $$(fn $method() -> &'static [u32] { &[$$$$($$$$val),*] })*} + }} + }} +}} + +macro_rules !register_methods { + ($($method: ident), *) = > { + macro_rules!implement_methods { + ($$($val: expr), *) = > { + struct Foo; + impl Foo { + $(fn $method()-> & 'static[u32] { + &[$$($$val), *] + } + )* + } + } + } + } +} +macro_rules !implement_methods { + ($($val: expr), *) = > { + struct Foo; + impl Foo { + fn alpha()-> & 'static[u32] { + &[$($val), *] + } + fn beta()-> & 'static[u32] { + &[$($val), *] + } + } + } +} +struct Foo; +impl Foo { + fn alpha() -> & 'static[u32] { + &[1, 2, 3] + } + fn beta() -> & 'static[u32] { + &[1, 2, 3] + } +} +"#]], + ) +} + +#[test] +fn test_metavar_exprs() { + check( + r#" +macro_rules! m { + ( $( $t:tt )* ) => ( $( ${ignore(t)} -${index()} )-* ); +} +const _: i32 = m!(a b c); + "#, + expect![[r#" +macro_rules! m { + ( $( $t:tt )* ) => ( $( ${ignore(t)} -${index()} )-* ); +} +const _: i32 = -0--1--2; + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs new file mode 100644 index 000000000..bc162d0fa --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs @@ -0,0 +1,138 @@ +//! Test that `$var:expr` captures function correctly. + +use expect_test::expect; + +use crate::macro_expansion_tests::check; + +#[test] +fn unary_minus_is_a_literal() { + check( + r#" +macro_rules! m { ($x:literal) => (literal!();); ($x:tt) => (not_a_literal!();); } +m!(92); +m!(-92); +m!(-9.2); +m!(--92); +"#, + expect![[r#" +macro_rules! m { ($x:literal) => (literal!();); ($x:tt) => (not_a_literal!();); } +literal!(); +literal!(); +literal!(); +/* error: leftover tokens */not_a_literal!(); +"#]], + ) +} + +#[test] +fn test_expand_bad_literal() { + check( + r#" +macro_rules! m { ($i:literal) => {}; } +m!(&k"); +"#, + expect![[r#" +macro_rules! m { ($i:literal) => {}; } +/* error: Failed to lower macro args to token tree */"#]], + ); +} + +#[test] +fn test_empty_comments() { + check( + r#" +macro_rules! m{ ($fmt:expr) => (); } +m!(/**/); +"#, + expect![[r#" +macro_rules! m{ ($fmt:expr) => (); } +/* error: expected Expr */ +"#]], + ); +} + +#[test] +fn asi() { + // Thanks, Christopher! + // + // https://internals.rust-lang.org/t/understanding-decisions-behind-semicolons/15181/29 + check( + r#" +macro_rules! asi { ($($stmt:stmt)*) => ($($stmt)*); } + +fn main() { + asi! { + let a = 2 + let b = 5 + drop(b-a) + println!("{}", a+b) + } +} +"#, + expect![[r#" +macro_rules! asi { ($($stmt:stmt)*) => ($($stmt)*); } + +fn main() { + let a = 2let b = 5drop(b-a)println!("{}", a+b) +} +"#]], + ) +} + +#[test] +fn stmt_boundaries() { + // FIXME: this actually works OK under rustc. + check( + r#" +macro_rules! m { + ($($s:stmt)*) => (stringify!($($s |)*);) +} +m!(;;92;let x = 92; loop {};); +"#, + expect![[r#" +macro_rules! m { + ($($s:stmt)*) => (stringify!($($s |)*);) +} +stringify!(; +|; +|92|; +|let x = 92|; +|loop {} +|; +|); +"#]], + ); +} + +#[test] +fn range_patterns() { + // FIXME: rustc thinks there are three patterns here, not one. + check( + r#" +macro_rules! m { + ($($p:pat)*) => (stringify!($($p |)*);) +} +m!(.. .. ..); +"#, + expect![[r#" +macro_rules! m { + ($($p:pat)*) => (stringify!($($p |)*);) +} +stringify!(.. .. ..|); +"#]], + ); +} + +#[test] +fn trailing_vis() { + check( + r#" +macro_rules! m { ($($i:ident)? $vis:vis) => () } +m!(x pub); +"#, + expect![[r#" +macro_rules! m { ($($i:ident)? $vis:vis) => () } + +"#]], + ) +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs new file mode 100644 index 000000000..8aff78408 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs @@ -0,0 +1,154 @@ +//! Test for the syntax of macros themselves. + +use expect_test::expect; + +use crate::macro_expansion_tests::check; + +#[test] +fn well_formed_macro_rules() { + check( + r#" +macro_rules! m { + ($i:ident) => (); + ($(x),*) => (); + ($(x)_*) => (); + ($(x)i*) => (); + ($($i:ident)*) => ($_); + ($($true:ident)*) => ($true); + ($($false:ident)*) => ($false); + (double_dollar) => ($$); + ($) => (m!($);); + ($($t:tt)*) => ($( ${ignore(t)} ${index()} )-*); +} +m!($); +"#, + expect![[r#" +macro_rules! m { + ($i:ident) => (); + ($(x),*) => (); + ($(x)_*) => (); + ($(x)i*) => (); + ($($i:ident)*) => ($_); + ($($true:ident)*) => ($true); + ($($false:ident)*) => ($false); + (double_dollar) => ($$); + ($) => (m!($);); + ($($t:tt)*) => ($( ${ignore(t)} ${index()} )-*); +} +m!($); +"#]], + ) +} + +#[test] +fn malformed_macro_rules() { + check( + r#" +macro_rules! i1 { invalid } +i1!(); + +macro_rules! e1 { $i:ident => () } +e1!(); +macro_rules! e2 { ($i:ident) () } +e2!(); +macro_rules! e3 { ($(i:ident)_) => () } +e3!(); + +macro_rules! f1 { ($i) => ($i) } +f1!(); +macro_rules! f2 { ($i:) => ($i) } +f2!(); +macro_rules! f3 { ($i:_) => () } +f3!(); + +macro_rules! m1 { ($$i) => () } +m1!(); +macro_rules! m2 { () => ( ${invalid()} ) } +m2!(); +"#, + expect![[r#" +macro_rules! i1 { invalid } +/* error: invalid macro definition: expected subtree */ + +macro_rules! e1 { $i:ident => () } +/* error: invalid macro definition: expected subtree */ +macro_rules! e2 { ($i:ident) () } +/* error: invalid macro definition: expected `=` */ +macro_rules! e3 { ($(i:ident)_) => () } +/* error: invalid macro definition: invalid repeat */ + +macro_rules! f1 { ($i) => ($i) } +/* error: invalid macro definition: missing fragment specifier */ +macro_rules! f2 { ($i:) => ($i) } +/* error: invalid macro definition: missing fragment specifier */ +macro_rules! f3 { ($i:_) => () } +/* error: invalid macro definition: missing fragment specifier */ + +macro_rules! m1 { ($$i) => () } +/* error: invalid macro definition: `$$` is not allowed on the pattern side */ +macro_rules! m2 { () => ( ${invalid()} ) } +/* error: invalid macro definition: invalid metavariable expression */ +"#]], + ) +} + +#[test] +fn test_rustc_issue_57597() { + // <https://github.com/rust-lang/rust/blob/master/src/test/ui/issues/issue-57597.rs> + check( + r#" +macro_rules! m0 { ($($($i:ident)?)+) => {}; } +macro_rules! m1 { ($($($i:ident)?)*) => {}; } +macro_rules! m2 { ($($($i:ident)?)?) => {}; } +macro_rules! m3 { ($($($($i:ident)?)?)?) => {}; } +macro_rules! m4 { ($($($($i:ident)*)?)?) => {}; } +macro_rules! m5 { ($($($($i:ident)?)*)?) => {}; } +macro_rules! m6 { ($($($($i:ident)?)?)*) => {}; } +macro_rules! m7 { ($($($($i:ident)*)*)?) => {}; } +macro_rules! m8 { ($($($($i:ident)?)*)*) => {}; } +macro_rules! m9 { ($($($($i:ident)?)*)+) => {}; } +macro_rules! mA { ($($($($i:ident)+)?)*) => {}; } +macro_rules! mB { ($($($($i:ident)+)*)?) => {}; } + +m0!(); +m1!(); +m2!(); +m3!(); +m4!(); +m5!(); +m6!(); +m7!(); +m8!(); +m9!(); +mA!(); +mB!(); + "#, + expect![[r#" +macro_rules! m0 { ($($($i:ident)?)+) => {}; } +macro_rules! m1 { ($($($i:ident)?)*) => {}; } +macro_rules! m2 { ($($($i:ident)?)?) => {}; } +macro_rules! m3 { ($($($($i:ident)?)?)?) => {}; } +macro_rules! m4 { ($($($($i:ident)*)?)?) => {}; } +macro_rules! m5 { ($($($($i:ident)?)*)?) => {}; } +macro_rules! m6 { ($($($($i:ident)?)?)*) => {}; } +macro_rules! m7 { ($($($($i:ident)*)*)?) => {}; } +macro_rules! m8 { ($($($($i:ident)?)*)*) => {}; } +macro_rules! m9 { ($($($($i:ident)?)*)+) => {}; } +macro_rules! mA { ($($($($i:ident)+)?)*) => {}; } +macro_rules! mB { ($($($($i:ident)+)*)?) => {}; } + +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs new file mode 100644 index 000000000..2dff4adf2 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs @@ -0,0 +1,911 @@ +//! Real world regressions and issues, not particularly minimized. +//! +//! While it's OK to just dump large macros here, it's preferable to come up +//! with a minimal example for the program and put a specific test to the parent +//! directory. + +use expect_test::expect; + +use crate::macro_expansion_tests::check; + +#[test] +fn test_vec() { + check( + r#" +macro_rules! vec { + ($($item:expr),*) => {{ + let mut v = Vec::new(); + $( v.push($item); )* + v + }}; +} +fn main() { + vec!(); + vec![1u32,2]; +} +"#, + expect![[r#" +macro_rules! vec { + ($($item:expr),*) => {{ + let mut v = Vec::new(); + $( v.push($item); )* + v + }}; +} +fn main() { + { + let mut v = Vec::new(); + v + }; + { + let mut v = Vec::new(); + v.push(1u32); + v.push(2); + v + }; +} +"#]], + ); +} + +#[test] +fn test_winapi_struct() { + // from https://github.com/retep998/winapi-rs/blob/a7ef2bca086aae76cf6c4ce4c2552988ed9798ad/src/macros.rs#L366 + + check( + r#" +macro_rules! STRUCT { + ($(#[$attrs:meta])* struct $name:ident { + $($field:ident: $ftype:ty,)+ + }) => ( + #[repr(C)] #[derive(Copy)] $(#[$attrs])* + pub struct $name { + $(pub $field: $ftype,)+ + } + impl Clone for $name { + #[inline] + fn clone(&self) -> $name { *self } + } + #[cfg(feature = "impl-default")] + impl Default for $name { + #[inline] + fn default() -> $name { unsafe { $crate::_core::mem::zeroed() } } + } + ); +} + +// from https://github.com/retep998/winapi-rs/blob/a7ef2bca086aae76cf6c4ce4c2552988ed9798ad/src/shared/d3d9caps.rs +STRUCT!{struct D3DVSHADERCAPS2_0 {Caps: u8,}} + +STRUCT!{#[cfg_attr(target_arch = "x86", repr(packed))] struct D3DCONTENTPROTECTIONCAPS {Caps : u8 ,}} +"#, + expect![[r##" +macro_rules! STRUCT { + ($(#[$attrs:meta])* struct $name:ident { + $($field:ident: $ftype:ty,)+ + }) => ( + #[repr(C)] #[derive(Copy)] $(#[$attrs])* + pub struct $name { + $(pub $field: $ftype,)+ + } + impl Clone for $name { + #[inline] + fn clone(&self) -> $name { *self } + } + #[cfg(feature = "impl-default")] + impl Default for $name { + #[inline] + fn default() -> $name { unsafe { $crate::_core::mem::zeroed() } } + } + ); +} + +#[repr(C)] +#[derive(Copy)] pub struct D3DVSHADERCAPS2_0 { + pub Caps: u8, +} +impl Clone for D3DVSHADERCAPS2_0 { + #[inline] fn clone(&self ) -> D3DVSHADERCAPS2_0 { + *self + } +} +#[cfg(feature = "impl-default")] impl Default for D3DVSHADERCAPS2_0 { + #[inline] fn default() -> D3DVSHADERCAPS2_0 { + unsafe { + $crate::_core::mem::zeroed() + } + } +} + +#[repr(C)] +#[derive(Copy)] +#[cfg_attr(target_arch = "x86", repr(packed))] pub struct D3DCONTENTPROTECTIONCAPS { + pub Caps: u8, +} +impl Clone for D3DCONTENTPROTECTIONCAPS { + #[inline] fn clone(&self ) -> D3DCONTENTPROTECTIONCAPS { + *self + } +} +#[cfg(feature = "impl-default")] impl Default for D3DCONTENTPROTECTIONCAPS { + #[inline] fn default() -> D3DCONTENTPROTECTIONCAPS { + unsafe { + $crate::_core::mem::zeroed() + } + } +} +"##]], + ); +} + +#[test] +fn test_int_base() { + check( + r#" +macro_rules! int_base { + ($Trait:ident for $T:ident as $U:ident -> $Radix:ident) => { + #[stable(feature = "rust1", since = "1.0.0")] + impl fmt::$Trait for $T { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + $Radix.fmt_int(*self as $U, f) + } + } + } +} +int_base!{Binary for isize as usize -> Binary} +"#, + expect![[r##" +macro_rules! int_base { + ($Trait:ident for $T:ident as $U:ident -> $Radix:ident) => { + #[stable(feature = "rust1", since = "1.0.0")] + impl fmt::$Trait for $T { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + $Radix.fmt_int(*self as $U, f) + } + } + } +} +#[stable(feature = "rust1", since = "1.0.0")] impl fmt::Binary for isize { + fn fmt(&self , f: &mut fmt::Formatter< '_>) -> fmt::Result { + Binary.fmt_int(*self as usize, f) + } +} +"##]], + ); +} + +#[test] +fn test_generate_pattern_iterators() { + // From <https://github.com/rust-lang/rust/blob/316a391dcb7d66dc25f1f9a4ec9d368ef7615005/src/libcore/str/mod.rs>. + check( + r#" +macro_rules! generate_pattern_iterators { + { double ended; with $(#[$common_stability_attribute:meta])*, + $forward_iterator:ident, + $reverse_iterator:ident, $iterty:ty + } => { ok!(); } +} +generate_pattern_iterators ! ( double ended ; with # [ stable ( feature = "rust1" , since = "1.0.0" ) ] , Split , RSplit , & 'a str ); +"#, + expect![[r##" +macro_rules! generate_pattern_iterators { + { double ended; with $(#[$common_stability_attribute:meta])*, + $forward_iterator:ident, + $reverse_iterator:ident, $iterty:ty + } => { ok!(); } +} +ok!(); +"##]], + ); +} + +#[test] +fn test_impl_fn_for_zst() { + // From <https://github.com/rust-lang/rust/blob/5d20ff4d2718c820632b38c1e49d4de648a9810b/src/libcore/internal_macros.rs>. + check( + r#" +macro_rules! impl_fn_for_zst { + {$( $( #[$attr: meta] )* + struct $Name: ident impl$( <$( $lifetime : lifetime ),+> )? Fn = + |$( $arg: ident: $ArgTy: ty ),*| -> $ReturnTy: ty $body: block; + )+} => {$( + $( #[$attr] )* + struct $Name; + + impl $( <$( $lifetime ),+> )? Fn<($( $ArgTy, )*)> for $Name { + #[inline] + extern "rust-call" fn call(&self, ($( $arg, )*): ($( $ArgTy, )*)) -> $ReturnTy { + $body + } + } + + impl $( <$( $lifetime ),+> )? FnMut<($( $ArgTy, )*)> for $Name { + #[inline] + extern "rust-call" fn call_mut( + &mut self, + ($( $arg, )*): ($( $ArgTy, )*) + ) -> $ReturnTy { + Fn::call(&*self, ($( $arg, )*)) + } + } + + impl $( <$( $lifetime ),+> )? FnOnce<($( $ArgTy, )*)> for $Name { + type Output = $ReturnTy; + + #[inline] + extern "rust-call" fn call_once(self, ($( $arg, )*): ($( $ArgTy, )*)) -> $ReturnTy { + Fn::call(&self, ($( $arg, )*)) + } + } + )+} +} + +impl_fn_for_zst ! { + #[derive(Clone)] + struct CharEscapeDebugContinue impl Fn = |c: char| -> char::EscapeDebug { + c.escape_debug_ext(false) + }; + + #[derive(Clone)] + struct CharEscapeUnicode impl Fn = |c: char| -> char::EscapeUnicode { + c.escape_unicode() + }; + + #[derive(Clone)] + struct CharEscapeDefault impl Fn = |c: char| -> char::EscapeDefault { + c.escape_default() + }; +} + +"#, + expect![[r##" +macro_rules! impl_fn_for_zst { + {$( $( #[$attr: meta] )* + struct $Name: ident impl$( <$( $lifetime : lifetime ),+> )? Fn = + |$( $arg: ident: $ArgTy: ty ),*| -> $ReturnTy: ty $body: block; + )+} => {$( + $( #[$attr] )* + struct $Name; + + impl $( <$( $lifetime ),+> )? Fn<($( $ArgTy, )*)> for $Name { + #[inline] + extern "rust-call" fn call(&self, ($( $arg, )*): ($( $ArgTy, )*)) -> $ReturnTy { + $body + } + } + + impl $( <$( $lifetime ),+> )? FnMut<($( $ArgTy, )*)> for $Name { + #[inline] + extern "rust-call" fn call_mut( + &mut self, + ($( $arg, )*): ($( $ArgTy, )*) + ) -> $ReturnTy { + Fn::call(&*self, ($( $arg, )*)) + } + } + + impl $( <$( $lifetime ),+> )? FnOnce<($( $ArgTy, )*)> for $Name { + type Output = $ReturnTy; + + #[inline] + extern "rust-call" fn call_once(self, ($( $arg, )*): ($( $ArgTy, )*)) -> $ReturnTy { + Fn::call(&self, ($( $arg, )*)) + } + } + )+} +} + +#[derive(Clone)] struct CharEscapeDebugContinue; +impl Fn<(char, )> for CharEscapeDebugContinue { + #[inline] extern "rust-call"fn call(&self , (c, ): (char, )) -> char::EscapeDebug { { + c.escape_debug_ext(false ) + } + } +} +impl FnMut<(char, )> for CharEscapeDebugContinue { + #[inline] extern "rust-call"fn call_mut(&mut self , (c, ): (char, )) -> char::EscapeDebug { + Fn::call(&*self , (c, )) + } +} +impl FnOnce<(char, )> for CharEscapeDebugContinue { + type Output = char::EscapeDebug; + #[inline] extern "rust-call"fn call_once(self , (c, ): (char, )) -> char::EscapeDebug { + Fn::call(&self , (c, )) + } +} +#[derive(Clone)] struct CharEscapeUnicode; +impl Fn<(char, )> for CharEscapeUnicode { + #[inline] extern "rust-call"fn call(&self , (c, ): (char, )) -> char::EscapeUnicode { { + c.escape_unicode() + } + } +} +impl FnMut<(char, )> for CharEscapeUnicode { + #[inline] extern "rust-call"fn call_mut(&mut self , (c, ): (char, )) -> char::EscapeUnicode { + Fn::call(&*self , (c, )) + } +} +impl FnOnce<(char, )> for CharEscapeUnicode { + type Output = char::EscapeUnicode; + #[inline] extern "rust-call"fn call_once(self , (c, ): (char, )) -> char::EscapeUnicode { + Fn::call(&self , (c, )) + } +} +#[derive(Clone)] struct CharEscapeDefault; +impl Fn<(char, )> for CharEscapeDefault { + #[inline] extern "rust-call"fn call(&self , (c, ): (char, )) -> char::EscapeDefault { { + c.escape_default() + } + } +} +impl FnMut<(char, )> for CharEscapeDefault { + #[inline] extern "rust-call"fn call_mut(&mut self , (c, ): (char, )) -> char::EscapeDefault { + Fn::call(&*self , (c, )) + } +} +impl FnOnce<(char, )> for CharEscapeDefault { + type Output = char::EscapeDefault; + #[inline] extern "rust-call"fn call_once(self , (c, ): (char, )) -> char::EscapeDefault { + Fn::call(&self , (c, )) + } +} + +"##]], + ); +} + +#[test] +fn test_impl_nonzero_fmt() { + // From <https://github.com/rust-lang/rust/blob/316a391dcb7d66dc25f1f9a4ec9d368ef7615005/src/libcore/num/mod.rs#L12>. + check( + r#" +macro_rules! impl_nonzero_fmt { + ( #[$stability: meta] ( $( $Trait: ident ),+ ) for $Ty: ident ) => { ok!(); } +} +impl_nonzero_fmt! { + #[stable(feature= "nonzero",since="1.28.0")] + (Debug, Display, Binary, Octal, LowerHex, UpperHex) for NonZeroU8 +} +"#, + expect![[r##" +macro_rules! impl_nonzero_fmt { + ( #[$stability: meta] ( $( $Trait: ident ),+ ) for $Ty: ident ) => { ok!(); } +} +ok!(); +"##]], + ); +} + +#[test] +fn test_cfg_if_items() { + // From <https://github.com/rust-lang/rust/blob/33fe1131cadba69d317156847be9a402b89f11bb/src/libstd/macros.rs#L986>. + check( + r#" +macro_rules! __cfg_if_items { + (($($not:meta,)*) ; ) => {}; + (($($not:meta,)*) ; ( ($($m:meta),*) ($($it:item)*) ), $($rest:tt)*) => { + __cfg_if_items! { ($($not,)* $($m,)*) ; $($rest)* } + } +} +__cfg_if_items! { + (rustdoc,); + ( () ( + #[ cfg(any(target_os = "redox", unix))] + #[ stable(feature = "rust1", since = "1.0.0")] + pub use sys::ext as unix; + + #[cfg(windows)] + #[stable(feature = "rust1", since = "1.0.0")] + pub use sys::ext as windows; + + #[cfg(any(target_os = "linux", target_os = "l4re"))] + pub mod linux; + )), +} +"#, + expect![[r#" +macro_rules! __cfg_if_items { + (($($not:meta,)*) ; ) => {}; + (($($not:meta,)*) ; ( ($($m:meta),*) ($($it:item)*) ), $($rest:tt)*) => { + __cfg_if_items! { ($($not,)* $($m,)*) ; $($rest)* } + } +} +__cfg_if_items! { + (rustdoc, ); +} +"#]], + ); +} + +#[test] +fn test_cfg_if_main() { + // From <https://github.com/rust-lang/rust/blob/3d211248393686e0f73851fc7548f6605220fbe1/src/libpanic_unwind/macros.rs#L9>. + check( + r#" +macro_rules! cfg_if { + ($(if #[cfg($($meta:meta),*)] { $($it:item)* } )else* else { $($it2:item)* }) + => { + __cfg_if_items! { + () ; + $( ( ($($meta),*) ($($it)*) ), )* + ( () ($($it2)*) ), + } + }; + + // Internal macro to Apply a cfg attribute to a list of items + (@__apply $m:meta, $($it:item)*) => { $(#[$m] $it)* }; +} + +cfg_if! { + if #[cfg(target_env = "msvc")] { + // no extra unwinder support needed + } else if #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] { + // no unwinder on the system! + } else { + mod libunwind; + pub use libunwind::*; + } +} + +cfg_if! { + @__apply cfg(all(not(any(not(any(target_os = "solaris", target_os = "illumos")))))), +} +"#, + expect![[r##" +macro_rules! cfg_if { + ($(if #[cfg($($meta:meta),*)] { $($it:item)* } )else* else { $($it2:item)* }) + => { + __cfg_if_items! { + () ; + $( ( ($($meta),*) ($($it)*) ), )* + ( () ($($it2)*) ), + } + }; + + // Internal macro to Apply a cfg attribute to a list of items + (@__apply $m:meta, $($it:item)*) => { $(#[$m] $it)* }; +} + +__cfg_if_items! { + (); + ((target_env = "msvc")()), ((all(target_arch = "wasm32", not(target_os = "emscripten")))()), (()(mod libunwind; + pub use libunwind::*; + )), +} + + +"##]], + ); +} + +#[test] +fn test_proptest_arbitrary() { + // From <https://github.com/AltSysrq/proptest/blob/d1c4b049337d2f75dd6f49a095115f7c532e5129/proptest/src/arbitrary/macros.rs#L16>. + check( + r#" +macro_rules! arbitrary { + ([$($bounds : tt)*] $typ: ty, $strat: ty, $params: ty; + $args: ident => $logic: expr) => { + impl<$($bounds)*> $crate::arbitrary::Arbitrary for $typ { + type Parameters = $params; + type Strategy = $strat; + fn arbitrary_with($args: Self::Parameters) -> Self::Strategy { + $logic + } + } + }; +} + +arbitrary!( + [A:Arbitrary] + Vec<A> , + VecStrategy<A::Strategy>, + RangedParams1<A::Parameters>; + args => { + let product_unpack![range, a] = args; + vec(any_with::<A>(a), range) + } +); +"#, + expect![[r#" +macro_rules! arbitrary { + ([$($bounds : tt)*] $typ: ty, $strat: ty, $params: ty; + $args: ident => $logic: expr) => { + impl<$($bounds)*> $crate::arbitrary::Arbitrary for $typ { + type Parameters = $params; + type Strategy = $strat; + fn arbitrary_with($args: Self::Parameters) -> Self::Strategy { + $logic + } + } + }; +} + +impl <A: Arbitrary> $crate::arbitrary::Arbitrary for Vec<A> { + type Parameters = RangedParams1<A::Parameters>; + type Strategy = VecStrategy<A::Strategy>; + fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { { + let product_unpack![range, a] = args; + vec(any_with::<A>(a), range) + } + } +} +"#]], + ); +} + +#[test] +fn test_old_ridl() { + // This is from winapi 2.8, which do not have a link from github. + check( + r#" +#[macro_export] +macro_rules! RIDL { + (interface $interface:ident ($vtbl:ident) : $pinterface:ident ($pvtbl:ident) + {$( + fn $method:ident(&mut self $(,$p:ident : $t:ty)*) -> $rtr:ty + ),+} + ) => { + impl $interface { + $(pub unsafe fn $method(&mut self) -> $rtr { + ((*self.lpVtbl).$method)(self $(,$p)*) + })+ + } + }; +} + +RIDL!{interface ID3D11Asynchronous(ID3D11AsynchronousVtbl): ID3D11DeviceChild(ID3D11DeviceChildVtbl) { + fn GetDataSize(&mut self) -> UINT +}} +"#, + expect![[r##" +#[macro_export] +macro_rules! RIDL { + (interface $interface:ident ($vtbl:ident) : $pinterface:ident ($pvtbl:ident) + {$( + fn $method:ident(&mut self $(,$p:ident : $t:ty)*) -> $rtr:ty + ),+} + ) => { + impl $interface { + $(pub unsafe fn $method(&mut self) -> $rtr { + ((*self.lpVtbl).$method)(self $(,$p)*) + })+ + } + }; +} + +impl ID3D11Asynchronous { + pub unsafe fn GetDataSize(&mut self ) -> UINT { + ((*self .lpVtbl).GetDataSize)(self ) + } +} +"##]], + ); +} + +#[test] +fn test_quick_error() { + check( + r#" +macro_rules! quick_error { + (SORT [enum $name:ident $( #[$meta:meta] )*] + items [$($( #[$imeta:meta] )* + => $iitem:ident: $imode:tt [$( $ivar:ident: $ityp:ty ),*] + {$( $ifuncs:tt )*} )* ] + buf [ ] + queue [ ] + ) => { + quick_error!(ENUMINITION [enum $name $( #[$meta] )*] + body [] + queue [$( + $( #[$imeta] )* + => + $iitem: $imode [$( $ivar: $ityp ),*] + )*] + ); + }; +} +quick_error ! ( + SORT + [enum Wrapped #[derive(Debug)]] + items [ + => One: UNIT [] {} + => Two: TUPLE [s :String] {display ("two: {}" , s) from ()} ] + buf [ ] + queue [ ] +); + +"#, + expect![[r##" +macro_rules! quick_error { + (SORT [enum $name:ident $( #[$meta:meta] )*] + items [$($( #[$imeta:meta] )* + => $iitem:ident: $imode:tt [$( $ivar:ident: $ityp:ty ),*] + {$( $ifuncs:tt )*} )* ] + buf [ ] + queue [ ] + ) => { + quick_error!(ENUMINITION [enum $name $( #[$meta] )*] + body [] + queue [$( + $( #[$imeta] )* + => + $iitem: $imode [$( $ivar: $ityp ),*] + )*] + ); + }; +} +quick_error!(ENUMINITION[enum Wrapped#[derive(Debug)]]body[]queue[ = > One: UNIT[] = > Two: TUPLE[s: String]]); + +"##]], + ) +} + +#[test] +fn test_empty_repeat_vars_in_empty_repeat_vars() { + check( + r#" +macro_rules! delegate_impl { + ([$self_type:ident, $self_wrap:ty, $self_map:ident] + pub trait $name:ident $(: $sup:ident)* $(+ $more_sup:ident)* { + + $( + @escape [type $assoc_name_ext:ident] + )* + $( + @section type + $( + $(#[$_assoc_attr:meta])* + type $assoc_name:ident $(: $assoc_bound:ty)*; + )+ + )* + $( + @section self + $( + $(#[$_method_attr:meta])* + fn $method_name:ident(self $(: $self_selftype:ty)* $(,$marg:ident : $marg_ty:ty)*) -> $mret:ty; + )+ + )* + $( + @section nodelegate + $($tail:tt)* + )* + }) => { + impl<> $name for $self_wrap where $self_type: $name { + $( + $( + fn $method_name(self $(: $self_selftype)* $(,$marg: $marg_ty)*) -> $mret { + $self_map!(self).$method_name($($marg),*) + } + )* + )* + } + } +} +delegate_impl ! { + [G, &'a mut G, deref] pub trait Data: GraphBase {@section type type NodeWeight;} +} +"#, + expect![[r##" +macro_rules! delegate_impl { + ([$self_type:ident, $self_wrap:ty, $self_map:ident] + pub trait $name:ident $(: $sup:ident)* $(+ $more_sup:ident)* { + + $( + @escape [type $assoc_name_ext:ident] + )* + $( + @section type + $( + $(#[$_assoc_attr:meta])* + type $assoc_name:ident $(: $assoc_bound:ty)*; + )+ + )* + $( + @section self + $( + $(#[$_method_attr:meta])* + fn $method_name:ident(self $(: $self_selftype:ty)* $(,$marg:ident : $marg_ty:ty)*) -> $mret:ty; + )+ + )* + $( + @section nodelegate + $($tail:tt)* + )* + }) => { + impl<> $name for $self_wrap where $self_type: $name { + $( + $( + fn $method_name(self $(: $self_selftype)* $(,$marg: $marg_ty)*) -> $mret { + $self_map!(self).$method_name($($marg),*) + } + )* + )* + } + } +} +impl <> Data for & 'amut G where G: Data {} +"##]], + ); +} + +#[test] +fn test_issue_2520() { + check( + r#" +macro_rules! my_macro { + { + ( $( + $( [] $sname:ident : $stype:ty )? + $( [$expr:expr] $nname:ident : $ntype:ty )? + ),* ) + } => {ok!( + Test { + $( + $( $sname, )? + )* + } + );}; +} + +my_macro! { + ([] p1: u32, [|_| S0K0] s: S0K0, [] k0: i32) +} + "#, + expect![[r#" +macro_rules! my_macro { + { + ( $( + $( [] $sname:ident : $stype:ty )? + $( [$expr:expr] $nname:ident : $ntype:ty )? + ),* ) + } => {ok!( + Test { + $( + $( $sname, )? + )* + } + );}; +} + +ok!(Test { + p1, k0, +} +); + "#]], + ); +} + +#[test] +fn test_repeat_bad_var() { + // FIXME: the second rule of the macro should be removed and an error about + // `$( $c )+` raised + check( + r#" +macro_rules! foo { + ($( $b:ident )+) => { ok!($( $c )+); }; + ($( $b:ident )+) => { ok!($( $b )+); } +} + +foo!(b0 b1); +"#, + expect![[r#" +macro_rules! foo { + ($( $b:ident )+) => { ok!($( $c )+); }; + ($( $b:ident )+) => { ok!($( $b )+); } +} + +ok!(b0 b1); +"#]], + ); +} + +#[test] +fn test_issue_3861() { + // This is should (and does) produce a parse error. It used to infinite loop + // instead. + check( + r#" +macro_rules! rgb_color { + ($p:expr, $t:ty) => { + pub fn new() { + let _ = 0 as $t << $p; + } + }; +} +// +tree +errors +rgb_color!(8 + 8, u32); +"#, + expect![[r#" +macro_rules! rgb_color { + ($p:expr, $t:ty) => { + pub fn new() { + let _ = 0 as $t << $p; + } + }; +} +/* parse error: expected type */ +/* parse error: expected R_PAREN */ +/* parse error: expected R_ANGLE */ +/* parse error: expected COMMA */ +/* parse error: expected R_ANGLE */ +/* parse error: expected SEMICOLON */ +/* parse error: expected SEMICOLON */ +/* parse error: expected expression */ +pub fn new() { + let _ = 0as u32<<(8+8); +} +// MACRO_ITEMS@0..31 +// FN@0..31 +// VISIBILITY@0..3 +// PUB_KW@0..3 "pub" +// FN_KW@3..5 "fn" +// NAME@5..8 +// IDENT@5..8 "new" +// PARAM_LIST@8..10 +// L_PAREN@8..9 "(" +// R_PAREN@9..10 ")" +// BLOCK_EXPR@10..31 +// STMT_LIST@10..31 +// L_CURLY@10..11 "{" +// LET_STMT@11..27 +// LET_KW@11..14 "let" +// WILDCARD_PAT@14..15 +// UNDERSCORE@14..15 "_" +// EQ@15..16 "=" +// CAST_EXPR@16..27 +// LITERAL@16..17 +// INT_NUMBER@16..17 "0" +// AS_KW@17..19 "as" +// PATH_TYPE@19..27 +// PATH@19..27 +// PATH_SEGMENT@19..27 +// NAME_REF@19..22 +// IDENT@19..22 "u32" +// GENERIC_ARG_LIST@22..27 +// L_ANGLE@22..23 "<" +// TYPE_ARG@23..27 +// DYN_TRAIT_TYPE@23..27 +// TYPE_BOUND_LIST@23..27 +// TYPE_BOUND@23..26 +// PATH_TYPE@23..26 +// PATH@23..26 +// PATH_SEGMENT@23..26 +// L_ANGLE@23..24 "<" +// PAREN_TYPE@24..26 +// L_PAREN@24..25 "(" +// ERROR@25..26 +// INT_NUMBER@25..26 "8" +// PLUS@26..27 "+" +// EXPR_STMT@27..28 +// LITERAL@27..28 +// INT_NUMBER@27..28 "8" +// ERROR@28..29 +// R_PAREN@28..29 ")" +// SEMICOLON@29..30 ";" +// R_CURLY@30..31 "}" + +"#]], + ); +} + +#[test] +fn test_no_space_after_semi_colon() { + check( + r#" +macro_rules! with_std { + ($($i:item)*) => ($(#[cfg(feature = "std")]$i)*) +} + +with_std! {mod m;mod f;} +"#, + expect![[r##" +macro_rules! with_std { + ($($i:item)*) => ($(#[cfg(feature = "std")]$i)*) +} + +#[cfg(feature = "std")] mod m; +#[cfg(feature = "std")] mod f; +"##]], + ) +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/tt_conversion.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/tt_conversion.rs new file mode 100644 index 000000000..0710b1ac3 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/tt_conversion.rs @@ -0,0 +1,200 @@ +//! Unlike rustc, rust-analyzer's syntax tree are not "made of" token trees. +//! Rather, token trees are an explicit bridge between the parser and +//! (procedural or declarative) macros. +//! +//! This module tests tt <-> syntax tree conversion specifically. In particular, +//! it, among other things, check that we convert `tt` to the right kind of +//! syntax node depending on the macro call-site. +use expect_test::expect; + +use crate::macro_expansion_tests::check; + +#[test] +fn round_trips_compound_tokens() { + check( + r#" +macro_rules! m { + () => { type qual: ::T = qual::T; } +} +m!(); +"#, + expect![[r#" +macro_rules! m { + () => { type qual: ::T = qual::T; } +} +type qual: ::T = qual::T; +"#]], + ) +} + +#[test] +fn round_trips_literals() { + check( + r#" +macro_rules! m { + () => { + let _ = 'c'; + let _ = 1000; + let _ = 12E+99_f64; + let _ = "rust1"; + let _ = -92; + } +} +fn f() { + m!() +} +"#, + expect![[r#" +macro_rules! m { + () => { + let _ = 'c'; + let _ = 1000; + let _ = 12E+99_f64; + let _ = "rust1"; + let _ = -92; + } +} +fn f() { + let _ = 'c'; + let _ = 1000; + let _ = 12E+99_f64; + let _ = "rust1"; + let _ = -92; +} +"#]], + ); +} + +#[test] +fn roundtrip_lifetime() { + check( + r#" +macro_rules! m { + ($($t:tt)*) => { $($t)*} +} +m!(static bar: &'static str = "hello";); +"#, + expect![[r#" +macro_rules! m { + ($($t:tt)*) => { $($t)*} +} +static bar: & 'static str = "hello"; +"#]], + ); +} + +#[test] +fn broken_parenthesis_sequence() { + check( + r#" +macro_rules! m1 { ($x:ident) => { ($x } } +macro_rules! m2 { ($x:ident) => {} } + +m1!(); +m2!(x +"#, + expect![[r#" +macro_rules! m1 { ($x:ident) => { ($x } } +macro_rules! m2 { ($x:ident) => {} } + +/* error: invalid macro definition: expected subtree */ +/* error: Failed to lower macro args to token tree */ +"#]], + ) +} + +#[test] +fn expansion_does_not_parse_as_expression() { + check( + r#" +macro_rules! stmts { + () => { fn foo() {} } +} + +fn f() { let _ = stmts!/*+errors*/(); } +"#, + expect![[r#" +macro_rules! stmts { + () => { fn foo() {} } +} + +fn f() { let _ = /* parse error: expected expression */ +fn foo() {}; } +"#]], + ) +} + +#[test] +fn broken_pat() { + check( + r#" +macro_rules! m1 { () => (Some(x) left overs) } +macro_rules! m2 { () => ($) } + +fn main() { + let m1!() = (); + let m2!/*+errors*/() = (); +} +"#, + expect![[r#" +macro_rules! m1 { () => (Some(x) left overs) } +macro_rules! m2 { () => ($) } + +fn main() { + let Some(x)left overs = (); + let /* parse error: expected pattern */ +$ = (); +} +"#]], + ) +} + +#[test] +fn float_literal_in_tt() { + check( + r#" +macro_rules! constant { + ($( $ret:expr; )*) => {}; +} +macro_rules! float_const_impl { + () => ( constant!(0.3; 3.3;); ); +} +float_const_impl! {} +"#, + expect![[r#" +macro_rules! constant { + ($( $ret:expr; )*) => {}; +} +macro_rules! float_const_impl { + () => ( constant!(0.3; 3.3;); ); +} +constant!(0.3; +3.3; +); +"#]], + ); +} + +#[test] +fn float_literal_in_output() { + check( + r#" +macro_rules! constant { + ($e:expr ;) => {$e}; +} + +const _: () = constant!(0.0;); +const _: () = constant!(0.;); +const _: () = constant!(0e0;); +"#, + expect![[r#" +macro_rules! constant { + ($e:expr ;) => {$e}; +} + +const _: () = 0.0; +const _: () = 0.; +const _: () = 0e0; +"#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/proc_macros.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/proc_macros.rs new file mode 100644 index 000000000..72c44a0fb --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/proc_macros.rs @@ -0,0 +1,130 @@ +//! Tests for user-defined procedural macros. +//! +//! Note `//- proc_macros: identity` fixture metas in tests -- we don't use real +//! proc-macros here, as that would be slow. Instead, we use several hard-coded +//! in-memory macros. +use expect_test::expect; + +use crate::macro_expansion_tests::check; + +#[test] +fn attribute_macro_attr_censoring() { + cov_mark::check!(attribute_macro_attr_censoring); + check( + r#" +//- proc_macros: identity +#[attr1] #[proc_macros::identity] #[attr2] +struct S; +"#, + expect![[r##" +#[attr1] #[proc_macros::identity] #[attr2] +struct S; + +#[attr1] +#[attr2] struct S;"##]], + ); +} + +#[test] +fn derive_censoring() { + cov_mark::check!(derive_censoring); + check( + r#" +//- proc_macros: derive_identity +//- minicore:derive +#[attr1] +#[derive(Foo)] +#[derive(proc_macros::DeriveIdentity)] +#[derive(Bar)] +#[attr2] +struct S; +"#, + expect![[r##" +#[attr1] +#[derive(Foo)] +#[derive(proc_macros::DeriveIdentity)] +#[derive(Bar)] +#[attr2] +struct S; + +#[attr1] +#[derive(Bar)] +#[attr2] struct S;"##]], + ); +} + +#[test] +fn attribute_macro_syntax_completion_1() { + // this is just the case where the input is actually valid + check( + r#" +//- proc_macros: identity_when_valid +#[proc_macros::identity_when_valid] +fn foo() { bar.baz(); blub } +"#, + expect![[r##" +#[proc_macros::identity_when_valid] +fn foo() { bar.baz(); blub } + +fn foo() { + bar.baz(); + blub +}"##]], + ); +} + +#[test] +fn attribute_macro_syntax_completion_2() { + // common case of dot completion while typing + check( + r#" +//- proc_macros: identity_when_valid +#[proc_macros::identity_when_valid] +fn foo() { bar.; blub } +"#, + expect![[r##" +#[proc_macros::identity_when_valid] +fn foo() { bar.; blub } + +fn foo() { + bar. ; + blub +}"##]], + ); +} + +#[test] +fn float_parsing_panic() { + // Regression test for https://github.com/rust-lang/rust-analyzer/issues/12211 + check( + r#" +//- proc_macros: identity +macro_rules! id { + ($($t:tt)*) => { + $($t)* + }; +} +id /*+errors*/! { + #[proc_macros::identity] + impl Foo for WrapBj { + async fn foo(&self) { + self.0. id().await; + } + } +} +"#, + expect![[r##" +macro_rules! id { + ($($t:tt)*) => { + $($t)* + }; +} +/* parse error: expected SEMICOLON */ +#[proc_macros::identity] impl Foo for WrapBj { + async fn foo(&self ) { + self .0.id().await ; + } +} +"##]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs new file mode 100644 index 000000000..6eb530ecc --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs @@ -0,0 +1,545 @@ +//! This module implements import-resolution/macro expansion algorithm. +//! +//! The result of this module is `DefMap`: a data structure which contains: +//! +//! * a tree of modules for the crate +//! * for each module, a set of items visible in the module (directly declared +//! or imported) +//! +//! Note that `DefMap` contains fully macro expanded code. +//! +//! Computing `DefMap` can be partitioned into several logically +//! independent "phases". The phases are mutually recursive though, there's no +//! strict ordering. +//! +//! ## Collecting RawItems +//! +//! This happens in the `raw` module, which parses a single source file into a +//! set of top-level items. Nested imports are desugared to flat imports in this +//! phase. Macro calls are represented as a triple of (Path, Option<Name>, +//! TokenTree). +//! +//! ## Collecting Modules +//! +//! This happens in the `collector` module. In this phase, we recursively walk +//! tree of modules, collect raw items from submodules, populate module scopes +//! with defined items (so, we assign item ids in this phase) and record the set +//! of unresolved imports and macros. +//! +//! While we walk tree of modules, we also record macro_rules definitions and +//! expand calls to macro_rules defined macros. +//! +//! ## Resolving Imports +//! +//! We maintain a list of currently unresolved imports. On every iteration, we +//! try to resolve some imports from this list. If the import is resolved, we +//! record it, by adding an item to current module scope and, if necessary, by +//! recursively populating glob imports. +//! +//! ## Resolving Macros +//! +//! macro_rules from the same crate use a global mutable namespace. We expand +//! them immediately, when we collect modules. +//! +//! Macros from other crates (including proc-macros) can be used with +//! `foo::bar!` syntax. We handle them similarly to imports. There's a list of +//! unexpanded macros. On every iteration, we try to resolve each macro call +//! path and, upon success, we run macro expansion and "collect module" phase on +//! the result + +pub mod attr_resolution; +pub mod proc_macro; +pub mod diagnostics; +mod collector; +mod mod_resolution; +mod path_resolution; + +#[cfg(test)] +mod tests; + +use std::{cmp::Ord, ops::Deref, sync::Arc}; + +use base_db::{CrateId, Edition, FileId}; +use hir_expand::{name::Name, InFile, MacroCallId, MacroDefId}; +use itertools::Itertools; +use la_arena::Arena; +use profile::Count; +use rustc_hash::FxHashMap; +use stdx::format_to; +use syntax::{ast, SmolStr}; + +use crate::{ + db::DefDatabase, + item_scope::{BuiltinShadowMode, ItemScope}, + item_tree::{ItemTreeId, Mod, TreeId}, + nameres::{diagnostics::DefDiagnostic, path_resolution::ResolveMode}, + path::ModPath, + per_ns::PerNs, + visibility::Visibility, + AstId, BlockId, BlockLoc, FunctionId, LocalModuleId, MacroId, ModuleId, ProcMacroId, +}; + +/// Contains the results of (early) name resolution. +/// +/// A `DefMap` stores the module tree and the definitions that are in scope in every module after +/// item-level macros have been expanded. +/// +/// Every crate has a primary `DefMap` whose root is the crate's main file (`main.rs`/`lib.rs`), +/// computed by the `crate_def_map` query. Additionally, every block expression introduces the +/// opportunity to write arbitrary item and module hierarchies, and thus gets its own `DefMap` that +/// is computed by the `block_def_map` query. +#[derive(Debug, PartialEq, Eq)] +pub struct DefMap { + _c: Count<Self>, + block: Option<BlockInfo>, + root: LocalModuleId, + modules: Arena<ModuleData>, + krate: CrateId, + /// The prelude module for this crate. This either comes from an import + /// marked with the `prelude_import` attribute, or (in the normal case) from + /// a dependency (`std` or `core`). + prelude: Option<ModuleId>, + extern_prelude: FxHashMap<Name, ModuleId>, + + /// Side table for resolving derive helpers. + exported_derives: FxHashMap<MacroDefId, Box<[Name]>>, + fn_proc_macro_mapping: FxHashMap<FunctionId, ProcMacroId>, + /// The error that occurred when failing to load the proc-macro dll. + proc_macro_loading_error: Option<Box<str>>, + /// Tracks which custom derives are in scope for an item, to allow resolution of derive helper + /// attributes. + derive_helpers_in_scope: FxHashMap<AstId<ast::Item>, Vec<(Name, MacroId, MacroCallId)>>, + + /// Custom attributes registered with `#![register_attr]`. + registered_attrs: Vec<SmolStr>, + /// Custom tool modules registered with `#![register_tool]`. + registered_tools: Vec<SmolStr>, + + edition: Edition, + recursion_limit: Option<u32>, + diagnostics: Vec<DefDiagnostic>, +} + +/// For `DefMap`s computed for a block expression, this stores its location in the parent map. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +struct BlockInfo { + /// The `BlockId` this `DefMap` was created from. + block: BlockId, + /// The containing module. + parent: ModuleId, +} + +impl std::ops::Index<LocalModuleId> for DefMap { + type Output = ModuleData; + fn index(&self, id: LocalModuleId) -> &ModuleData { + &self.modules[id] + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub enum ModuleOrigin { + CrateRoot { + definition: FileId, + }, + /// Note that non-inline modules, by definition, live inside non-macro file. + File { + is_mod_rs: bool, + declaration: AstId<ast::Module>, + declaration_tree_id: ItemTreeId<Mod>, + definition: FileId, + }, + Inline { + definition_tree_id: ItemTreeId<Mod>, + definition: AstId<ast::Module>, + }, + /// Pseudo-module introduced by a block scope (contains only inner items). + BlockExpr { + block: AstId<ast::BlockExpr>, + }, +} + +impl ModuleOrigin { + pub fn declaration(&self) -> Option<AstId<ast::Module>> { + match self { + ModuleOrigin::File { declaration: module, .. } + | ModuleOrigin::Inline { definition: module, .. } => Some(*module), + ModuleOrigin::CrateRoot { .. } | ModuleOrigin::BlockExpr { .. } => None, + } + } + + pub fn file_id(&self) -> Option<FileId> { + match self { + ModuleOrigin::File { definition, .. } | ModuleOrigin::CrateRoot { definition } => { + Some(*definition) + } + _ => None, + } + } + + pub fn is_inline(&self) -> bool { + match self { + ModuleOrigin::Inline { .. } | ModuleOrigin::BlockExpr { .. } => true, + ModuleOrigin::CrateRoot { .. } | ModuleOrigin::File { .. } => false, + } + } + + /// Returns a node which defines this module. + /// That is, a file or a `mod foo {}` with items. + fn definition_source(&self, db: &dyn DefDatabase) -> InFile<ModuleSource> { + match self { + ModuleOrigin::File { definition, .. } | ModuleOrigin::CrateRoot { definition } => { + let file_id = *definition; + let sf = db.parse(file_id).tree(); + InFile::new(file_id.into(), ModuleSource::SourceFile(sf)) + } + ModuleOrigin::Inline { definition, .. } => InFile::new( + definition.file_id, + ModuleSource::Module(definition.to_node(db.upcast())), + ), + ModuleOrigin::BlockExpr { block } => { + InFile::new(block.file_id, ModuleSource::BlockExpr(block.to_node(db.upcast()))) + } + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct ModuleData { + /// Where does this module come from? + pub origin: ModuleOrigin, + /// Declared visibility of this module. + pub visibility: Visibility, + + pub parent: Option<LocalModuleId>, + pub children: FxHashMap<Name, LocalModuleId>, + pub scope: ItemScope, +} + +impl DefMap { + pub(crate) fn crate_def_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<DefMap> { + let _p = profile::span("crate_def_map_query").detail(|| { + db.crate_graph()[krate].display_name.as_deref().unwrap_or_default().to_string() + }); + + let crate_graph = db.crate_graph(); + + let edition = crate_graph[krate].edition; + let origin = ModuleOrigin::CrateRoot { definition: crate_graph[krate].root_file_id }; + let def_map = DefMap::empty(krate, edition, ModuleData::new(origin, Visibility::Public)); + let def_map = collector::collect_defs( + db, + def_map, + TreeId::new(crate_graph[krate].root_file_id.into(), None), + ); + + Arc::new(def_map) + } + + pub(crate) fn block_def_map_query( + db: &dyn DefDatabase, + block_id: BlockId, + ) -> Option<Arc<DefMap>> { + let block: BlockLoc = db.lookup_intern_block(block_id); + + let tree_id = TreeId::new(block.ast_id.file_id, Some(block_id)); + let item_tree = tree_id.item_tree(db); + if item_tree.top_level_items().is_empty() { + return None; + } + + let parent_map = block.module.def_map(db); + let krate = block.module.krate; + let local_id = LocalModuleId::from_raw(la_arena::RawIdx::from(0)); + // NB: we use `None` as block here, which would be wrong for implicit + // modules declared by blocks with items. At the moment, we don't use + // this visibility for anything outside IDE, so that's probably OK. + let visibility = Visibility::Module(ModuleId { krate, local_id, block: None }); + let module_data = + ModuleData::new(ModuleOrigin::BlockExpr { block: block.ast_id }, visibility); + + let mut def_map = DefMap::empty(krate, parent_map.edition, module_data); + def_map.block = Some(BlockInfo { block: block_id, parent: block.module }); + + let def_map = collector::collect_defs(db, def_map, tree_id); + Some(Arc::new(def_map)) + } + + fn empty(krate: CrateId, edition: Edition, module_data: ModuleData) -> DefMap { + let mut modules: Arena<ModuleData> = Arena::default(); + let root = modules.alloc(module_data); + + DefMap { + _c: Count::new(), + block: None, + krate, + edition, + recursion_limit: None, + extern_prelude: FxHashMap::default(), + exported_derives: FxHashMap::default(), + fn_proc_macro_mapping: FxHashMap::default(), + proc_macro_loading_error: None, + derive_helpers_in_scope: FxHashMap::default(), + prelude: None, + root, + modules, + registered_attrs: Vec::new(), + registered_tools: Vec::new(), + diagnostics: Vec::new(), + } + } + + pub fn modules_for_file(&self, file_id: FileId) -> impl Iterator<Item = LocalModuleId> + '_ { + self.modules + .iter() + .filter(move |(_id, data)| data.origin.file_id() == Some(file_id)) + .map(|(id, _data)| id) + } + + pub fn modules(&self) -> impl Iterator<Item = (LocalModuleId, &ModuleData)> + '_ { + self.modules.iter() + } + + pub fn derive_helpers_in_scope( + &self, + id: AstId<ast::Adt>, + ) -> Option<&[(Name, MacroId, MacroCallId)]> { + self.derive_helpers_in_scope.get(&id.map(|it| it.upcast())).map(Deref::deref) + } + + pub fn registered_tools(&self) -> &[SmolStr] { + &self.registered_tools + } + + pub fn registered_attrs(&self) -> &[SmolStr] { + &self.registered_attrs + } + + pub fn root(&self) -> LocalModuleId { + self.root + } + + pub fn fn_as_proc_macro(&self, id: FunctionId) -> Option<ProcMacroId> { + self.fn_proc_macro_mapping.get(&id).copied() + } + + pub fn proc_macro_loading_error(&self) -> Option<&str> { + self.proc_macro_loading_error.as_deref() + } + + pub(crate) fn krate(&self) -> CrateId { + self.krate + } + + pub(crate) fn block_id(&self) -> Option<BlockId> { + self.block.as_ref().map(|block| block.block) + } + + pub(crate) fn prelude(&self) -> Option<ModuleId> { + self.prelude + } + + pub(crate) fn extern_prelude(&self) -> impl Iterator<Item = (&Name, &ModuleId)> + '_ { + self.extern_prelude.iter() + } + + pub fn module_id(&self, local_id: LocalModuleId) -> ModuleId { + let block = self.block.as_ref().map(|b| b.block); + ModuleId { krate: self.krate, local_id, block } + } + + pub(crate) fn crate_root(&self, db: &dyn DefDatabase) -> ModuleId { + self.with_ancestor_maps(db, self.root, &mut |def_map, _module| { + if def_map.block.is_none() { Some(def_map.module_id(def_map.root)) } else { None } + }) + .expect("DefMap chain without root") + } + + pub(crate) fn resolve_path( + &self, + db: &dyn DefDatabase, + original_module: LocalModuleId, + path: &ModPath, + shadow: BuiltinShadowMode, + ) -> (PerNs, Option<usize>) { + let res = + self.resolve_path_fp_with_macro(db, ResolveMode::Other, original_module, path, shadow); + (res.resolved_def, res.segment_index) + } + + pub(crate) fn resolve_path_locally( + &self, + db: &dyn DefDatabase, + original_module: LocalModuleId, + path: &ModPath, + shadow: BuiltinShadowMode, + ) -> (PerNs, Option<usize>) { + let res = self.resolve_path_fp_with_macro_single( + db, + ResolveMode::Other, + original_module, + path, + shadow, + ); + (res.resolved_def, res.segment_index) + } + + /// Ascends the `DefMap` hierarchy and calls `f` with every `DefMap` and containing module. + /// + /// If `f` returns `Some(val)`, iteration is stopped and `Some(val)` is returned. If `f` returns + /// `None`, iteration continues. + pub fn with_ancestor_maps<T>( + &self, + db: &dyn DefDatabase, + local_mod: LocalModuleId, + f: &mut dyn FnMut(&DefMap, LocalModuleId) -> Option<T>, + ) -> Option<T> { + if let Some(it) = f(self, local_mod) { + return Some(it); + } + let mut block = self.block; + while let Some(block_info) = block { + let parent = block_info.parent.def_map(db); + if let Some(it) = f(&parent, block_info.parent.local_id) { + return Some(it); + } + block = parent.block; + } + + None + } + + /// If this `DefMap` is for a block expression, returns the module containing the block (which + /// might again be a block, or a module inside a block). + pub fn parent(&self) -> Option<ModuleId> { + Some(self.block?.parent) + } + + /// Returns the module containing `local_mod`, either the parent `mod`, or the module containing + /// the block, if `self` corresponds to a block expression. + pub fn containing_module(&self, local_mod: LocalModuleId) -> Option<ModuleId> { + match &self[local_mod].parent { + Some(parent) => Some(self.module_id(*parent)), + None => self.block.as_ref().map(|block| block.parent), + } + } + + // FIXME: this can use some more human-readable format (ideally, an IR + // even), as this should be a great debugging aid. + pub fn dump(&self, db: &dyn DefDatabase) -> String { + let mut buf = String::new(); + let mut arc; + let mut current_map = self; + while let Some(block) = ¤t_map.block { + go(&mut buf, current_map, "block scope", current_map.root); + buf.push('\n'); + arc = block.parent.def_map(db); + current_map = &*arc; + } + go(&mut buf, current_map, "crate", current_map.root); + return buf; + + fn go(buf: &mut String, map: &DefMap, path: &str, module: LocalModuleId) { + format_to!(buf, "{}\n", path); + + map.modules[module].scope.dump(buf); + + for (name, child) in + map.modules[module].children.iter().sorted_by(|a, b| Ord::cmp(&a.0, &b.0)) + { + let path = format!("{}::{}", path, name); + buf.push('\n'); + go(buf, map, &path, *child); + } + } + } + + pub fn dump_block_scopes(&self, db: &dyn DefDatabase) -> String { + let mut buf = String::new(); + let mut arc; + let mut current_map = self; + while let Some(block) = ¤t_map.block { + format_to!(buf, "{:?} in {:?}\n", block.block, block.parent); + arc = block.parent.def_map(db); + current_map = &*arc; + } + + format_to!(buf, "crate scope\n"); + buf + } + + fn shrink_to_fit(&mut self) { + // Exhaustive match to require handling new fields. + let Self { + _c: _, + exported_derives, + extern_prelude, + diagnostics, + modules, + registered_attrs, + registered_tools, + fn_proc_macro_mapping, + derive_helpers_in_scope, + proc_macro_loading_error: _, + block: _, + edition: _, + recursion_limit: _, + krate: _, + prelude: _, + root: _, + } = self; + + extern_prelude.shrink_to_fit(); + exported_derives.shrink_to_fit(); + diagnostics.shrink_to_fit(); + modules.shrink_to_fit(); + registered_attrs.shrink_to_fit(); + registered_tools.shrink_to_fit(); + fn_proc_macro_mapping.shrink_to_fit(); + derive_helpers_in_scope.shrink_to_fit(); + for (_, module) in modules.iter_mut() { + module.children.shrink_to_fit(); + module.scope.shrink_to_fit(); + } + } + + /// Get a reference to the def map's diagnostics. + pub fn diagnostics(&self) -> &[DefDiagnostic] { + self.diagnostics.as_slice() + } + + pub fn recursion_limit(&self) -> Option<u32> { + self.recursion_limit + } +} + +impl ModuleData { + pub(crate) fn new(origin: ModuleOrigin, visibility: Visibility) -> Self { + ModuleData { + origin, + visibility, + parent: None, + children: FxHashMap::default(), + scope: ItemScope::default(), + } + } + + /// Returns a node which defines this module. That is, a file or a `mod foo {}` with items. + pub fn definition_source(&self, db: &dyn DefDatabase) -> InFile<ModuleSource> { + self.origin.definition_source(db) + } + + /// Returns a node which declares this module, either a `mod foo;` or a `mod foo {}`. + /// `None` for the crate root or block. + pub fn declaration_source(&self, db: &dyn DefDatabase) -> Option<InFile<ast::Module>> { + let decl = self.origin.declaration()?; + let value = decl.to_node(db.upcast()); + Some(InFile { file_id: decl.file_id, value }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ModuleSource { + SourceFile(ast::SourceFile), + Module(ast::Module), + BlockExpr(ast::BlockExpr), +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/attr_resolution.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/attr_resolution.rs new file mode 100644 index 000000000..3650204ee --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/attr_resolution.rs @@ -0,0 +1,98 @@ +//! Post-nameres attribute resolution. + +use hir_expand::MacroCallId; +use syntax::{ast, SmolStr}; + +use crate::{ + attr::Attr, + attr_macro_as_call_id, builtin_attr, + db::DefDatabase, + item_scope::BuiltinShadowMode, + macro_id_to_def_id, + nameres::path_resolution::ResolveMode, + path::{ModPath, PathKind}, + AstIdWithPath, LocalModuleId, UnresolvedMacro, +}; + +use super::DefMap; + +pub enum ResolvedAttr { + /// Attribute resolved to an attribute macro. + Macro(MacroCallId), + /// Attribute resolved to something else that does not require expansion. + Other, +} + +impl DefMap { + pub(crate) fn resolve_attr_macro( + &self, + db: &dyn DefDatabase, + original_module: LocalModuleId, + ast_id: AstIdWithPath<ast::Item>, + attr: &Attr, + ) -> Result<ResolvedAttr, UnresolvedMacro> { + // NB: does not currently work for derive helpers as they aren't recorded in the `DefMap` + + if self.is_builtin_or_registered_attr(&ast_id.path) { + return Ok(ResolvedAttr::Other); + } + + let resolved_res = self.resolve_path_fp_with_macro( + db, + ResolveMode::Other, + original_module, + &ast_id.path, + BuiltinShadowMode::Module, + ); + let def = match resolved_res.resolved_def.take_macros() { + Some(def) => { + if def.is_attribute(db) { + def + } else { + return Ok(ResolvedAttr::Other); + } + } + None => return Err(UnresolvedMacro { path: ast_id.path }), + }; + + Ok(ResolvedAttr::Macro(attr_macro_as_call_id( + db, + &ast_id, + attr, + self.krate, + macro_id_to_def_id(db, def), + false, + ))) + } + + pub(crate) fn is_builtin_or_registered_attr(&self, path: &ModPath) -> bool { + if path.kind != PathKind::Plain { + return false; + } + + let segments = path.segments(); + + if let Some(name) = segments.first() { + let name = name.to_smol_str(); + let pred = |n: &_| *n == name; + + let registered = self.registered_tools.iter().map(SmolStr::as_str); + let is_tool = builtin_attr::TOOL_MODULES.iter().copied().chain(registered).any(pred); + // FIXME: tool modules can be shadowed by actual modules + if is_tool { + return true; + } + + if segments.len() == 1 { + let registered = self.registered_attrs.iter().map(SmolStr::as_str); + let is_inert = builtin_attr::INERT_ATTRIBUTES + .iter() + .map(|it| it.name) + .chain(registered) + .any(pred); + return is_inert; + } + } + false + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs new file mode 100644 index 000000000..8a6bb929c --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs @@ -0,0 +1,2202 @@ +//! The core of the module-level name resolution algorithm. +//! +//! `DefCollector::collect` contains the fixed-point iteration loop which +//! resolves imports and expands macros. + +use std::{iter, mem}; + +use base_db::{CrateId, Edition, FileId}; +use cfg::{CfgExpr, CfgOptions}; +use either::Either; +use hir_expand::{ + ast_id_map::FileAstId, + builtin_attr_macro::find_builtin_attr, + builtin_derive_macro::find_builtin_derive, + builtin_fn_macro::find_builtin_macro, + name::{name, AsName, Name}, + proc_macro::ProcMacroExpander, + ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId, + MacroDefKind, +}; +use itertools::{izip, Itertools}; +use la_arena::Idx; +use limit::Limit; +use rustc_hash::{FxHashMap, FxHashSet}; +use stdx::always; +use syntax::{ast, SmolStr}; + +use crate::{ + attr::{Attr, AttrId, Attrs}, + attr_macro_as_call_id, + db::DefDatabase, + derive_macro_as_call_id, + item_scope::{ImportType, PerNsGlobImports}, + item_tree::{ + self, Fields, FileItemTreeId, ImportKind, ItemTree, ItemTreeId, ItemTreeNode, MacroCall, + MacroDef, MacroRules, Mod, ModItem, ModKind, TreeId, + }, + macro_call_as_call_id, macro_id_to_def_id, + nameres::{ + diagnostics::DefDiagnostic, + mod_resolution::ModDir, + path_resolution::ReachedFixedPoint, + proc_macro::{ProcMacroDef, ProcMacroKind}, + BuiltinShadowMode, DefMap, ModuleData, ModuleOrigin, ResolveMode, + }, + path::{ImportAlias, ModPath, PathKind}, + per_ns::PerNs, + visibility::{RawVisibility, Visibility}, + AdtId, AstId, AstIdWithPath, ConstLoc, EnumLoc, EnumVariantId, ExternBlockLoc, FunctionId, + FunctionLoc, ImplLoc, Intern, ItemContainerId, LocalModuleId, Macro2Id, Macro2Loc, + MacroExpander, MacroId, MacroRulesId, MacroRulesLoc, ModuleDefId, ModuleId, ProcMacroId, + ProcMacroLoc, StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc, UnresolvedMacro, +}; + +static GLOB_RECURSION_LIMIT: Limit = Limit::new(100); +static EXPANSION_DEPTH_LIMIT: Limit = Limit::new(128); +static FIXED_POINT_LIMIT: Limit = Limit::new(8192); + +pub(super) fn collect_defs(db: &dyn DefDatabase, mut def_map: DefMap, tree_id: TreeId) -> DefMap { + let crate_graph = db.crate_graph(); + + let mut deps = FxHashMap::default(); + // populate external prelude and dependency list + let krate = &crate_graph[def_map.krate]; + for dep in &krate.dependencies { + tracing::debug!("crate dep {:?} -> {:?}", dep.name, dep.crate_id); + let dep_def_map = db.crate_def_map(dep.crate_id); + let dep_root = dep_def_map.module_id(dep_def_map.root); + + deps.insert(dep.as_name(), dep_root.into()); + + if dep.is_prelude() && !tree_id.is_block() { + def_map.extern_prelude.insert(dep.as_name(), dep_root); + } + } + + let cfg_options = &krate.cfg_options; + let proc_macros = match &krate.proc_macro { + Ok(proc_macros) => { + proc_macros + .iter() + .enumerate() + .map(|(idx, it)| { + // FIXME: a hacky way to create a Name from string. + let name = tt::Ident { text: it.name.clone(), id: tt::TokenId::unspecified() }; + ( + name.as_name(), + ProcMacroExpander::new(def_map.krate, base_db::ProcMacroId(idx as u32)), + ) + }) + .collect() + } + Err(e) => { + def_map.proc_macro_loading_error = Some(e.clone().into_boxed_str()); + Vec::new() + } + }; + let is_proc_macro = krate.is_proc_macro; + + let mut collector = DefCollector { + db, + def_map, + deps, + glob_imports: FxHashMap::default(), + unresolved_imports: Vec::new(), + indeterminate_imports: Vec::new(), + unresolved_macros: Vec::new(), + mod_dirs: FxHashMap::default(), + cfg_options, + proc_macros, + from_glob_import: Default::default(), + skip_attrs: Default::default(), + is_proc_macro, + }; + if tree_id.is_block() { + collector.seed_with_inner(tree_id); + } else { + collector.seed_with_top_level(); + } + collector.collect(); + let mut def_map = collector.finish(); + def_map.shrink_to_fit(); + def_map +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum PartialResolvedImport { + /// None of any namespaces is resolved + Unresolved, + /// One of namespaces is resolved + Indeterminate(PerNs), + /// All namespaces are resolved, OR it comes from other crate + Resolved(PerNs), +} + +impl PartialResolvedImport { + fn namespaces(self) -> PerNs { + match self { + PartialResolvedImport::Unresolved => PerNs::none(), + PartialResolvedImport::Indeterminate(ns) | PartialResolvedImport::Resolved(ns) => ns, + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum ImportSource { + Import { id: ItemTreeId<item_tree::Import>, use_tree: Idx<ast::UseTree> }, + ExternCrate(ItemTreeId<item_tree::ExternCrate>), +} + +#[derive(Debug, Eq, PartialEq)] +struct Import { + path: ModPath, + alias: Option<ImportAlias>, + visibility: RawVisibility, + kind: ImportKind, + is_prelude: bool, + is_extern_crate: bool, + is_macro_use: bool, + source: ImportSource, +} + +impl Import { + fn from_use( + db: &dyn DefDatabase, + krate: CrateId, + tree: &ItemTree, + id: ItemTreeId<item_tree::Import>, + ) -> Vec<Self> { + let it = &tree[id.value]; + let attrs = &tree.attrs(db, krate, ModItem::from(id.value).into()); + let visibility = &tree[it.visibility]; + let is_prelude = attrs.by_key("prelude_import").exists(); + + let mut res = Vec::new(); + it.use_tree.expand(|idx, path, kind, alias| { + res.push(Self { + path, + alias, + visibility: visibility.clone(), + kind, + is_prelude, + is_extern_crate: false, + is_macro_use: false, + source: ImportSource::Import { id, use_tree: idx }, + }); + }); + res + } + + fn from_extern_crate( + db: &dyn DefDatabase, + krate: CrateId, + tree: &ItemTree, + id: ItemTreeId<item_tree::ExternCrate>, + ) -> Self { + let it = &tree[id.value]; + let attrs = &tree.attrs(db, krate, ModItem::from(id.value).into()); + let visibility = &tree[it.visibility]; + Self { + path: ModPath::from_segments(PathKind::Plain, iter::once(it.name.clone())), + alias: it.alias.clone(), + visibility: visibility.clone(), + kind: ImportKind::Plain, + is_prelude: false, + is_extern_crate: true, + is_macro_use: attrs.by_key("macro_use").exists(), + source: ImportSource::ExternCrate(id), + } + } +} + +#[derive(Debug, Eq, PartialEq)] +struct ImportDirective { + module_id: LocalModuleId, + import: Import, + status: PartialResolvedImport, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +struct MacroDirective { + module_id: LocalModuleId, + depth: usize, + kind: MacroDirectiveKind, + container: ItemContainerId, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum MacroDirectiveKind { + FnLike { ast_id: AstIdWithPath<ast::MacroCall>, expand_to: ExpandTo }, + Derive { ast_id: AstIdWithPath<ast::Adt>, derive_attr: AttrId, derive_pos: usize }, + Attr { ast_id: AstIdWithPath<ast::Item>, attr: Attr, mod_item: ModItem, tree: TreeId }, +} + +/// Walks the tree of module recursively +struct DefCollector<'a> { + db: &'a dyn DefDatabase, + def_map: DefMap, + deps: FxHashMap<Name, ModuleId>, + glob_imports: FxHashMap<LocalModuleId, Vec<(LocalModuleId, Visibility)>>, + unresolved_imports: Vec<ImportDirective>, + indeterminate_imports: Vec<ImportDirective>, + unresolved_macros: Vec<MacroDirective>, + mod_dirs: FxHashMap<LocalModuleId, ModDir>, + cfg_options: &'a CfgOptions, + /// List of procedural macros defined by this crate. This is read from the dynamic library + /// built by the build system, and is the list of proc. macros we can actually expand. It is + /// empty when proc. macro support is disabled (in which case we still do name resolution for + /// them). + proc_macros: Vec<(Name, ProcMacroExpander)>, + is_proc_macro: bool, + from_glob_import: PerNsGlobImports, + /// If we fail to resolve an attribute on a `ModItem`, we fall back to ignoring the attribute. + /// This map is used to skip all attributes up to and including the one that failed to resolve, + /// in order to not expand them twice. + /// + /// This also stores the attributes to skip when we resolve derive helpers and non-macro + /// non-builtin attributes in general. + skip_attrs: FxHashMap<InFile<ModItem>, AttrId>, +} + +impl DefCollector<'_> { + fn seed_with_top_level(&mut self) { + let _p = profile::span("seed_with_top_level"); + + let file_id = self.db.crate_graph()[self.def_map.krate].root_file_id; + let item_tree = self.db.file_item_tree(file_id.into()); + let module_id = self.def_map.root; + + let attrs = item_tree.top_level_attrs(self.db, self.def_map.krate); + if attrs.cfg().map_or(true, |cfg| self.cfg_options.check(&cfg) != Some(false)) { + self.inject_prelude(&attrs); + + // Process other crate-level attributes. + for attr in &*attrs { + let attr_name = match attr.path.as_ident() { + Some(name) => name, + None => continue, + }; + + if *attr_name == hir_expand::name![recursion_limit] { + if let Some(limit) = attr.string_value() { + if let Ok(limit) = limit.parse() { + self.def_map.recursion_limit = Some(limit); + } + } + continue; + } + + if *attr_name == hir_expand::name![crate_type] { + if let Some("proc-macro") = attr.string_value().map(SmolStr::as_str) { + self.is_proc_macro = true; + } + continue; + } + + let attr_is_register_like = *attr_name == hir_expand::name![register_attr] + || *attr_name == hir_expand::name![register_tool]; + if !attr_is_register_like { + continue; + } + + let registered_name = match attr.single_ident_value() { + Some(ident) => ident.as_name(), + _ => continue, + }; + + if *attr_name == hir_expand::name![register_attr] { + self.def_map.registered_attrs.push(registered_name.to_smol_str()); + cov_mark::hit!(register_attr); + } else { + self.def_map.registered_tools.push(registered_name.to_smol_str()); + cov_mark::hit!(register_tool); + } + } + + ModCollector { + def_collector: self, + macro_depth: 0, + module_id, + tree_id: TreeId::new(file_id.into(), None), + item_tree: &item_tree, + mod_dir: ModDir::root(), + } + .collect_in_top_module(item_tree.top_level_items()); + } + } + + fn seed_with_inner(&mut self, tree_id: TreeId) { + let item_tree = tree_id.item_tree(self.db); + let module_id = self.def_map.root; + + let is_cfg_enabled = item_tree + .top_level_attrs(self.db, self.def_map.krate) + .cfg() + .map_or(true, |cfg| self.cfg_options.check(&cfg) != Some(false)); + if is_cfg_enabled { + ModCollector { + def_collector: self, + macro_depth: 0, + module_id, + tree_id, + item_tree: &item_tree, + mod_dir: ModDir::root(), + } + .collect_in_top_module(item_tree.top_level_items()); + } + } + + fn resolution_loop(&mut self) { + let _p = profile::span("DefCollector::resolution_loop"); + + // main name resolution fixed-point loop. + let mut i = 0; + 'resolve_attr: loop { + 'resolve_macros: loop { + self.db.unwind_if_cancelled(); + + { + let _p = profile::span("resolve_imports loop"); + + 'resolve_imports: loop { + if self.resolve_imports() == ReachedFixedPoint::Yes { + break 'resolve_imports; + } + } + } + if self.resolve_macros() == ReachedFixedPoint::Yes { + break 'resolve_macros; + } + + i += 1; + if FIXED_POINT_LIMIT.check(i).is_err() { + tracing::error!("name resolution is stuck"); + break 'resolve_attr; + } + } + + if self.reseed_with_unresolved_attribute() == ReachedFixedPoint::Yes { + break 'resolve_attr; + } + } + } + + fn collect(&mut self) { + let _p = profile::span("DefCollector::collect"); + + self.resolution_loop(); + + // Resolve all indeterminate resolved imports again + // As some of the macros will expand newly import shadowing partial resolved imports + // FIXME: We maybe could skip this, if we handle the indeterminate imports in `resolve_imports` + // correctly + let partial_resolved = self.indeterminate_imports.drain(..).map(|directive| { + ImportDirective { status: PartialResolvedImport::Unresolved, ..directive } + }); + self.unresolved_imports.extend(partial_resolved); + self.resolve_imports(); + + let unresolved_imports = mem::take(&mut self.unresolved_imports); + // show unresolved imports in completion, etc + for directive in &unresolved_imports { + self.record_resolved_import(directive); + } + self.unresolved_imports = unresolved_imports; + + if self.is_proc_macro { + // A crate exporting procedural macros is not allowed to export anything else. + // + // Additionally, while the proc macro entry points must be `pub`, they are not publicly + // exported in type/value namespace. This function reduces the visibility of all items + // in the crate root that aren't proc macros. + let root = self.def_map.root; + let module_id = self.def_map.module_id(root); + let root = &mut self.def_map.modules[root]; + root.scope.censor_non_proc_macros(module_id); + } + } + + /// When the fixed-point loop reaches a stable state, we might still have + /// some unresolved attributes left over. This takes one of them, and feeds + /// the item it's applied to back into name resolution. + /// + /// This effectively ignores the fact that the macro is there and just treats the items as + /// normal code. + /// + /// This improves UX for unresolved attributes, and replicates the + /// behavior before we supported proc. attribute macros. + fn reseed_with_unresolved_attribute(&mut self) -> ReachedFixedPoint { + cov_mark::hit!(unresolved_attribute_fallback); + + let unresolved_attr = + self.unresolved_macros.iter().enumerate().find_map(|(idx, directive)| match &directive + .kind + { + MacroDirectiveKind::Attr { ast_id, mod_item, attr, tree } => { + self.def_map.diagnostics.push(DefDiagnostic::unresolved_macro_call( + directive.module_id, + MacroCallKind::Attr { + ast_id: ast_id.ast_id, + attr_args: Default::default(), + invoc_attr_index: attr.id.ast_index, + is_derive: false, + }, + attr.path().clone(), + )); + + self.skip_attrs.insert(ast_id.ast_id.with_value(*mod_item), attr.id); + + Some((idx, directive, *mod_item, *tree)) + } + _ => None, + }); + + match unresolved_attr { + Some((pos, &MacroDirective { module_id, depth, container, .. }, mod_item, tree_id)) => { + let item_tree = &tree_id.item_tree(self.db); + let mod_dir = self.mod_dirs[&module_id].clone(); + ModCollector { + def_collector: self, + macro_depth: depth, + module_id, + tree_id, + item_tree, + mod_dir, + } + .collect(&[mod_item], container); + + self.unresolved_macros.swap_remove(pos); + // Continue name resolution with the new data. + ReachedFixedPoint::No + } + None => ReachedFixedPoint::Yes, + } + } + + fn inject_prelude(&mut self, crate_attrs: &Attrs) { + // See compiler/rustc_builtin_macros/src/standard_library_imports.rs + + if crate_attrs.by_key("no_core").exists() { + // libcore does not get a prelude. + return; + } + + let krate = if crate_attrs.by_key("no_std").exists() { + name![core] + } else { + let std = name![std]; + if self.def_map.extern_prelude().any(|(name, _)| *name == std) { + std + } else { + // If `std` does not exist for some reason, fall back to core. This mostly helps + // keep r-a's own tests minimal. + name![core] + } + }; + + let edition = match self.def_map.edition { + Edition::Edition2015 => name![rust_2015], + Edition::Edition2018 => name![rust_2018], + Edition::Edition2021 => name![rust_2021], + }; + + let path_kind = if self.def_map.edition == Edition::Edition2015 { + PathKind::Plain + } else { + PathKind::Abs + }; + let path = + ModPath::from_segments(path_kind, [krate.clone(), name![prelude], edition].into_iter()); + // Fall back to the older `std::prelude::v1` for compatibility with Rust <1.52.0 + // FIXME remove this fallback + let fallback_path = + ModPath::from_segments(path_kind, [krate, name![prelude], name![v1]].into_iter()); + + for path in &[path, fallback_path] { + let (per_ns, _) = self.def_map.resolve_path( + self.db, + self.def_map.root, + path, + BuiltinShadowMode::Other, + ); + + match per_ns.types { + Some((ModuleDefId::ModuleId(m), _)) => { + self.def_map.prelude = Some(m); + return; + } + types => { + tracing::debug!( + "could not resolve prelude path `{}` to module (resolved to {:?})", + path, + types + ); + } + } + } + } + + /// Adds a definition of procedural macro `name` to the root module. + /// + /// # Notes on procedural macro resolution + /// + /// Procedural macro functionality is provided by the build system: It has to build the proc + /// macro and pass the resulting dynamic library to rust-analyzer. + /// + /// When procedural macro support is enabled, the list of proc macros exported by a crate is + /// known before we resolve names in the crate. This list is stored in `self.proc_macros` and is + /// derived from the dynamic library. + /// + /// However, we *also* would like to be able to at least *resolve* macros on our own, without + /// help by the build system. So, when the macro isn't found in `self.proc_macros`, we instead + /// use a dummy expander that always errors. This comes with the drawback of macros potentially + /// going out of sync with what the build system sees (since we resolve using VFS state, but + /// Cargo builds only on-disk files). We could and probably should add diagnostics for that. + fn export_proc_macro( + &mut self, + def: ProcMacroDef, + id: ItemTreeId<item_tree::Function>, + fn_id: FunctionId, + module_id: ModuleId, + ) { + let kind = def.kind.to_basedb_kind(); + let (expander, kind) = match self.proc_macros.iter().find(|(n, _)| n == &def.name) { + Some(&(_, expander)) => (expander, kind), + None => (ProcMacroExpander::dummy(self.def_map.krate), kind), + }; + + let proc_macro_id = + ProcMacroLoc { container: module_id, id, expander, kind }.intern(self.db); + self.define_proc_macro(def.name.clone(), proc_macro_id); + if let ProcMacroKind::CustomDerive { helpers } = def.kind { + self.def_map + .exported_derives + .insert(macro_id_to_def_id(self.db, proc_macro_id.into()), helpers); + } + self.def_map.fn_proc_macro_mapping.insert(fn_id, proc_macro_id); + } + + /// Define a macro with `macro_rules`. + /// + /// It will define the macro in legacy textual scope, and if it has `#[macro_export]`, + /// then it is also defined in the root module scope. + /// You can `use` or invoke it by `crate::macro_name` anywhere, before or after the definition. + /// + /// It is surprising that the macro will never be in the current module scope. + /// These code fails with "unresolved import/macro", + /// ```rust,compile_fail + /// mod m { macro_rules! foo { () => {} } } + /// use m::foo as bar; + /// ``` + /// + /// ```rust,compile_fail + /// macro_rules! foo { () => {} } + /// self::foo!(); + /// crate::foo!(); + /// ``` + /// + /// Well, this code compiles, because the plain path `foo` in `use` is searched + /// in the legacy textual scope only. + /// ```rust + /// macro_rules! foo { () => {} } + /// use foo as bar; + /// ``` + fn define_macro_rules( + &mut self, + module_id: LocalModuleId, + name: Name, + macro_: MacroRulesId, + export: bool, + ) { + // Textual scoping + self.define_legacy_macro(module_id, name.clone(), macro_.into()); + + // Module scoping + // In Rust, `#[macro_export]` macros are unconditionally visible at the + // crate root, even if the parent modules is **not** visible. + if export { + let module_id = self.def_map.root; + self.def_map.modules[module_id].scope.declare(macro_.into()); + self.update( + module_id, + &[(Some(name), PerNs::macros(macro_.into(), Visibility::Public))], + Visibility::Public, + ImportType::Named, + ); + } + } + + /// Define a legacy textual scoped macro in module + /// + /// We use a map `legacy_macros` to store all legacy textual scoped macros visible per module. + /// It will clone all macros from parent legacy scope, whose definition is prior to + /// the definition of current module. + /// And also, `macro_use` on a module will import all legacy macros visible inside to + /// current legacy scope, with possible shadowing. + fn define_legacy_macro(&mut self, module_id: LocalModuleId, name: Name, mac: MacroId) { + // Always shadowing + self.def_map.modules[module_id].scope.define_legacy_macro(name, mac); + } + + /// Define a macro 2.0 macro + /// + /// The scoped of macro 2.0 macro is equal to normal function + fn define_macro_def( + &mut self, + module_id: LocalModuleId, + name: Name, + macro_: Macro2Id, + vis: &RawVisibility, + ) { + let vis = + self.def_map.resolve_visibility(self.db, module_id, vis).unwrap_or(Visibility::Public); + self.def_map.modules[module_id].scope.declare(macro_.into()); + self.update( + module_id, + &[(Some(name), PerNs::macros(macro_.into(), Visibility::Public))], + vis, + ImportType::Named, + ); + } + + /// Define a proc macro + /// + /// A proc macro is similar to normal macro scope, but it would not visible in legacy textual scoped. + /// And unconditionally exported. + fn define_proc_macro(&mut self, name: Name, macro_: ProcMacroId) { + let module_id = self.def_map.root; + self.def_map.modules[module_id].scope.declare(macro_.into()); + self.update( + module_id, + &[(Some(name), PerNs::macros(macro_.into(), Visibility::Public))], + Visibility::Public, + ImportType::Named, + ); + } + + /// Import macros from `#[macro_use] extern crate`. + fn import_macros_from_extern_crate( + &mut self, + current_module_id: LocalModuleId, + extern_crate: &item_tree::ExternCrate, + ) { + tracing::debug!( + "importing macros from extern crate: {:?} ({:?})", + extern_crate, + self.def_map.edition, + ); + + if let Some(m) = self.resolve_extern_crate(&extern_crate.name) { + if m == self.def_map.module_id(current_module_id) { + cov_mark::hit!(ignore_macro_use_extern_crate_self); + return; + } + + cov_mark::hit!(macro_rules_from_other_crates_are_visible_with_macro_use); + self.import_all_macros_exported(current_module_id, m.krate); + } + } + + /// Import all exported macros from another crate + /// + /// Exported macros are just all macros in the root module scope. + /// Note that it contains not only all `#[macro_export]` macros, but also all aliases + /// created by `use` in the root module, ignoring the visibility of `use`. + fn import_all_macros_exported(&mut self, current_module_id: LocalModuleId, krate: CrateId) { + let def_map = self.db.crate_def_map(krate); + for (name, def) in def_map[def_map.root].scope.macros() { + // `#[macro_use]` brings macros into legacy scope. Yes, even non-`macro_rules!` macros. + self.define_legacy_macro(current_module_id, name.clone(), def); + } + } + + /// Tries to resolve every currently unresolved import. + fn resolve_imports(&mut self) -> ReachedFixedPoint { + let mut res = ReachedFixedPoint::Yes; + let imports = mem::take(&mut self.unresolved_imports); + + self.unresolved_imports = imports + .into_iter() + .filter_map(|mut directive| { + directive.status = self.resolve_import(directive.module_id, &directive.import); + match directive.status { + PartialResolvedImport::Indeterminate(_) => { + self.record_resolved_import(&directive); + self.indeterminate_imports.push(directive); + res = ReachedFixedPoint::No; + None + } + PartialResolvedImport::Resolved(_) => { + self.record_resolved_import(&directive); + res = ReachedFixedPoint::No; + None + } + PartialResolvedImport::Unresolved => Some(directive), + } + }) + .collect(); + res + } + + fn resolve_import(&self, module_id: LocalModuleId, import: &Import) -> PartialResolvedImport { + let _p = profile::span("resolve_import").detail(|| format!("{}", import.path)); + tracing::debug!("resolving import: {:?} ({:?})", import, self.def_map.edition); + if import.is_extern_crate { + let name = import + .path + .as_ident() + .expect("extern crate should have been desugared to one-element path"); + + let res = self.resolve_extern_crate(name); + + match res { + Some(res) => { + PartialResolvedImport::Resolved(PerNs::types(res.into(), Visibility::Public)) + } + None => PartialResolvedImport::Unresolved, + } + } else { + let res = self.def_map.resolve_path_fp_with_macro( + self.db, + ResolveMode::Import, + module_id, + &import.path, + BuiltinShadowMode::Module, + ); + + let def = res.resolved_def; + if res.reached_fixedpoint == ReachedFixedPoint::No || def.is_none() { + return PartialResolvedImport::Unresolved; + } + + if let Some(krate) = res.krate { + if krate != self.def_map.krate { + return PartialResolvedImport::Resolved( + def.filter_visibility(|v| matches!(v, Visibility::Public)), + ); + } + } + + // Check whether all namespace is resolved + if def.take_types().is_some() + && def.take_values().is_some() + && def.take_macros().is_some() + { + PartialResolvedImport::Resolved(def) + } else { + PartialResolvedImport::Indeterminate(def) + } + } + } + + fn resolve_extern_crate(&self, name: &Name) -> Option<ModuleId> { + if *name == name!(self) { + cov_mark::hit!(extern_crate_self_as); + let root = match self.def_map.block { + Some(_) => { + let def_map = self.def_map.crate_root(self.db).def_map(self.db); + def_map.module_id(def_map.root()) + } + None => self.def_map.module_id(self.def_map.root()), + }; + Some(root) + } else { + self.deps.get(name).copied() + } + } + + fn record_resolved_import(&mut self, directive: &ImportDirective) { + let _p = profile::span("record_resolved_import"); + + let module_id = directive.module_id; + let import = &directive.import; + let mut def = directive.status.namespaces(); + let vis = self + .def_map + .resolve_visibility(self.db, module_id, &directive.import.visibility) + .unwrap_or(Visibility::Public); + + match import.kind { + ImportKind::Plain | ImportKind::TypeOnly => { + let name = match &import.alias { + Some(ImportAlias::Alias(name)) => Some(name), + Some(ImportAlias::Underscore) => None, + None => match import.path.segments().last() { + Some(last_segment) => Some(last_segment), + None => { + cov_mark::hit!(bogus_paths); + return; + } + }, + }; + + if import.kind == ImportKind::TypeOnly { + def.values = None; + def.macros = None; + } + + tracing::debug!("resolved import {:?} ({:?}) to {:?}", name, import, def); + + // extern crates in the crate root are special-cased to insert entries into the extern prelude: rust-lang/rust#54658 + if import.is_extern_crate && module_id == self.def_map.root { + if let (Some(ModuleDefId::ModuleId(def)), Some(name)) = (def.take_types(), name) + { + self.def_map.extern_prelude.insert(name.clone(), def); + } + } + + self.update(module_id, &[(name.cloned(), def)], vis, ImportType::Named); + } + ImportKind::Glob => { + tracing::debug!("glob import: {:?}", import); + match def.take_types() { + Some(ModuleDefId::ModuleId(m)) => { + if import.is_prelude { + // Note: This dodgily overrides the injected prelude. The rustc + // implementation seems to work the same though. + cov_mark::hit!(std_prelude); + self.def_map.prelude = Some(m); + } else if m.krate != self.def_map.krate { + cov_mark::hit!(glob_across_crates); + // glob import from other crate => we can just import everything once + let item_map = m.def_map(self.db); + let scope = &item_map[m.local_id].scope; + + // Module scoped macros is included + let items = scope + .resolutions() + // only keep visible names... + .map(|(n, res)| { + (n, res.filter_visibility(|v| v.is_visible_from_other_crate())) + }) + .filter(|(_, res)| !res.is_none()) + .collect::<Vec<_>>(); + + self.update(module_id, &items, vis, ImportType::Glob); + } else { + // glob import from same crate => we do an initial + // import, and then need to propagate any further + // additions + let def_map; + let scope = if m.block == self.def_map.block_id() { + &self.def_map[m.local_id].scope + } else { + def_map = m.def_map(self.db); + &def_map[m.local_id].scope + }; + + // Module scoped macros is included + let items = scope + .resolutions() + // only keep visible names... + .map(|(n, res)| { + ( + n, + res.filter_visibility(|v| { + v.is_visible_from_def_map( + self.db, + &self.def_map, + module_id, + ) + }), + ) + }) + .filter(|(_, res)| !res.is_none()) + .collect::<Vec<_>>(); + + self.update(module_id, &items, vis, ImportType::Glob); + // record the glob import in case we add further items + let glob = self.glob_imports.entry(m.local_id).or_default(); + if !glob.iter().any(|(mid, _)| *mid == module_id) { + glob.push((module_id, vis)); + } + } + } + Some(ModuleDefId::AdtId(AdtId::EnumId(e))) => { + cov_mark::hit!(glob_enum); + // glob import from enum => just import all the variants + + // XXX: urgh, so this works by accident! Here, we look at + // the enum data, and, in theory, this might require us to + // look back at the crate_def_map, creating a cycle. For + // example, `enum E { crate::some_macro!(); }`. Luckily, the + // only kind of macro that is allowed inside enum is a + // `cfg_macro`, and we don't need to run name resolution for + // it, but this is sheer luck! + let enum_data = self.db.enum_data(e); + let resolutions = enum_data + .variants + .iter() + .map(|(local_id, variant_data)| { + let name = variant_data.name.clone(); + let variant = EnumVariantId { parent: e, local_id }; + let res = PerNs::both(variant.into(), variant.into(), vis); + (Some(name), res) + }) + .collect::<Vec<_>>(); + self.update(module_id, &resolutions, vis, ImportType::Glob); + } + Some(d) => { + tracing::debug!("glob import {:?} from non-module/enum {:?}", import, d); + } + None => { + tracing::debug!("glob import {:?} didn't resolve as type", import); + } + } + } + } + } + + fn update( + &mut self, + module_id: LocalModuleId, + resolutions: &[(Option<Name>, PerNs)], + vis: Visibility, + import_type: ImportType, + ) { + self.db.unwind_if_cancelled(); + self.update_recursive(module_id, resolutions, vis, import_type, 0) + } + + fn update_recursive( + &mut self, + module_id: LocalModuleId, + resolutions: &[(Option<Name>, PerNs)], + // All resolutions are imported with this visibility; the visibilities in + // the `PerNs` values are ignored and overwritten + vis: Visibility, + import_type: ImportType, + depth: usize, + ) { + if GLOB_RECURSION_LIMIT.check(depth).is_err() { + // prevent stack overflows (but this shouldn't be possible) + panic!("infinite recursion in glob imports!"); + } + let mut changed = false; + + for (name, res) in resolutions { + match name { + Some(name) => { + let scope = &mut self.def_map.modules[module_id].scope; + changed |= scope.push_res_with_import( + &mut self.from_glob_import, + (module_id, name.clone()), + res.with_visibility(vis), + import_type, + ); + } + None => { + let tr = match res.take_types() { + Some(ModuleDefId::TraitId(tr)) => tr, + Some(other) => { + tracing::debug!("non-trait `_` import of {:?}", other); + continue; + } + None => continue, + }; + let old_vis = self.def_map.modules[module_id].scope.unnamed_trait_vis(tr); + let should_update = match old_vis { + None => true, + Some(old_vis) => { + let max_vis = old_vis.max(vis, &self.def_map).unwrap_or_else(|| { + panic!("`Tr as _` imports with unrelated visibilities {:?} and {:?} (trait {:?})", old_vis, vis, tr); + }); + + if max_vis == old_vis { + false + } else { + cov_mark::hit!(upgrade_underscore_visibility); + true + } + } + }; + + if should_update { + changed = true; + self.def_map.modules[module_id].scope.push_unnamed_trait(tr, vis); + } + } + } + } + + if !changed { + return; + } + let glob_imports = self + .glob_imports + .get(&module_id) + .into_iter() + .flatten() + .filter(|(glob_importing_module, _)| { + // we know all resolutions have the same visibility (`vis`), so we + // just need to check that once + vis.is_visible_from_def_map(self.db, &self.def_map, *glob_importing_module) + }) + .cloned() + .collect::<Vec<_>>(); + + for (glob_importing_module, glob_import_vis) in glob_imports { + self.update_recursive( + glob_importing_module, + resolutions, + glob_import_vis, + ImportType::Glob, + depth + 1, + ); + } + } + + fn resolve_macros(&mut self) -> ReachedFixedPoint { + let mut macros = mem::take(&mut self.unresolved_macros); + let mut resolved = Vec::new(); + let mut push_resolved = |directive: &MacroDirective, call_id| { + resolved.push((directive.module_id, directive.depth, directive.container, call_id)); + }; + let mut res = ReachedFixedPoint::Yes; + macros.retain(|directive| { + let resolver = |path| { + let resolved_res = self.def_map.resolve_path_fp_with_macro( + self.db, + ResolveMode::Other, + directive.module_id, + &path, + BuiltinShadowMode::Module, + ); + resolved_res + .resolved_def + .take_macros() + .map(|it| (it, macro_id_to_def_id(self.db, it))) + }; + let resolver_def_id = |path| resolver(path).map(|(_, it)| it); + + match &directive.kind { + MacroDirectiveKind::FnLike { ast_id, expand_to } => { + let call_id = macro_call_as_call_id( + self.db, + ast_id, + *expand_to, + self.def_map.krate, + &resolver_def_id, + &mut |_err| (), + ); + if let Ok(Ok(call_id)) = call_id { + push_resolved(directive, call_id); + res = ReachedFixedPoint::No; + return false; + } + } + MacroDirectiveKind::Derive { ast_id, derive_attr, derive_pos } => { + let id = derive_macro_as_call_id( + self.db, + ast_id, + *derive_attr, + *derive_pos as u32, + self.def_map.krate, + &resolver, + ); + + if let Ok((macro_id, def_id, call_id)) = id { + self.def_map.modules[directive.module_id].scope.set_derive_macro_invoc( + ast_id.ast_id, + call_id, + *derive_attr, + *derive_pos, + ); + // Record its helper attributes. + if def_id.krate != self.def_map.krate { + let def_map = self.db.crate_def_map(def_id.krate); + if let Some(helpers) = def_map.exported_derives.get(&def_id) { + self.def_map + .derive_helpers_in_scope + .entry(ast_id.ast_id.map(|it| it.upcast())) + .or_default() + .extend(izip!( + helpers.iter().cloned(), + iter::repeat(macro_id), + iter::repeat(call_id), + )); + } + } + + push_resolved(directive, call_id); + res = ReachedFixedPoint::No; + return false; + } + } + MacroDirectiveKind::Attr { ast_id: file_ast_id, mod_item, attr, tree } => { + let &AstIdWithPath { ast_id, ref path } = file_ast_id; + let file_id = ast_id.file_id; + + let mut recollect_without = |collector: &mut Self| { + // Remove the original directive since we resolved it. + let mod_dir = collector.mod_dirs[&directive.module_id].clone(); + collector.skip_attrs.insert(InFile::new(file_id, *mod_item), attr.id); + + let item_tree = tree.item_tree(self.db); + ModCollector { + def_collector: collector, + macro_depth: directive.depth, + module_id: directive.module_id, + tree_id: *tree, + item_tree: &item_tree, + mod_dir, + } + .collect(&[*mod_item], directive.container); + res = ReachedFixedPoint::No; + false + }; + + if let Some(ident) = path.as_ident() { + if let Some(helpers) = self.def_map.derive_helpers_in_scope.get(&ast_id) { + if helpers.iter().any(|(it, ..)| it == ident) { + cov_mark::hit!(resolved_derive_helper); + // Resolved to derive helper. Collect the item's attributes again, + // starting after the derive helper. + return recollect_without(self); + } + } + } + + let def = match resolver_def_id(path.clone()) { + Some(def) if def.is_attribute() => def, + _ => return true, + }; + if matches!( + def, + MacroDefId { kind:MacroDefKind::BuiltInAttr(expander, _),.. } + if expander.is_derive() + ) { + // Resolved to `#[derive]` + + let item_tree = tree.item_tree(self.db); + let ast_adt_id: FileAstId<ast::Adt> = match *mod_item { + ModItem::Struct(strukt) => item_tree[strukt].ast_id().upcast(), + ModItem::Union(union) => item_tree[union].ast_id().upcast(), + ModItem::Enum(enum_) => item_tree[enum_].ast_id().upcast(), + _ => { + let diag = DefDiagnostic::invalid_derive_target( + directive.module_id, + ast_id, + attr.id, + ); + self.def_map.diagnostics.push(diag); + return recollect_without(self); + } + }; + let ast_id = ast_id.with_value(ast_adt_id); + + match attr.parse_path_comma_token_tree() { + Some(derive_macros) => { + let mut len = 0; + for (idx, path) in derive_macros.enumerate() { + let ast_id = AstIdWithPath::new(file_id, ast_id.value, path); + self.unresolved_macros.push(MacroDirective { + module_id: directive.module_id, + depth: directive.depth + 1, + kind: MacroDirectiveKind::Derive { + ast_id, + derive_attr: attr.id, + derive_pos: idx, + }, + container: directive.container, + }); + len = idx; + } + + // We treat the #[derive] macro as an attribute call, but we do not resolve it for nameres collection. + // This is just a trick to be able to resolve the input to derives as proper paths. + // Check the comment in [`builtin_attr_macro`]. + let call_id = attr_macro_as_call_id( + self.db, + file_ast_id, + attr, + self.def_map.krate, + def, + true, + ); + self.def_map.modules[directive.module_id] + .scope + .init_derive_attribute(ast_id, attr.id, call_id, len + 1); + } + None => { + let diag = DefDiagnostic::malformed_derive( + directive.module_id, + ast_id, + attr.id, + ); + self.def_map.diagnostics.push(diag); + } + } + + return recollect_without(self); + } + + // Not resolved to a derive helper or the derive attribute, so try to treat as a normal attribute. + let call_id = attr_macro_as_call_id( + self.db, + file_ast_id, + attr, + self.def_map.krate, + def, + false, + ); + let loc: MacroCallLoc = self.db.lookup_intern_macro_call(call_id); + + // If proc attribute macro expansion is disabled, skip expanding it here + if !self.db.enable_proc_attr_macros() { + self.def_map.diagnostics.push(DefDiagnostic::unresolved_proc_macro( + directive.module_id, + loc.kind, + loc.def.krate, + )); + return recollect_without(self); + } + + // Skip #[test]/#[bench] expansion, which would merely result in more memory usage + // due to duplicating functions into macro expansions + if matches!( + loc.def.kind, + MacroDefKind::BuiltInAttr(expander, _) + if expander.is_test() || expander.is_bench() + ) { + return recollect_without(self); + } + + if let MacroDefKind::ProcMacro(exp, ..) = loc.def.kind { + if exp.is_dummy() { + // If there's no expander for the proc macro (e.g. + // because proc macros are disabled, or building the + // proc macro crate failed), report this and skip + // expansion like we would if it was disabled + self.def_map.diagnostics.push(DefDiagnostic::unresolved_proc_macro( + directive.module_id, + loc.kind, + loc.def.krate, + )); + + return recollect_without(self); + } + } + + self.def_map.modules[directive.module_id] + .scope + .add_attr_macro_invoc(ast_id, call_id); + + push_resolved(directive, call_id); + res = ReachedFixedPoint::No; + return false; + } + } + + true + }); + // Attribute resolution can add unresolved macro invocations, so concatenate the lists. + macros.extend(mem::take(&mut self.unresolved_macros)); + self.unresolved_macros = macros; + + for (module_id, depth, container, macro_call_id) in resolved { + self.collect_macro_expansion(module_id, macro_call_id, depth, container); + } + + res + } + + fn collect_macro_expansion( + &mut self, + module_id: LocalModuleId, + macro_call_id: MacroCallId, + depth: usize, + container: ItemContainerId, + ) { + if EXPANSION_DEPTH_LIMIT.check(depth).is_err() { + cov_mark::hit!(macro_expansion_overflow); + tracing::warn!("macro expansion is too deep"); + return; + } + let file_id = macro_call_id.as_file(); + + // First, fetch the raw expansion result for purposes of error reporting. This goes through + // `macro_expand_error` to avoid depending on the full expansion result (to improve + // incrementality). + let loc: MacroCallLoc = self.db.lookup_intern_macro_call(macro_call_id); + let err = self.db.macro_expand_error(macro_call_id); + if let Some(err) = err { + let diag = match err { + hir_expand::ExpandError::UnresolvedProcMacro(krate) => { + always!(krate == loc.def.krate); + // Missing proc macros are non-fatal, so they are handled specially. + DefDiagnostic::unresolved_proc_macro(module_id, loc.kind.clone(), loc.def.krate) + } + _ => DefDiagnostic::macro_error(module_id, loc.kind.clone(), err.to_string()), + }; + + self.def_map.diagnostics.push(diag); + } + + // Then, fetch and process the item tree. This will reuse the expansion result from above. + let item_tree = self.db.file_item_tree(file_id); + let mod_dir = self.mod_dirs[&module_id].clone(); + ModCollector { + def_collector: &mut *self, + macro_depth: depth, + tree_id: TreeId::new(file_id, None), + module_id, + item_tree: &item_tree, + mod_dir, + } + .collect(item_tree.top_level_items(), container); + } + + fn finish(mut self) -> DefMap { + // Emit diagnostics for all remaining unexpanded macros. + + let _p = profile::span("DefCollector::finish"); + + for directive in &self.unresolved_macros { + match &directive.kind { + MacroDirectiveKind::FnLike { ast_id, expand_to } => { + let macro_call_as_call_id = macro_call_as_call_id( + self.db, + ast_id, + *expand_to, + self.def_map.krate, + |path| { + let resolved_res = self.def_map.resolve_path_fp_with_macro( + self.db, + ResolveMode::Other, + directive.module_id, + &path, + BuiltinShadowMode::Module, + ); + resolved_res + .resolved_def + .take_macros() + .map(|it| macro_id_to_def_id(self.db, it)) + }, + &mut |_| (), + ); + if let Err(UnresolvedMacro { path }) = macro_call_as_call_id { + self.def_map.diagnostics.push(DefDiagnostic::unresolved_macro_call( + directive.module_id, + MacroCallKind::FnLike { ast_id: ast_id.ast_id, expand_to: *expand_to }, + path, + )); + } + } + MacroDirectiveKind::Derive { ast_id, derive_attr, derive_pos } => { + self.def_map.diagnostics.push(DefDiagnostic::unresolved_macro_call( + directive.module_id, + MacroCallKind::Derive { + ast_id: ast_id.ast_id, + derive_attr_index: derive_attr.ast_index, + derive_index: *derive_pos as u32, + }, + ast_id.path.clone(), + )); + } + // These are diagnosed by `reseed_with_unresolved_attribute`, as that function consumes them + MacroDirectiveKind::Attr { .. } => {} + } + } + + // Emit diagnostics for all remaining unresolved imports. + + // We'd like to avoid emitting a diagnostics avalanche when some `extern crate` doesn't + // resolve. We first emit diagnostics for unresolved extern crates and collect the missing + // crate names. Then we emit diagnostics for unresolved imports, but only if the import + // doesn't start with an unresolved crate's name. Due to renaming and reexports, this is a + // heuristic, but it works in practice. + let mut diagnosed_extern_crates = FxHashSet::default(); + for directive in &self.unresolved_imports { + if let ImportSource::ExternCrate(krate) = directive.import.source { + let item_tree = krate.item_tree(self.db); + let extern_crate = &item_tree[krate.value]; + + diagnosed_extern_crates.insert(extern_crate.name.clone()); + + self.def_map.diagnostics.push(DefDiagnostic::unresolved_extern_crate( + directive.module_id, + InFile::new(krate.file_id(), extern_crate.ast_id), + )); + } + } + + for directive in &self.unresolved_imports { + if let ImportSource::Import { id: import, use_tree } = directive.import.source { + if matches!( + (directive.import.path.segments().first(), &directive.import.path.kind), + (Some(krate), PathKind::Plain | PathKind::Abs) if diagnosed_extern_crates.contains(krate) + ) { + continue; + } + + self.def_map.diagnostics.push(DefDiagnostic::unresolved_import( + directive.module_id, + import, + use_tree, + )); + } + } + + self.def_map + } +} + +/// Walks a single module, populating defs, imports and macros +struct ModCollector<'a, 'b> { + def_collector: &'a mut DefCollector<'b>, + macro_depth: usize, + module_id: LocalModuleId, + tree_id: TreeId, + item_tree: &'a ItemTree, + mod_dir: ModDir, +} + +impl ModCollector<'_, '_> { + fn collect_in_top_module(&mut self, items: &[ModItem]) { + let module = self.def_collector.def_map.module_id(self.module_id); + self.collect(items, module.into()) + } + + fn collect(&mut self, items: &[ModItem], container: ItemContainerId) { + let krate = self.def_collector.def_map.krate; + + // Note: don't assert that inserted value is fresh: it's simply not true + // for macros. + self.def_collector.mod_dirs.insert(self.module_id, self.mod_dir.clone()); + + // Prelude module is always considered to be `#[macro_use]`. + if let Some(prelude_module) = self.def_collector.def_map.prelude { + if prelude_module.krate != krate { + cov_mark::hit!(prelude_is_macro_use); + self.def_collector.import_all_macros_exported(self.module_id, prelude_module.krate); + } + } + + // This should be processed eagerly instead of deferred to resolving. + // `#[macro_use] extern crate` is hoisted to imports macros before collecting + // any other items. + for &item in items { + let attrs = self.item_tree.attrs(self.def_collector.db, krate, item.into()); + if attrs.cfg().map_or(true, |cfg| self.is_cfg_enabled(&cfg)) { + if let ModItem::ExternCrate(id) = item { + let import = &self.item_tree[id]; + let attrs = self.item_tree.attrs( + self.def_collector.db, + krate, + ModItem::from(id).into(), + ); + if attrs.by_key("macro_use").exists() { + self.def_collector.import_macros_from_extern_crate(self.module_id, import); + } + } + } + } + + for &item in items { + let attrs = self.item_tree.attrs(self.def_collector.db, krate, item.into()); + if let Some(cfg) = attrs.cfg() { + if !self.is_cfg_enabled(&cfg) { + self.emit_unconfigured_diagnostic(item, &cfg); + continue; + } + } + + if let Err(()) = self.resolve_attributes(&attrs, item, container) { + // Do not process the item. It has at least one non-builtin attribute, so the + // fixed-point algorithm is required to resolve the rest of them. + continue; + } + + let db = self.def_collector.db; + let module = self.def_collector.def_map.module_id(self.module_id); + let def_map = &mut self.def_collector.def_map; + let update_def = + |def_collector: &mut DefCollector<'_>, id, name: &Name, vis, has_constructor| { + def_collector.def_map.modules[self.module_id].scope.declare(id); + def_collector.update( + self.module_id, + &[(Some(name.clone()), PerNs::from_def(id, vis, has_constructor))], + vis, + ImportType::Named, + ) + }; + let resolve_vis = |def_map: &DefMap, visibility| { + def_map + .resolve_visibility(db, self.module_id, visibility) + .unwrap_or(Visibility::Public) + }; + + match item { + ModItem::Mod(m) => self.collect_module(m, &attrs), + ModItem::Import(import_id) => { + let imports = Import::from_use( + db, + krate, + self.item_tree, + ItemTreeId::new(self.tree_id, import_id), + ); + self.def_collector.unresolved_imports.extend(imports.into_iter().map( + |import| ImportDirective { + module_id: self.module_id, + import, + status: PartialResolvedImport::Unresolved, + }, + )); + } + ModItem::ExternCrate(import_id) => { + self.def_collector.unresolved_imports.push(ImportDirective { + module_id: self.module_id, + import: Import::from_extern_crate( + db, + krate, + self.item_tree, + ItemTreeId::new(self.tree_id, import_id), + ), + status: PartialResolvedImport::Unresolved, + }) + } + ModItem::ExternBlock(block) => self.collect( + &self.item_tree[block].children, + ItemContainerId::ExternBlockId( + ExternBlockLoc { + container: module, + id: ItemTreeId::new(self.tree_id, block), + } + .intern(db), + ), + ), + ModItem::MacroCall(mac) => self.collect_macro_call(&self.item_tree[mac], container), + ModItem::MacroRules(id) => self.collect_macro_rules(id, module), + ModItem::MacroDef(id) => self.collect_macro_def(id, module), + ModItem::Impl(imp) => { + let impl_id = + ImplLoc { container: module, id: ItemTreeId::new(self.tree_id, imp) } + .intern(db); + self.def_collector.def_map.modules[self.module_id].scope.define_impl(impl_id) + } + ModItem::Function(id) => { + let it = &self.item_tree[id]; + let fn_id = + FunctionLoc { container, id: ItemTreeId::new(self.tree_id, id) }.intern(db); + + let vis = resolve_vis(def_map, &self.item_tree[it.visibility]); + if self.def_collector.is_proc_macro { + if self.module_id == def_map.root { + if let Some(proc_macro) = attrs.parse_proc_macro_decl(&it.name) { + let crate_root = def_map.module_id(def_map.root); + self.def_collector.export_proc_macro( + proc_macro, + ItemTreeId::new(self.tree_id, id), + fn_id, + crate_root, + ); + } + } + } + + update_def(self.def_collector, fn_id.into(), &it.name, vis, false); + } + ModItem::Struct(id) => { + let it = &self.item_tree[id]; + + let vis = resolve_vis(def_map, &self.item_tree[it.visibility]); + update_def( + self.def_collector, + StructLoc { container: module, id: ItemTreeId::new(self.tree_id, id) } + .intern(db) + .into(), + &it.name, + vis, + !matches!(it.fields, Fields::Record(_)), + ); + } + ModItem::Union(id) => { + let it = &self.item_tree[id]; + + let vis = resolve_vis(def_map, &self.item_tree[it.visibility]); + update_def( + self.def_collector, + UnionLoc { container: module, id: ItemTreeId::new(self.tree_id, id) } + .intern(db) + .into(), + &it.name, + vis, + false, + ); + } + ModItem::Enum(id) => { + let it = &self.item_tree[id]; + + let vis = resolve_vis(def_map, &self.item_tree[it.visibility]); + update_def( + self.def_collector, + EnumLoc { container: module, id: ItemTreeId::new(self.tree_id, id) } + .intern(db) + .into(), + &it.name, + vis, + false, + ); + } + ModItem::Const(id) => { + let it = &self.item_tree[id]; + let const_id = + ConstLoc { container, id: ItemTreeId::new(self.tree_id, id) }.intern(db); + + match &it.name { + Some(name) => { + let vis = resolve_vis(def_map, &self.item_tree[it.visibility]); + update_def(self.def_collector, const_id.into(), name, vis, false); + } + None => { + // const _: T = ...; + self.def_collector.def_map.modules[self.module_id] + .scope + .define_unnamed_const(const_id); + } + } + } + ModItem::Static(id) => { + let it = &self.item_tree[id]; + + let vis = resolve_vis(def_map, &self.item_tree[it.visibility]); + update_def( + self.def_collector, + StaticLoc { container, id: ItemTreeId::new(self.tree_id, id) } + .intern(db) + .into(), + &it.name, + vis, + false, + ); + } + ModItem::Trait(id) => { + let it = &self.item_tree[id]; + + let vis = resolve_vis(def_map, &self.item_tree[it.visibility]); + update_def( + self.def_collector, + TraitLoc { container: module, id: ItemTreeId::new(self.tree_id, id) } + .intern(db) + .into(), + &it.name, + vis, + false, + ); + } + ModItem::TypeAlias(id) => { + let it = &self.item_tree[id]; + + let vis = resolve_vis(def_map, &self.item_tree[it.visibility]); + update_def( + self.def_collector, + TypeAliasLoc { container, id: ItemTreeId::new(self.tree_id, id) } + .intern(db) + .into(), + &it.name, + vis, + false, + ); + } + } + } + } + + fn collect_module(&mut self, module_id: FileItemTreeId<Mod>, attrs: &Attrs) { + let path_attr = attrs.by_key("path").string_value(); + let is_macro_use = attrs.by_key("macro_use").exists(); + let module = &self.item_tree[module_id]; + match &module.kind { + // inline module, just recurse + ModKind::Inline { items } => { + let module_id = self.push_child_module( + module.name.clone(), + AstId::new(self.file_id(), module.ast_id), + None, + &self.item_tree[module.visibility], + module_id, + ); + + if let Some(mod_dir) = self.mod_dir.descend_into_definition(&module.name, path_attr) + { + ModCollector { + def_collector: &mut *self.def_collector, + macro_depth: self.macro_depth, + module_id, + tree_id: self.tree_id, + item_tree: self.item_tree, + mod_dir, + } + .collect_in_top_module(&*items); + if is_macro_use { + self.import_all_legacy_macros(module_id); + } + } + } + // out of line module, resolve, parse and recurse + ModKind::Outline => { + let ast_id = AstId::new(self.tree_id.file_id(), module.ast_id); + let db = self.def_collector.db; + match self.mod_dir.resolve_declaration(db, self.file_id(), &module.name, path_attr) + { + Ok((file_id, is_mod_rs, mod_dir)) => { + let item_tree = db.file_item_tree(file_id.into()); + let krate = self.def_collector.def_map.krate; + let is_enabled = item_tree + .top_level_attrs(db, krate) + .cfg() + .map_or(true, |cfg| self.is_cfg_enabled(&cfg)); + if is_enabled { + let module_id = self.push_child_module( + module.name.clone(), + ast_id, + Some((file_id, is_mod_rs)), + &self.item_tree[module.visibility], + module_id, + ); + ModCollector { + def_collector: self.def_collector, + macro_depth: self.macro_depth, + module_id, + tree_id: TreeId::new(file_id.into(), None), + item_tree: &item_tree, + mod_dir, + } + .collect_in_top_module(item_tree.top_level_items()); + let is_macro_use = is_macro_use + || item_tree + .top_level_attrs(db, krate) + .by_key("macro_use") + .exists(); + if is_macro_use { + self.import_all_legacy_macros(module_id); + } + } + } + Err(candidates) => { + self.push_child_module( + module.name.clone(), + ast_id, + None, + &self.item_tree[module.visibility], + module_id, + ); + self.def_collector.def_map.diagnostics.push( + DefDiagnostic::unresolved_module(self.module_id, ast_id, candidates), + ); + } + }; + } + } + } + + fn push_child_module( + &mut self, + name: Name, + declaration: AstId<ast::Module>, + definition: Option<(FileId, bool)>, + visibility: &crate::visibility::RawVisibility, + mod_tree_id: FileItemTreeId<Mod>, + ) -> LocalModuleId { + let def_map = &mut self.def_collector.def_map; + let vis = def_map + .resolve_visibility(self.def_collector.db, self.module_id, visibility) + .unwrap_or(Visibility::Public); + let modules = &mut def_map.modules; + let origin = match definition { + None => ModuleOrigin::Inline { + definition: declaration, + definition_tree_id: ItemTreeId::new(self.tree_id, mod_tree_id), + }, + Some((definition, is_mod_rs)) => ModuleOrigin::File { + declaration, + definition, + is_mod_rs, + declaration_tree_id: ItemTreeId::new(self.tree_id, mod_tree_id), + }, + }; + + let res = modules.alloc(ModuleData::new(origin, vis)); + modules[res].parent = Some(self.module_id); + for (name, mac) in modules[self.module_id].scope.collect_legacy_macros() { + for &mac in &mac { + modules[res].scope.define_legacy_macro(name.clone(), mac); + } + } + modules[self.module_id].children.insert(name.clone(), res); + + let module = def_map.module_id(res); + let def = ModuleDefId::from(module); + + def_map.modules[self.module_id].scope.declare(def); + self.def_collector.update( + self.module_id, + &[(Some(name), PerNs::from_def(def, vis, false))], + vis, + ImportType::Named, + ); + res + } + + /// Resolves attributes on an item. + /// + /// Returns `Err` when some attributes could not be resolved to builtins and have been + /// registered as unresolved. + /// + /// If `ignore_up_to` is `Some`, attributes preceding and including that attribute will be + /// assumed to be resolved already. + fn resolve_attributes( + &mut self, + attrs: &Attrs, + mod_item: ModItem, + container: ItemContainerId, + ) -> Result<(), ()> { + let mut ignore_up_to = + self.def_collector.skip_attrs.get(&InFile::new(self.file_id(), mod_item)).copied(); + let iter = attrs + .iter() + .dedup_by(|a, b| { + // FIXME: this should not be required, all attributes on an item should have a + // unique ID! + // Still, this occurs because `#[cfg_attr]` can "expand" to multiple attributes: + // #[cfg_attr(not(off), unresolved, unresolved)] + // struct S; + // We should come up with a different way to ID attributes. + a.id == b.id + }) + .skip_while(|attr| match ignore_up_to { + Some(id) if attr.id == id => { + ignore_up_to = None; + true + } + Some(_) => true, + None => false, + }); + + for attr in iter { + if self.def_collector.def_map.is_builtin_or_registered_attr(&attr.path) { + continue; + } + tracing::debug!("non-builtin attribute {}", attr.path); + + let ast_id = AstIdWithPath::new( + self.file_id(), + mod_item.ast_id(self.item_tree), + attr.path.as_ref().clone(), + ); + self.def_collector.unresolved_macros.push(MacroDirective { + module_id: self.module_id, + depth: self.macro_depth + 1, + kind: MacroDirectiveKind::Attr { + ast_id, + attr: attr.clone(), + mod_item, + tree: self.tree_id, + }, + container, + }); + + return Err(()); + } + + Ok(()) + } + + fn collect_macro_rules(&mut self, id: FileItemTreeId<MacroRules>, module: ModuleId) { + let krate = self.def_collector.def_map.krate; + let mac = &self.item_tree[id]; + let attrs = self.item_tree.attrs(self.def_collector.db, krate, ModItem::from(id).into()); + let ast_id = InFile::new(self.file_id(), mac.ast_id.upcast()); + + let export_attr = attrs.by_key("macro_export"); + + let is_export = export_attr.exists(); + let local_inner = if is_export { + export_attr.tt_values().flat_map(|it| &it.token_trees).any(|it| match it { + tt::TokenTree::Leaf(tt::Leaf::Ident(ident)) => { + ident.text.contains("local_inner_macros") + } + _ => false, + }) + } else { + false + }; + + // Case 1: builtin macros + let expander = if attrs.by_key("rustc_builtin_macro").exists() { + // `#[rustc_builtin_macro = "builtin_name"]` overrides the `macro_rules!` name. + let name; + let name = match attrs.by_key("rustc_builtin_macro").string_value() { + Some(it) => { + // FIXME: a hacky way to create a Name from string. + name = tt::Ident { text: it.clone(), id: tt::TokenId::unspecified() }.as_name(); + &name + } + None => { + let explicit_name = + attrs.by_key("rustc_builtin_macro").tt_values().next().and_then(|tt| { + match tt.token_trees.first() { + Some(tt::TokenTree::Leaf(tt::Leaf::Ident(name))) => Some(name), + _ => None, + } + }); + match explicit_name { + Some(ident) => { + name = ident.as_name(); + &name + } + None => &mac.name, + } + } + }; + match find_builtin_macro(name) { + Some(Either::Left(it)) => MacroExpander::BuiltIn(it), + Some(Either::Right(it)) => MacroExpander::BuiltInEager(it), + None => { + self.def_collector + .def_map + .diagnostics + .push(DefDiagnostic::unimplemented_builtin_macro(self.module_id, ast_id)); + return; + } + } + } else { + // Case 2: normal `macro_rules!` macro + MacroExpander::Declarative + }; + + let macro_id = MacroRulesLoc { + container: module, + id: ItemTreeId::new(self.tree_id, id), + local_inner, + expander, + } + .intern(self.def_collector.db); + self.def_collector.define_macro_rules( + self.module_id, + mac.name.clone(), + macro_id, + is_export, + ); + } + + fn collect_macro_def(&mut self, id: FileItemTreeId<MacroDef>, module: ModuleId) { + let krate = self.def_collector.def_map.krate; + let mac = &self.item_tree[id]; + let ast_id = InFile::new(self.file_id(), mac.ast_id.upcast()); + + // Case 1: builtin macros + let attrs = self.item_tree.attrs(self.def_collector.db, krate, ModItem::from(id).into()); + let expander = if attrs.by_key("rustc_builtin_macro").exists() { + if let Some(expander) = find_builtin_macro(&mac.name) { + match expander { + Either::Left(it) => MacroExpander::BuiltIn(it), + Either::Right(it) => MacroExpander::BuiltInEager(it), + } + } else if let Some(expander) = find_builtin_derive(&mac.name) { + MacroExpander::BuiltInDerive(expander) + } else if let Some(expander) = find_builtin_attr(&mac.name) { + MacroExpander::BuiltInAttr(expander) + } else { + self.def_collector + .def_map + .diagnostics + .push(DefDiagnostic::unimplemented_builtin_macro(self.module_id, ast_id)); + return; + } + } else { + // Case 2: normal `macro` + MacroExpander::Declarative + }; + + let macro_id = + Macro2Loc { container: module, id: ItemTreeId::new(self.tree_id, id), expander } + .intern(self.def_collector.db); + self.def_collector.define_macro_def( + self.module_id, + mac.name.clone(), + macro_id, + &self.item_tree[mac.visibility], + ); + } + + fn collect_macro_call(&mut self, mac: &MacroCall, container: ItemContainerId) { + let ast_id = AstIdWithPath::new(self.file_id(), mac.ast_id, ModPath::clone(&mac.path)); + + // Case 1: try to resolve in legacy scope and expand macro_rules + let mut error = None; + match macro_call_as_call_id( + self.def_collector.db, + &ast_id, + mac.expand_to, + self.def_collector.def_map.krate, + |path| { + path.as_ident().and_then(|name| { + self.def_collector.def_map.with_ancestor_maps( + self.def_collector.db, + self.module_id, + &mut |map, module| { + map[module] + .scope + .get_legacy_macro(name) + .and_then(|it| it.last()) + .map(|&it| macro_id_to_def_id(self.def_collector.db, it.into())) + }, + ) + }) + }, + &mut |err| { + error.get_or_insert(err); + }, + ) { + Ok(Ok(macro_call_id)) => { + // Legacy macros need to be expanded immediately, so that any macros they produce + // are in scope. + self.def_collector.collect_macro_expansion( + self.module_id, + macro_call_id, + self.macro_depth + 1, + container, + ); + + if let Some(err) = error { + self.def_collector.def_map.diagnostics.push(DefDiagnostic::macro_error( + self.module_id, + MacroCallKind::FnLike { ast_id: ast_id.ast_id, expand_to: mac.expand_to }, + err.to_string(), + )); + } + + return; + } + Ok(Err(_)) => { + // Built-in macro failed eager expansion. + + self.def_collector.def_map.diagnostics.push(DefDiagnostic::macro_error( + self.module_id, + MacroCallKind::FnLike { ast_id: ast_id.ast_id, expand_to: mac.expand_to }, + error.unwrap().to_string(), + )); + return; + } + Err(UnresolvedMacro { .. }) => (), + } + + // Case 2: resolve in module scope, expand during name resolution. + self.def_collector.unresolved_macros.push(MacroDirective { + module_id: self.module_id, + depth: self.macro_depth + 1, + kind: MacroDirectiveKind::FnLike { ast_id, expand_to: mac.expand_to }, + container, + }); + } + + fn import_all_legacy_macros(&mut self, module_id: LocalModuleId) { + let macros = self.def_collector.def_map[module_id].scope.collect_legacy_macros(); + for (name, macs) in macros { + macs.last().map(|&mac| { + self.def_collector.define_legacy_macro(self.module_id, name.clone(), mac) + }); + } + } + + fn is_cfg_enabled(&self, cfg: &CfgExpr) -> bool { + self.def_collector.cfg_options.check(cfg) != Some(false) + } + + fn emit_unconfigured_diagnostic(&mut self, item: ModItem, cfg: &CfgExpr) { + let ast_id = item.ast_id(self.item_tree); + + let ast_id = InFile::new(self.file_id(), ast_id); + self.def_collector.def_map.diagnostics.push(DefDiagnostic::unconfigured_code( + self.module_id, + ast_id, + cfg.clone(), + self.def_collector.cfg_options.clone(), + )); + } + + fn file_id(&self) -> HirFileId { + self.tree_id.file_id() + } +} + +#[cfg(test)] +mod tests { + use crate::{db::DefDatabase, test_db::TestDB}; + use base_db::{fixture::WithFixture, SourceDatabase}; + + use super::*; + + fn do_collect_defs(db: &dyn DefDatabase, def_map: DefMap) -> DefMap { + let mut collector = DefCollector { + db, + def_map, + deps: FxHashMap::default(), + glob_imports: FxHashMap::default(), + unresolved_imports: Vec::new(), + indeterminate_imports: Vec::new(), + unresolved_macros: Vec::new(), + mod_dirs: FxHashMap::default(), + cfg_options: &CfgOptions::default(), + proc_macros: Default::default(), + from_glob_import: Default::default(), + skip_attrs: Default::default(), + is_proc_macro: false, + }; + collector.seed_with_top_level(); + collector.collect(); + collector.def_map + } + + fn do_resolve(not_ra_fixture: &str) -> DefMap { + let (db, file_id) = TestDB::with_single_file(not_ra_fixture); + let krate = db.test_crate(); + + let edition = db.crate_graph()[krate].edition; + let module_origin = ModuleOrigin::CrateRoot { definition: file_id }; + let def_map = + DefMap::empty(krate, edition, ModuleData::new(module_origin, Visibility::Public)); + do_collect_defs(&db, def_map) + } + + #[test] + fn test_macro_expand_will_stop_1() { + do_resolve( + r#" +macro_rules! foo { + ($($ty:ty)*) => { foo!($($ty)*); } +} +foo!(KABOOM); +"#, + ); + do_resolve( + r#" +macro_rules! foo { + ($($ty:ty)*) => { foo!(() $($ty)*); } +} +foo!(KABOOM); +"#, + ); + } + + #[ignore] + #[test] + fn test_macro_expand_will_stop_2() { + // FIXME: this test does succeed, but takes quite a while: 90 seconds in + // the release mode. That's why the argument is not an ra_fixture -- + // otherwise injection highlighting gets stuck. + // + // We need to find a way to fail this faster. + do_resolve( + r#" +macro_rules! foo { + ($($ty:ty)*) => { foo!($($ty)* $($ty)*); } +} +foo!(KABOOM); +"#, + ); + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/diagnostics.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/diagnostics.rs new file mode 100644 index 000000000..0d01f6d0a --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/diagnostics.rs @@ -0,0 +1,137 @@ +//! Diagnostics emitted during DefMap construction. + +use base_db::CrateId; +use cfg::{CfgExpr, CfgOptions}; +use hir_expand::MacroCallKind; +use la_arena::Idx; +use syntax::ast; + +use crate::{ + attr::AttrId, + item_tree::{self, ItemTreeId}, + nameres::LocalModuleId, + path::ModPath, + AstId, +}; + +#[derive(Debug, PartialEq, Eq)] +pub enum DefDiagnosticKind { + UnresolvedModule { ast: AstId<ast::Module>, candidates: Box<[String]> }, + + UnresolvedExternCrate { ast: AstId<ast::ExternCrate> }, + + UnresolvedImport { id: ItemTreeId<item_tree::Import>, index: Idx<ast::UseTree> }, + + UnconfiguredCode { ast: AstId<ast::Item>, cfg: CfgExpr, opts: CfgOptions }, + + UnresolvedProcMacro { ast: MacroCallKind, krate: CrateId }, + + UnresolvedMacroCall { ast: MacroCallKind, path: ModPath }, + + MacroError { ast: MacroCallKind, message: String }, + + UnimplementedBuiltinMacro { ast: AstId<ast::Macro> }, + + InvalidDeriveTarget { ast: AstId<ast::Item>, id: u32 }, + + MalformedDerive { ast: AstId<ast::Adt>, id: u32 }, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct DefDiagnostic { + pub in_module: LocalModuleId, + pub kind: DefDiagnosticKind, +} + +impl DefDiagnostic { + pub(super) fn unresolved_module( + container: LocalModuleId, + declaration: AstId<ast::Module>, + candidates: Box<[String]>, + ) -> Self { + Self { + in_module: container, + kind: DefDiagnosticKind::UnresolvedModule { ast: declaration, candidates }, + } + } + + pub(super) fn unresolved_extern_crate( + container: LocalModuleId, + declaration: AstId<ast::ExternCrate>, + ) -> Self { + Self { + in_module: container, + kind: DefDiagnosticKind::UnresolvedExternCrate { ast: declaration }, + } + } + + pub(super) fn unresolved_import( + container: LocalModuleId, + id: ItemTreeId<item_tree::Import>, + index: Idx<ast::UseTree>, + ) -> Self { + Self { in_module: container, kind: DefDiagnosticKind::UnresolvedImport { id, index } } + } + + pub(super) fn unconfigured_code( + container: LocalModuleId, + ast: AstId<ast::Item>, + cfg: CfgExpr, + opts: CfgOptions, + ) -> Self { + Self { in_module: container, kind: DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } } + } + + pub(super) fn unresolved_proc_macro( + container: LocalModuleId, + ast: MacroCallKind, + krate: CrateId, + ) -> Self { + Self { in_module: container, kind: DefDiagnosticKind::UnresolvedProcMacro { ast, krate } } + } + + pub(super) fn macro_error( + container: LocalModuleId, + ast: MacroCallKind, + message: String, + ) -> Self { + Self { in_module: container, kind: DefDiagnosticKind::MacroError { ast, message } } + } + + pub(super) fn unresolved_macro_call( + container: LocalModuleId, + ast: MacroCallKind, + path: ModPath, + ) -> Self { + Self { in_module: container, kind: DefDiagnosticKind::UnresolvedMacroCall { ast, path } } + } + + pub(super) fn unimplemented_builtin_macro( + container: LocalModuleId, + ast: AstId<ast::Macro>, + ) -> Self { + Self { in_module: container, kind: DefDiagnosticKind::UnimplementedBuiltinMacro { ast } } + } + + pub(super) fn invalid_derive_target( + container: LocalModuleId, + ast: AstId<ast::Item>, + id: AttrId, + ) -> Self { + Self { + in_module: container, + kind: DefDiagnosticKind::InvalidDeriveTarget { ast, id: id.ast_index }, + } + } + + pub(super) fn malformed_derive( + container: LocalModuleId, + ast: AstId<ast::Adt>, + id: AttrId, + ) -> Self { + Self { + in_module: container, + kind: DefDiagnosticKind::MalformedDerive { ast, id: id.ast_index }, + } + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/mod_resolution.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/mod_resolution.rs new file mode 100644 index 000000000..52a620fe2 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/mod_resolution.rs @@ -0,0 +1,161 @@ +//! This module resolves `mod foo;` declaration to file. +use arrayvec::ArrayVec; +use base_db::{AnchoredPath, FileId}; +use hir_expand::name::Name; +use limit::Limit; +use syntax::SmolStr; + +use crate::{db::DefDatabase, HirFileId}; + +const MOD_DEPTH_LIMIT: Limit = Limit::new(32); + +#[derive(Clone, Debug)] +pub(super) struct ModDir { + /// `` for `mod.rs`, `lib.rs` + /// `foo/` for `foo.rs` + /// `foo/bar/` for `mod bar { mod x; }` nested in `foo.rs` + /// Invariant: path.is_empty() || path.ends_with('/') + dir_path: DirPath, + /// inside `./foo.rs`, mods with `#[path]` should *not* be relative to `./foo/` + root_non_dir_owner: bool, + depth: u32, +} + +impl ModDir { + pub(super) fn root() -> ModDir { + ModDir { dir_path: DirPath::empty(), root_non_dir_owner: false, depth: 0 } + } + + pub(super) fn descend_into_definition( + &self, + name: &Name, + attr_path: Option<&SmolStr>, + ) -> Option<ModDir> { + let path = match attr_path.map(SmolStr::as_str) { + None => { + let mut path = self.dir_path.clone(); + path.push(&name.to_smol_str()); + path + } + Some(attr_path) => { + let mut path = self.dir_path.join_attr(attr_path, self.root_non_dir_owner); + if !(path.is_empty() || path.ends_with('/')) { + path.push('/') + } + DirPath::new(path) + } + }; + self.child(path, false) + } + + fn child(&self, dir_path: DirPath, root_non_dir_owner: bool) -> Option<ModDir> { + let depth = self.depth + 1; + if MOD_DEPTH_LIMIT.check(depth as usize).is_err() { + tracing::error!("MOD_DEPTH_LIMIT exceeded"); + cov_mark::hit!(circular_mods); + return None; + } + Some(ModDir { dir_path, root_non_dir_owner, depth }) + } + + pub(super) fn resolve_declaration( + &self, + db: &dyn DefDatabase, + file_id: HirFileId, + name: &Name, + attr_path: Option<&SmolStr>, + ) -> Result<(FileId, bool, ModDir), Box<[String]>> { + let orig_file_id = file_id.original_file(db.upcast()); + + let mut candidate_files = ArrayVec::<_, 2>::new(); + match attr_path { + Some(attr_path) => { + candidate_files.push(self.dir_path.join_attr(attr_path, self.root_non_dir_owner)) + } + None if file_id.is_include_macro(db.upcast()) => { + candidate_files.push(format!("{}.rs", name)); + candidate_files.push(format!("{}/mod.rs", name)); + } + None => { + candidate_files.push(format!("{}{}.rs", self.dir_path.0, name)); + candidate_files.push(format!("{}{}/mod.rs", self.dir_path.0, name)); + } + }; + + for candidate in candidate_files.iter() { + let path = AnchoredPath { anchor: orig_file_id, path: candidate.as_str() }; + if let Some(file_id) = db.resolve_path(path) { + let is_mod_rs = candidate.ends_with("/mod.rs"); + + let (dir_path, root_non_dir_owner) = if is_mod_rs || attr_path.is_some() { + (DirPath::empty(), false) + } else { + (DirPath::new(format!("{}/", name)), true) + }; + if let Some(mod_dir) = self.child(dir_path, root_non_dir_owner) { + return Ok((file_id, is_mod_rs, mod_dir)); + } + } + } + Err(candidate_files.into_iter().collect()) + } +} + +#[derive(Clone, Debug)] +struct DirPath(String); + +impl DirPath { + fn assert_invariant(&self) { + assert!(self.0.is_empty() || self.0.ends_with('/')); + } + fn new(repr: String) -> DirPath { + let res = DirPath(repr); + res.assert_invariant(); + res + } + fn empty() -> DirPath { + DirPath::new(String::new()) + } + fn push(&mut self, name: &str) { + self.0.push_str(name); + self.0.push('/'); + self.assert_invariant(); + } + fn parent(&self) -> Option<&str> { + if self.0.is_empty() { + return None; + }; + let idx = + self.0[..self.0.len() - '/'.len_utf8()].rfind('/').map_or(0, |it| it + '/'.len_utf8()); + Some(&self.0[..idx]) + } + /// So this is the case which doesn't really work I think if we try to be + /// 100% platform agnostic: + /// + /// ``` + /// mod a { + /// #[path="C://sad/face"] + /// mod b { mod c; } + /// } + /// ``` + /// + /// Here, we need to join logical dir path to a string path from an + /// attribute. Ideally, we should somehow losslessly communicate the whole + /// construction to `FileLoader`. + fn join_attr(&self, mut attr: &str, relative_to_parent: bool) -> String { + let base = if relative_to_parent { self.parent().unwrap() } else { &self.0 }; + + if attr.starts_with("./") { + attr = &attr["./".len()..]; + } + let tmp; + let attr = if attr.contains('\\') { + tmp = attr.replace('\\', "/"); + &tmp + } else { + attr + }; + let res = format!("{}{}", base, attr); + res + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs new file mode 100644 index 000000000..c579bc919 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs @@ -0,0 +1,448 @@ +//! This modules implements a function to resolve a path `foo::bar::baz` to a +//! def, which is used within the name resolution. +//! +//! When name resolution is finished, the result of resolving a path is either +//! `Some(def)` or `None`. However, when we are in process of resolving imports +//! or macros, there's a third possibility: +//! +//! I can't resolve this path right now, but I might be resolve this path +//! later, when more macros are expanded. +//! +//! `ReachedFixedPoint` signals about this. + +use base_db::Edition; +use hir_expand::name::Name; + +use crate::{ + db::DefDatabase, + item_scope::BUILTIN_SCOPE, + nameres::{BuiltinShadowMode, DefMap}, + path::{ModPath, PathKind}, + per_ns::PerNs, + visibility::{RawVisibility, Visibility}, + AdtId, CrateId, EnumVariantId, LocalModuleId, ModuleDefId, ModuleId, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum ResolveMode { + Import, + Other, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum ReachedFixedPoint { + Yes, + No, +} + +#[derive(Debug, Clone)] +pub(super) struct ResolvePathResult { + pub(super) resolved_def: PerNs, + pub(super) segment_index: Option<usize>, + pub(super) reached_fixedpoint: ReachedFixedPoint, + pub(super) krate: Option<CrateId>, +} + +impl ResolvePathResult { + fn empty(reached_fixedpoint: ReachedFixedPoint) -> ResolvePathResult { + ResolvePathResult::with(PerNs::none(), reached_fixedpoint, None, None) + } + + fn with( + resolved_def: PerNs, + reached_fixedpoint: ReachedFixedPoint, + segment_index: Option<usize>, + krate: Option<CrateId>, + ) -> ResolvePathResult { + ResolvePathResult { resolved_def, segment_index, reached_fixedpoint, krate } + } +} + +impl DefMap { + pub(super) fn resolve_name_in_extern_prelude( + &self, + db: &dyn DefDatabase, + name: &Name, + ) -> Option<ModuleId> { + match self.block { + Some(_) => self.crate_root(db).def_map(db).extern_prelude.get(name).copied(), + None => self.extern_prelude.get(name).copied(), + } + } + + pub(crate) fn resolve_visibility( + &self, + db: &dyn DefDatabase, + original_module: LocalModuleId, + visibility: &RawVisibility, + ) -> Option<Visibility> { + let mut vis = match visibility { + RawVisibility::Module(path) => { + let (result, remaining) = + self.resolve_path(db, original_module, path, BuiltinShadowMode::Module); + if remaining.is_some() { + return None; + } + let types = result.take_types()?; + match types { + ModuleDefId::ModuleId(m) => Visibility::Module(m), + _ => { + // error: visibility needs to refer to module + return None; + } + } + } + RawVisibility::Public => Visibility::Public, + }; + + // In block expressions, `self` normally refers to the containing non-block module, and + // `super` to its parent (etc.). However, visibilities must only refer to a module in the + // DefMap they're written in, so we restrict them when that happens. + if let Visibility::Module(m) = vis { + if self.block_id() != m.block { + cov_mark::hit!(adjust_vis_in_block_def_map); + vis = Visibility::Module(self.module_id(self.root())); + tracing::debug!("visibility {:?} points outside DefMap, adjusting to {:?}", m, vis); + } + } + + Some(vis) + } + + // Returns Yes if we are sure that additions to `ItemMap` wouldn't change + // the result. + pub(super) fn resolve_path_fp_with_macro( + &self, + db: &dyn DefDatabase, + mode: ResolveMode, + mut original_module: LocalModuleId, + path: &ModPath, + shadow: BuiltinShadowMode, + ) -> ResolvePathResult { + let mut result = ResolvePathResult::empty(ReachedFixedPoint::No); + + let mut arc; + let mut current_map = self; + loop { + let new = current_map.resolve_path_fp_with_macro_single( + db, + mode, + original_module, + path, + shadow, + ); + + // Merge `new` into `result`. + result.resolved_def = result.resolved_def.or(new.resolved_def); + if result.reached_fixedpoint == ReachedFixedPoint::No { + result.reached_fixedpoint = new.reached_fixedpoint; + } + // FIXME: this doesn't seem right; what if the different namespace resolutions come from different crates? + result.krate = result.krate.or(new.krate); + result.segment_index = match (result.segment_index, new.segment_index) { + (Some(idx), None) => Some(idx), + (Some(old), Some(new)) => Some(old.max(new)), + (None, new) => new, + }; + + match ¤t_map.block { + Some(block) => { + original_module = block.parent.local_id; + arc = block.parent.def_map(db); + current_map = &*arc; + } + None => return result, + } + } + } + + pub(super) fn resolve_path_fp_with_macro_single( + &self, + db: &dyn DefDatabase, + mode: ResolveMode, + original_module: LocalModuleId, + path: &ModPath, + shadow: BuiltinShadowMode, + ) -> ResolvePathResult { + let graph = db.crate_graph(); + let _cx = stdx::panic_context::enter(format!( + "DefMap {:?} crate_name={:?} block={:?} path={}", + self.krate, graph[self.krate].display_name, self.block, path + )); + + let mut segments = path.segments().iter().enumerate(); + let mut curr_per_ns: PerNs = match path.kind { + PathKind::DollarCrate(krate) => { + if krate == self.krate { + cov_mark::hit!(macro_dollar_crate_self); + PerNs::types(self.crate_root(db).into(), Visibility::Public) + } else { + let def_map = db.crate_def_map(krate); + let module = def_map.module_id(def_map.root); + cov_mark::hit!(macro_dollar_crate_other); + PerNs::types(module.into(), Visibility::Public) + } + } + PathKind::Crate => PerNs::types(self.crate_root(db).into(), Visibility::Public), + // plain import or absolute path in 2015: crate-relative with + // fallback to extern prelude (with the simplification in + // rust-lang/rust#57745) + // FIXME there must be a nicer way to write this condition + PathKind::Plain | PathKind::Abs + if self.edition == Edition::Edition2015 + && (path.kind == PathKind::Abs || mode == ResolveMode::Import) => + { + let (_, segment) = match segments.next() { + Some((idx, segment)) => (idx, segment), + None => return ResolvePathResult::empty(ReachedFixedPoint::Yes), + }; + tracing::debug!("resolving {:?} in crate root (+ extern prelude)", segment); + self.resolve_name_in_crate_root_or_extern_prelude(db, segment) + } + PathKind::Plain => { + let (_, segment) = match segments.next() { + Some((idx, segment)) => (idx, segment), + None => return ResolvePathResult::empty(ReachedFixedPoint::Yes), + }; + // The first segment may be a builtin type. If the path has more + // than one segment, we first try resolving it as a module + // anyway. + // FIXME: If the next segment doesn't resolve in the module and + // BuiltinShadowMode wasn't Module, then we need to try + // resolving it as a builtin. + let prefer_module = + if path.segments().len() == 1 { shadow } else { BuiltinShadowMode::Module }; + + tracing::debug!("resolving {:?} in module", segment); + self.resolve_name_in_module(db, original_module, segment, prefer_module) + } + PathKind::Super(lvl) => { + let mut module = original_module; + for i in 0..lvl { + match self.modules[module].parent { + Some(it) => module = it, + None => match &self.block { + Some(block) => { + // Look up remaining path in parent `DefMap` + let new_path = ModPath::from_segments( + PathKind::Super(lvl - i), + path.segments().to_vec(), + ); + tracing::debug!( + "`super` path: {} -> {} in parent map", + path, + new_path + ); + return block.parent.def_map(db).resolve_path_fp_with_macro( + db, + mode, + block.parent.local_id, + &new_path, + shadow, + ); + } + None => { + tracing::debug!("super path in root module"); + return ResolvePathResult::empty(ReachedFixedPoint::Yes); + } + }, + } + } + + // Resolve `self` to the containing crate-rooted module if we're a block + self.with_ancestor_maps(db, module, &mut |def_map, module| { + if def_map.block.is_some() { + None // keep ascending + } else { + Some(PerNs::types(def_map.module_id(module).into(), Visibility::Public)) + } + }) + .expect("block DefMap not rooted in crate DefMap") + } + PathKind::Abs => { + // 2018-style absolute path -- only extern prelude + let segment = match segments.next() { + Some((_, segment)) => segment, + None => return ResolvePathResult::empty(ReachedFixedPoint::Yes), + }; + if let Some(&def) = self.extern_prelude.get(segment) { + tracing::debug!("absolute path {:?} resolved to crate {:?}", path, def); + PerNs::types(def.into(), Visibility::Public) + } else { + return ResolvePathResult::empty(ReachedFixedPoint::No); // extern crate declarations can add to the extern prelude + } + } + }; + + for (i, segment) in segments { + let (curr, vis) = match curr_per_ns.take_types_vis() { + Some(r) => r, + None => { + // we still have path segments left, but the path so far + // didn't resolve in the types namespace => no resolution + // (don't break here because `curr_per_ns` might contain + // something in the value namespace, and it would be wrong + // to return that) + return ResolvePathResult::empty(ReachedFixedPoint::No); + } + }; + // resolve segment in curr + + curr_per_ns = match curr { + ModuleDefId::ModuleId(module) => { + if module.krate != self.krate { + let path = ModPath::from_segments( + PathKind::Super(0), + path.segments()[i..].iter().cloned(), + ); + tracing::debug!("resolving {:?} in other crate", path); + let defp_map = module.def_map(db); + let (def, s) = defp_map.resolve_path(db, module.local_id, &path, shadow); + return ResolvePathResult::with( + def, + ReachedFixedPoint::Yes, + s.map(|s| s + i), + Some(module.krate), + ); + } + + let def_map; + let module_data = if module.block == self.block_id() { + &self[module.local_id] + } else { + def_map = module.def_map(db); + &def_map[module.local_id] + }; + + // Since it is a qualified path here, it should not contains legacy macros + module_data.scope.get(segment) + } + ModuleDefId::AdtId(AdtId::EnumId(e)) => { + // enum variant + cov_mark::hit!(can_import_enum_variant); + let enum_data = db.enum_data(e); + match enum_data.variant(segment) { + Some(local_id) => { + let variant = EnumVariantId { parent: e, local_id }; + match &*enum_data.variants[local_id].variant_data { + crate::adt::VariantData::Record(_) => { + PerNs::types(variant.into(), Visibility::Public) + } + crate::adt::VariantData::Tuple(_) + | crate::adt::VariantData::Unit => { + PerNs::both(variant.into(), variant.into(), Visibility::Public) + } + } + } + None => { + return ResolvePathResult::with( + PerNs::types(e.into(), vis), + ReachedFixedPoint::Yes, + Some(i), + Some(self.krate), + ); + } + } + } + s => { + // could be an inherent method call in UFCS form + // (`Struct::method`), or some other kind of associated item + tracing::debug!( + "path segment {:?} resolved to non-module {:?}, but is not last", + segment, + curr, + ); + + return ResolvePathResult::with( + PerNs::types(s, vis), + ReachedFixedPoint::Yes, + Some(i), + Some(self.krate), + ); + } + }; + } + + ResolvePathResult::with(curr_per_ns, ReachedFixedPoint::Yes, None, Some(self.krate)) + } + + fn resolve_name_in_module( + &self, + db: &dyn DefDatabase, + module: LocalModuleId, + name: &Name, + shadow: BuiltinShadowMode, + ) -> PerNs { + // Resolve in: + // - legacy scope of macro + // - current module / scope + // - extern prelude + // - std prelude + let from_legacy_macro = self[module] + .scope + .get_legacy_macro(name) + // FIXME: shadowing + .and_then(|it| it.last()) + .map_or_else(PerNs::none, |&m| PerNs::macros(m.into(), Visibility::Public)); + let from_scope = self[module].scope.get(name); + let from_builtin = match self.block { + Some(_) => { + // Only resolve to builtins in the root `DefMap`. + PerNs::none() + } + None => BUILTIN_SCOPE.get(name).copied().unwrap_or_else(PerNs::none), + }; + let from_scope_or_builtin = match shadow { + BuiltinShadowMode::Module => from_scope.or(from_builtin), + BuiltinShadowMode::Other => match from_scope.take_types() { + Some(ModuleDefId::ModuleId(_)) => from_builtin.or(from_scope), + Some(_) | None => from_scope.or(from_builtin), + }, + }; + let from_extern_prelude = self + .extern_prelude + .get(name) + .map_or(PerNs::none(), |&it| PerNs::types(it.into(), Visibility::Public)); + + let from_prelude = self.resolve_in_prelude(db, name); + + from_legacy_macro.or(from_scope_or_builtin).or(from_extern_prelude).or(from_prelude) + } + + fn resolve_name_in_crate_root_or_extern_prelude( + &self, + db: &dyn DefDatabase, + name: &Name, + ) -> PerNs { + let arc; + let crate_def_map = match self.block { + Some(_) => { + arc = self.crate_root(db).def_map(db); + &arc + } + None => self, + }; + let from_crate_root = crate_def_map[crate_def_map.root].scope.get(name); + let from_extern_prelude = self + .resolve_name_in_extern_prelude(db, name) + .map_or(PerNs::none(), |it| PerNs::types(it.into(), Visibility::Public)); + + from_crate_root.or(from_extern_prelude) + } + + fn resolve_in_prelude(&self, db: &dyn DefDatabase, name: &Name) -> PerNs { + if let Some(prelude) = self.prelude { + let keep; + let def_map = if prelude.krate == self.krate { + self + } else { + // Extend lifetime + keep = prelude.def_map(db); + &keep + }; + def_map[prelude.local_id].scope.get(name) + } else { + PerNs::none() + } + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/proc_macro.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/proc_macro.rs new file mode 100644 index 000000000..5089ef2d8 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/proc_macro.rs @@ -0,0 +1,81 @@ +//! Nameres-specific procedural macro data and helpers. + +use hir_expand::name::{AsName, Name}; +use tt::{Leaf, TokenTree}; + +use crate::attr::Attrs; + +#[derive(Debug, PartialEq, Eq)] +pub struct ProcMacroDef { + pub name: Name, + pub kind: ProcMacroKind, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum ProcMacroKind { + CustomDerive { helpers: Box<[Name]> }, + FnLike, + Attr, +} + +impl ProcMacroKind { + pub(super) fn to_basedb_kind(&self) -> base_db::ProcMacroKind { + match self { + ProcMacroKind::CustomDerive { .. } => base_db::ProcMacroKind::CustomDerive, + ProcMacroKind::FnLike => base_db::ProcMacroKind::FuncLike, + ProcMacroKind::Attr => base_db::ProcMacroKind::Attr, + } + } +} + +impl Attrs { + #[rustfmt::skip] + pub fn parse_proc_macro_decl(&self, func_name: &Name) -> Option<ProcMacroDef> { + if self.is_proc_macro() { + Some(ProcMacroDef { name: func_name.clone(), kind: ProcMacroKind::FnLike }) + } else if self.is_proc_macro_attribute() { + Some(ProcMacroDef { name: func_name.clone(), kind: ProcMacroKind::Attr }) + } else if self.by_key("proc_macro_derive").exists() { + let derive = self.by_key("proc_macro_derive").tt_values().next()?; + + match &*derive.token_trees { + // `#[proc_macro_derive(Trait)]` + [TokenTree::Leaf(Leaf::Ident(trait_name))] => Some(ProcMacroDef { + name: trait_name.as_name(), + kind: ProcMacroKind::CustomDerive { helpers: Box::new([]) }, + }), + + // `#[proc_macro_derive(Trait, attibutes(helper1, helper2, ...))]` + [ + TokenTree::Leaf(Leaf::Ident(trait_name)), + TokenTree::Leaf(Leaf::Punct(comma)), + TokenTree::Leaf(Leaf::Ident(attributes)), + TokenTree::Subtree(helpers) + ] if comma.char == ',' && attributes.text == "attributes" => + { + let helpers = helpers.token_trees.iter() + .filter(|tt| !matches!(tt, TokenTree::Leaf(Leaf::Punct(comma)) if comma.char == ',')) + .map(|tt| { + match tt { + TokenTree::Leaf(Leaf::Ident(helper)) => Some(helper.as_name()), + _ => None + } + }) + .collect::<Option<Box<[_]>>>()?; + + Some(ProcMacroDef { + name: trait_name.as_name(), + kind: ProcMacroKind::CustomDerive { helpers }, + }) + } + + _ => { + tracing::trace!("malformed `#[proc_macro_derive]`: {}", derive); + None + } + } + } else { + None + } + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs new file mode 100644 index 000000000..70dd2eb3a --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs @@ -0,0 +1,933 @@ +mod globs; +mod incremental; +mod macros; +mod mod_resolution; +mod primitives; + +use std::sync::Arc; + +use base_db::{fixture::WithFixture, SourceDatabase}; +use expect_test::{expect, Expect}; + +use crate::{db::DefDatabase, test_db::TestDB}; + +use super::DefMap; + +fn compute_crate_def_map(ra_fixture: &str) -> Arc<DefMap> { + let db = TestDB::with_files(ra_fixture); + let krate = db.crate_graph().iter().next().unwrap(); + db.crate_def_map(krate) +} + +fn render_crate_def_map(ra_fixture: &str) -> String { + let db = TestDB::with_files(ra_fixture); + let krate = db.crate_graph().iter().next().unwrap(); + db.crate_def_map(krate).dump(&db) +} + +fn check(ra_fixture: &str, expect: Expect) { + let actual = render_crate_def_map(ra_fixture); + expect.assert_eq(&actual); +} + +#[test] +fn crate_def_map_smoke_test() { + check( + r#" +//- /lib.rs +mod foo; +struct S; +use crate::foo::bar::E; +use self::E::V; + +//- /foo/mod.rs +pub mod bar; +fn f() {} + +//- /foo/bar.rs +pub struct Baz; + +union U { to_be: bool, not_to_be: u8 } +enum E { V } + +extern { + type Ext; + static EXT: u8; + fn ext(); +} +"#, + expect![[r#" + crate + E: t + S: t v + V: t v + foo: t + + crate::foo + bar: t + f: v + + crate::foo::bar + Baz: t v + E: t + EXT: v + Ext: t + U: t + ext: v + "#]], + ); +} + +#[test] +fn crate_def_map_super_super() { + check( + r#" +mod a { + const A: usize = 0; + mod b { + const B: usize = 0; + mod c { + use super::super::*; + } + } +} +"#, + expect![[r#" + crate + a: t + + crate::a + A: v + b: t + + crate::a::b + B: v + c: t + + crate::a::b::c + A: v + b: t + "#]], + ); +} + +#[test] +fn crate_def_map_fn_mod_same_name() { + check( + r#" +mod m { + pub mod z {} + pub fn z() {} +} +"#, + expect![[r#" + crate + m: t + + crate::m + z: t v + + crate::m::z + "#]], + ); +} + +#[test] +fn bogus_paths() { + cov_mark::check!(bogus_paths); + check( + r#" +//- /lib.rs +mod foo; +struct S; +use self; + +//- /foo/mod.rs +use super; +use crate; +"#, + expect![[r#" + crate + S: t v + foo: t + + crate::foo + "#]], + ); +} + +#[test] +fn use_as() { + check( + r#" +//- /lib.rs +mod foo; +use crate::foo::Baz as Foo; + +//- /foo/mod.rs +pub struct Baz; +"#, + expect![[r#" + crate + Foo: t v + foo: t + + crate::foo + Baz: t v + "#]], + ); +} + +#[test] +fn use_trees() { + check( + r#" +//- /lib.rs +mod foo; +use crate::foo::bar::{Baz, Quux}; + +//- /foo/mod.rs +pub mod bar; + +//- /foo/bar.rs +pub struct Baz; +pub enum Quux {}; +"#, + expect![[r#" + crate + Baz: t v + Quux: t + foo: t + + crate::foo + bar: t + + crate::foo::bar + Baz: t v + Quux: t + "#]], + ); +} + +#[test] +fn re_exports() { + check( + r#" +//- /lib.rs +mod foo; +use self::foo::Baz; + +//- /foo/mod.rs +pub mod bar; +pub use self::bar::Baz; + +//- /foo/bar.rs +pub struct Baz; +"#, + expect![[r#" + crate + Baz: t v + foo: t + + crate::foo + Baz: t v + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn std_prelude() { + cov_mark::check!(std_prelude); + check( + r#" +//- /main.rs crate:main deps:test_crate +#[prelude_import] +use ::test_crate::prelude::*; + +use Foo::*; + +//- /lib.rs crate:test_crate +pub mod prelude; + +//- /prelude.rs +pub enum Foo { Bar, Baz } +"#, + expect![[r#" + crate + Bar: t v + Baz: t v + "#]], + ); +} + +#[test] +fn can_import_enum_variant() { + cov_mark::check!(can_import_enum_variant); + check( + r#" +enum E { V } +use self::E::V; +"#, + expect![[r#" + crate + E: t + V: t v + "#]], + ); +} + +#[test] +fn edition_2015_imports() { + check( + r#" +//- /main.rs crate:main deps:other_crate edition:2015 +mod foo; +mod bar; + +//- /bar.rs +struct Bar; + +//- /foo.rs +use bar::Bar; +use other_crate::FromLib; + +//- /lib.rs crate:other_crate edition:2018 +pub struct FromLib; +"#, + expect![[r#" + crate + bar: t + foo: t + + crate::bar + Bar: t v + + crate::foo + Bar: t v + FromLib: t v + "#]], + ); +} + +#[test] +fn item_map_using_self() { + check( + r#" +//- /lib.rs +mod foo; +use crate::foo::bar::Baz::{self}; + +//- /foo/mod.rs +pub mod bar; + +//- /foo/bar.rs +pub struct Baz; +"#, + expect![[r#" + crate + Baz: t + foo: t + + crate::foo + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn item_map_across_crates() { + check( + r#" +//- /main.rs crate:main deps:test_crate +use test_crate::Baz; + +//- /lib.rs crate:test_crate +pub struct Baz; +"#, + expect![[r#" + crate + Baz: t v + "#]], + ); +} + +#[test] +fn extern_crate_rename() { + check( + r#" +//- /main.rs crate:main deps:alloc +extern crate alloc as alloc_crate; +mod alloc; +mod sync; + +//- /sync.rs +use alloc_crate::Arc; + +//- /lib.rs crate:alloc +pub struct Arc; +"#, + expect![[r#" + crate + alloc: t + alloc_crate: t + sync: t + + crate::alloc + + crate::sync + Arc: t v + "#]], + ); +} + +#[test] +fn extern_crate_rename_2015_edition() { + check( + r#" +//- /main.rs crate:main deps:alloc edition:2015 +extern crate alloc as alloc_crate; +mod alloc; +mod sync; + +//- /sync.rs +use alloc_crate::Arc; + +//- /lib.rs crate:alloc +pub struct Arc; +"#, + expect![[r#" + crate + alloc: t + alloc_crate: t + sync: t + + crate::alloc + + crate::sync + Arc: t v + "#]], + ); +} + +#[test] +fn macro_use_extern_crate_self() { + cov_mark::check!(ignore_macro_use_extern_crate_self); + check( + r#" +//- /main.rs crate:main +#[macro_use] +extern crate self as bla; +"#, + expect![[r#" + crate + bla: t + "#]], + ); +} + +#[test] +fn reexport_across_crates() { + check( + r#" +//- /main.rs crate:main deps:test_crate +use test_crate::Baz; + +//- /lib.rs crate:test_crate +pub use foo::Baz; +mod foo; + +//- /foo.rs +pub struct Baz; +"#, + expect![[r#" + crate + Baz: t v + "#]], + ); +} + +#[test] +fn values_dont_shadow_extern_crates() { + check( + r#" +//- /main.rs crate:main deps:foo +fn foo() {} +use foo::Bar; + +//- /foo/lib.rs crate:foo +pub struct Bar; +"#, + expect![[r#" + crate + Bar: t v + foo: v + "#]], + ); +} + +#[test] +fn no_std_prelude() { + check( + r#" + //- /main.rs crate:main deps:core,std + #![cfg_attr(not(never), no_std)] + use Rust; + + //- /core.rs crate:core + pub mod prelude { + pub mod rust_2018 { + pub struct Rust; + } + } + //- /std.rs crate:std deps:core + pub mod prelude { + pub mod rust_2018 { + } + } + "#, + expect![[r#" + crate + Rust: t v + "#]], + ); +} + +#[test] +fn edition_specific_preludes() { + // We can't test the 2015 prelude here since you can't reexport its contents with 2015's + // absolute paths. + + check( + r#" + //- /main.rs edition:2018 crate:main deps:std + use Rust2018; + + //- /std.rs crate:std + pub mod prelude { + pub mod rust_2018 { + pub struct Rust2018; + } + } + "#, + expect![[r#" + crate + Rust2018: t v + "#]], + ); + check( + r#" + //- /main.rs edition:2021 crate:main deps:std + use Rust2021; + + //- /std.rs crate:std + pub mod prelude { + pub mod rust_2021 { + pub struct Rust2021; + } + } + "#, + expect![[r#" + crate + Rust2021: t v + "#]], + ); +} + +#[test] +fn std_prelude_takes_precedence_above_core_prelude() { + check( + r#" +//- /main.rs crate:main deps:core,std +use {Foo, Bar}; + +//- /std.rs crate:std deps:core +pub mod prelude { + pub mod rust_2018 { + pub struct Foo; + pub use core::prelude::rust_2018::Bar; + } +} + +//- /core.rs crate:core +pub mod prelude { + pub mod rust_2018 { + pub struct Bar; + } +} +"#, + expect![[r#" + crate + Bar: t v + Foo: t v + "#]], + ); +} + +#[test] +fn cfg_not_test() { + check( + r#" +//- /main.rs crate:main deps:std +use {Foo, Bar, Baz}; + +//- /lib.rs crate:std +pub mod prelude { + pub mod rust_2018 { + #[cfg(test)] + pub struct Foo; + #[cfg(not(test))] + pub struct Bar; + #[cfg(all(not(any()), feature = "foo", feature = "bar", opt = "42"))] + pub struct Baz; + } +} +"#, + expect![[r#" + crate + Bar: t v + Baz: _ + Foo: _ + "#]], + ); +} + +#[test] +fn cfg_test() { + check( + r#" +//- /main.rs crate:main deps:std +use {Foo, Bar, Baz}; + +//- /lib.rs crate:std cfg:test,feature=foo,feature=bar,opt=42 +pub mod prelude { + pub mod rust_2018 { + #[cfg(test)] + pub struct Foo; + #[cfg(not(test))] + pub struct Bar; + #[cfg(all(not(any()), feature = "foo", feature = "bar", opt = "42"))] + pub struct Baz; + } +} +"#, + expect![[r#" + crate + Bar: _ + Baz: t v + Foo: t v + "#]], + ); +} + +#[test] +fn infer_multiple_namespace() { + check( + r#" +//- /main.rs +mod a { + pub type T = (); + pub use crate::b::*; +} + +use crate::a::T; + +mod b { + pub const T: () = (); +} +"#, + expect![[r#" + crate + T: t v + a: t + b: t + + crate::a + T: t v + + crate::b + T: v + "#]], + ); +} + +#[test] +fn underscore_import() { + check( + r#" +//- /main.rs +use tr::Tr as _; +use tr::Tr2 as _; + +mod tr { + pub trait Tr {} + pub trait Tr2 {} +} + "#, + expect![[r#" + crate + _: t + _: t + tr: t + + crate::tr + Tr: t + Tr2: t + "#]], + ); +} + +#[test] +fn underscore_reexport() { + check( + r#" +//- /main.rs +mod tr { + pub trait PubTr {} + pub trait PrivTr {} +} +mod reex { + use crate::tr::PrivTr as _; + pub use crate::tr::PubTr as _; +} +use crate::reex::*; + "#, + expect![[r#" + crate + _: t + reex: t + tr: t + + crate::reex + _: t + _: t + + crate::tr + PrivTr: t + PubTr: t + "#]], + ); +} + +#[test] +fn underscore_pub_crate_reexport() { + cov_mark::check!(upgrade_underscore_visibility); + check( + r#" +//- /main.rs crate:main deps:lib +use lib::*; + +//- /lib.rs crate:lib +use tr::Tr as _; +pub use tr::Tr as _; + +mod tr { + pub trait Tr {} +} + "#, + expect![[r#" + crate + _: t + "#]], + ); +} + +#[test] +fn underscore_nontrait() { + check( + r#" +//- /main.rs +mod m { + pub struct Struct; + pub enum Enum {} + pub const CONST: () = (); +} +use crate::m::{Struct as _, Enum as _, CONST as _}; + "#, + expect![[r#" + crate + m: t + + crate::m + CONST: v + Enum: t + Struct: t v + "#]], + ); +} + +#[test] +fn underscore_name_conflict() { + check( + r#" +//- /main.rs +struct Tr; + +use tr::Tr as _; + +mod tr { + pub trait Tr {} +} + "#, + expect![[r#" + crate + _: t + Tr: t v + tr: t + + crate::tr + Tr: t + "#]], + ); +} + +#[test] +fn cfg_the_entire_crate() { + check( + r#" +//- /main.rs +#![cfg(never)] + +pub struct S; +pub enum E {} +pub fn f() {} + "#, + expect![[r#" + crate + "#]], + ); +} + +#[test] +fn use_crate_as() { + check( + r#" +use crate as foo; + +use foo::bar as baz; + +fn bar() {} + "#, + expect![[r#" + crate + bar: v + baz: v + foo: t + "#]], + ); +} + +#[test] +fn self_imports_only_types() { + check( + r#" +//- /main.rs +mod m { + pub macro S() {} + pub struct S; +} + +use self::m::S::{self}; + "#, + expect![[r#" + crate + S: t + m: t + + crate::m + S: t v m + "#]], + ); +} + +#[test] +fn import_from_extern_crate_only_imports_public_items() { + check( + r#" +//- /lib.rs crate:lib deps:settings,macros +use macros::settings; +use settings::Settings; +//- /settings.rs crate:settings +pub struct Settings; +//- /macros.rs crate:macros +mod settings {} +pub const settings: () = (); + "#, + expect![[r#" + crate + Settings: t v + settings: v + "#]], + ) +} + +#[test] +fn non_prelude_deps() { + check( + r#" +//- /lib.rs crate:lib deps:dep extern-prelude: +use dep::Struct; +//- /dep.rs crate:dep +pub struct Struct; + "#, + expect![[r#" + crate + Struct: _ + "#]], + ); + check( + r#" +//- /lib.rs crate:lib deps:dep extern-prelude: +extern crate dep; +use dep::Struct; +//- /dep.rs crate:dep +pub struct Struct; + "#, + expect![[r#" + crate + Struct: t v + dep: t + "#]], + ); +} + +#[test] +fn braced_supers_in_use_tree() { + cov_mark::check!(concat_super_mod_paths); + check( + r#" +mod some_module { + pub fn unknown_func() {} +} + +mod other_module { + mod some_submodule { + use { super::{ super::unknown_func, }, }; + } +} + +use some_module::unknown_func; + "#, + expect![[r#" + crate + other_module: t + some_module: t + unknown_func: v + + crate::other_module + some_submodule: t + + crate::other_module::some_submodule + unknown_func: v + + crate::some_module + unknown_func: v + "#]], + ) +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/globs.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/globs.rs new file mode 100644 index 000000000..b2a6a592c --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/globs.rs @@ -0,0 +1,338 @@ +use super::*; + +#[test] +fn glob_1() { + check( + r#" +//- /lib.rs +mod foo; +use foo::*; + +//- /foo/mod.rs +pub mod bar; +pub use self::bar::Baz; +pub struct Foo; + +//- /foo/bar.rs +pub struct Baz; +"#, + expect![[r#" + crate + Baz: t v + Foo: t v + bar: t + foo: t + + crate::foo + Baz: t v + Foo: t v + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn glob_2() { + check( + r#" +//- /lib.rs +mod foo; +use foo::*; + +//- /foo/mod.rs +pub mod bar; +pub use self::bar::*; +pub struct Foo; + +//- /foo/bar.rs +pub struct Baz; +pub use super::*; +"#, + expect![[r#" + crate + Baz: t v + Foo: t v + bar: t + foo: t + + crate::foo + Baz: t v + Foo: t v + bar: t + + crate::foo::bar + Baz: t v + Foo: t v + bar: t + "#]], + ); +} + +#[test] +fn glob_privacy_1() { + check( + r" +//- /lib.rs +mod foo; +use foo::*; + +//- /foo/mod.rs +pub mod bar; +pub use self::bar::*; +struct PrivateStructFoo; + +//- /foo/bar.rs +pub struct Baz; +struct PrivateStructBar; +pub use super::*; +", + expect![[r#" + crate + Baz: t v + bar: t + foo: t + + crate::foo + Baz: t v + PrivateStructFoo: t v + bar: t + + crate::foo::bar + Baz: t v + PrivateStructBar: t v + PrivateStructFoo: t v + bar: t + "#]], + ); +} + +#[test] +fn glob_privacy_2() { + check( + r" +//- /lib.rs +mod foo; +use foo::*; +use foo::bar::*; + +//- /foo/mod.rs +mod bar; +fn Foo() {}; +pub struct Foo {}; + +//- /foo/bar.rs +pub(super) struct PrivateBaz; +struct PrivateBar; +pub(crate) struct PubCrateStruct; +", + expect![[r#" + crate + Foo: t + PubCrateStruct: t v + foo: t + + crate::foo + Foo: t v + bar: t + + crate::foo::bar + PrivateBar: t v + PrivateBaz: t v + PubCrateStruct: t v + "#]], + ); +} + +#[test] +fn glob_across_crates() { + cov_mark::check!(glob_across_crates); + check( + r#" +//- /main.rs crate:main deps:test_crate +use test_crate::*; + +//- /lib.rs crate:test_crate +pub struct Baz; +"#, + expect![[r#" + crate + Baz: t v + "#]], + ); +} + +#[test] +fn glob_privacy_across_crates() { + check( + r#" +//- /main.rs crate:main deps:test_crate +use test_crate::*; + +//- /lib.rs crate:test_crate +pub struct Baz; +struct Foo; +"#, + expect![[r#" + crate + Baz: t v + "#]], + ); +} + +#[test] +fn glob_enum() { + cov_mark::check!(glob_enum); + check( + r#" +enum Foo { Bar, Baz } +use self::Foo::*; +"#, + expect![[r#" + crate + Bar: t v + Baz: t v + Foo: t + "#]], + ); +} + +#[test] +fn glob_enum_group() { + cov_mark::check!(glob_enum_group); + check( + r#" +enum Foo { Bar, Baz } +use self::Foo::{*}; +"#, + expect![[r#" + crate + Bar: t v + Baz: t v + Foo: t + "#]], + ); +} + +#[test] +fn glob_shadowed_def() { + cov_mark::check!(import_shadowed); + check( + r#" +//- /lib.rs +mod foo; +mod bar; +use foo::*; +use bar::baz; +use baz::Bar; + +//- /foo.rs +pub mod baz { pub struct Foo; } + +//- /bar.rs +pub mod baz { pub struct Bar; } +"#, + expect![[r#" + crate + Bar: t v + bar: t + baz: t + foo: t + + crate::bar + baz: t + + crate::bar::baz + Bar: t v + + crate::foo + baz: t + + crate::foo::baz + Foo: t v + "#]], + ); +} + +#[test] +fn glob_shadowed_def_reversed() { + check( + r#" +//- /lib.rs +mod foo; +mod bar; +use bar::baz; +use foo::*; +use baz::Bar; + +//- /foo.rs +pub mod baz { pub struct Foo; } + +//- /bar.rs +pub mod baz { pub struct Bar; } +"#, + expect![[r#" + crate + Bar: t v + bar: t + baz: t + foo: t + + crate::bar + baz: t + + crate::bar::baz + Bar: t v + + crate::foo + baz: t + + crate::foo::baz + Foo: t v + "#]], + ); +} + +#[test] +fn glob_shadowed_def_dependencies() { + check( + r#" +mod a { pub mod foo { pub struct X; } } +mod b { pub use super::a::foo; } +mod c { pub mod foo { pub struct Y; } } +mod d { + use super::c::foo; + use super::b::*; + use foo::Y; +} +"#, + expect![[r#" + crate + a: t + b: t + c: t + d: t + + crate::a + foo: t + + crate::a::foo + X: t v + + crate::b + foo: t + + crate::c + foo: t + + crate::c::foo + Y: t v + + crate::d + Y: t v + foo: t + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs new file mode 100644 index 000000000..2e8cb3621 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs @@ -0,0 +1,237 @@ +use std::sync::Arc; + +use base_db::SourceDatabaseExt; + +use crate::{AdtId, ModuleDefId}; + +use super::*; + +fn check_def_map_is_not_recomputed(ra_fixture_initial: &str, ra_fixture_change: &str) { + let (mut db, pos) = TestDB::with_position(ra_fixture_initial); + let krate = db.test_crate(); + { + let events = db.log_executed(|| { + db.crate_def_map(krate); + }); + assert!(format!("{:?}", events).contains("crate_def_map"), "{:#?}", events) + } + db.set_file_text(pos.file_id, Arc::new(ra_fixture_change.to_string())); + + { + let events = db.log_executed(|| { + db.crate_def_map(krate); + }); + assert!(!format!("{:?}", events).contains("crate_def_map"), "{:#?}", events) + } +} + +#[test] +fn typing_inside_a_function_should_not_invalidate_def_map() { + check_def_map_is_not_recomputed( + r" + //- /lib.rs + mod foo;$0 + + use crate::foo::bar::Baz; + + enum E { A, B } + use E::*; + + fn foo() -> i32 { + 1 + 1 + } + + #[cfg(never)] + fn no() {} + //- /foo/mod.rs + pub mod bar; + + //- /foo/bar.rs + pub struct Baz; + ", + r" + mod foo; + + use crate::foo::bar::Baz; + + enum E { A, B } + use E::*; + + fn foo() -> i32 { 92 } + + #[cfg(never)] + fn no() {} + ", + ); +} + +#[test] +fn typing_inside_a_macro_should_not_invalidate_def_map() { + let (mut db, pos) = TestDB::with_position( + r" + //- /lib.rs + macro_rules! m { + ($ident:ident) => { + fn f() { + $ident + $ident; + }; + } + } + mod foo; + + //- /foo/mod.rs + pub mod bar; + + //- /foo/bar.rs + $0 + m!(X); + ", + ); + let krate = db.test_crate(); + { + let events = db.log_executed(|| { + let crate_def_map = db.crate_def_map(krate); + let (_, module_data) = crate_def_map.modules.iter().last().unwrap(); + assert_eq!(module_data.scope.resolutions().count(), 1); + }); + assert!(format!("{:?}", events).contains("crate_def_map"), "{:#?}", events) + } + db.set_file_text(pos.file_id, Arc::new("m!(Y);".to_string())); + + { + let events = db.log_executed(|| { + let crate_def_map = db.crate_def_map(krate); + let (_, module_data) = crate_def_map.modules.iter().last().unwrap(); + assert_eq!(module_data.scope.resolutions().count(), 1); + }); + assert!(!format!("{:?}", events).contains("crate_def_map"), "{:#?}", events) + } +} + +#[test] +fn typing_inside_a_function_should_not_invalidate_expansions() { + let (mut db, pos) = TestDB::with_position( + r#" +//- /lib.rs +macro_rules! m { + ($ident:ident) => { + fn $ident() { }; + } +} +mod foo; + +//- /foo/mod.rs +pub mod bar; + +//- /foo/bar.rs +m!(X); +fn quux() { 1$0 } +m!(Y); +m!(Z); +"#, + ); + let krate = db.test_crate(); + { + let events = db.log_executed(|| { + let crate_def_map = db.crate_def_map(krate); + let (_, module_data) = crate_def_map.modules.iter().last().unwrap(); + assert_eq!(module_data.scope.resolutions().count(), 4); + }); + let n_recalculated_item_trees = events.iter().filter(|it| it.contains("item_tree")).count(); + assert_eq!(n_recalculated_item_trees, 6); + let n_reparsed_macros = + events.iter().filter(|it| it.contains("parse_macro_expansion")).count(); + assert_eq!(n_reparsed_macros, 3); + } + + let new_text = r#" +m!(X); +fn quux() { 92 } +m!(Y); +m!(Z); +"#; + db.set_file_text(pos.file_id, Arc::new(new_text.to_string())); + + { + let events = db.log_executed(|| { + let crate_def_map = db.crate_def_map(krate); + let (_, module_data) = crate_def_map.modules.iter().last().unwrap(); + assert_eq!(module_data.scope.resolutions().count(), 4); + }); + let n_recalculated_item_trees = events.iter().filter(|it| it.contains("item_tree")).count(); + assert_eq!(n_recalculated_item_trees, 1); + let n_reparsed_macros = + events.iter().filter(|it| it.contains("parse_macro_expansion")).count(); + assert_eq!(n_reparsed_macros, 0); + } +} + +#[test] +fn item_tree_prevents_reparsing() { + // The `ItemTree` is used by both name resolution and the various queries in `adt.rs` and + // `data.rs`. After computing the `ItemTree` and deleting the parse tree, we should be able to + // run those other queries without triggering a reparse. + + let (db, pos) = TestDB::with_position( + r#" +pub struct S; +pub union U {} +pub enum E { + Variant, +} +pub fn f(_: S) { $0 } +pub trait Tr {} +impl Tr for () {} +pub const C: u8 = 0; +pub static ST: u8 = 0; +pub type Ty = (); +"#, + ); + let krate = db.test_crate(); + { + let events = db.log_executed(|| { + db.file_item_tree(pos.file_id.into()); + }); + let n_calculated_item_trees = events.iter().filter(|it| it.contains("item_tree")).count(); + assert_eq!(n_calculated_item_trees, 1); + let n_parsed_files = events.iter().filter(|it| it.contains("parse(")).count(); + assert_eq!(n_parsed_files, 1); + } + + // Delete the parse tree. + base_db::ParseQuery.in_db(&db).purge(); + + { + let events = db.log_executed(|| { + let crate_def_map = db.crate_def_map(krate); + let (_, module_data) = crate_def_map.modules.iter().last().unwrap(); + assert_eq!(module_data.scope.resolutions().count(), 8); + assert_eq!(module_data.scope.impls().count(), 1); + + for imp in module_data.scope.impls() { + db.impl_data(imp); + } + + for (_, res) in module_data.scope.resolutions() { + match res.values.or(res.types).unwrap().0 { + ModuleDefId::FunctionId(f) => drop(db.function_data(f)), + ModuleDefId::AdtId(adt) => match adt { + AdtId::StructId(it) => drop(db.struct_data(it)), + AdtId::UnionId(it) => drop(db.union_data(it)), + AdtId::EnumId(it) => drop(db.enum_data(it)), + }, + ModuleDefId::ConstId(it) => drop(db.const_data(it)), + ModuleDefId::StaticId(it) => drop(db.static_data(it)), + ModuleDefId::TraitId(it) => drop(db.trait_data(it)), + ModuleDefId::TypeAliasId(it) => drop(db.type_alias_data(it)), + ModuleDefId::EnumVariantId(_) + | ModuleDefId::ModuleId(_) + | ModuleDefId::MacroId(_) + | ModuleDefId::BuiltinType(_) => unreachable!(), + } + } + }); + let n_reparsed_files = events.iter().filter(|it| it.contains("parse(")).count(); + assert_eq!(n_reparsed_files, 0); + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs new file mode 100644 index 000000000..3ece1379a --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs @@ -0,0 +1,1187 @@ +use super::*; +use itertools::Itertools; + +#[test] +fn macro_rules_are_globally_visible() { + check( + r#" +//- /lib.rs +macro_rules! structs { + ($($i:ident),*) => { + $(struct $i { field: u32 } )* + } +} +structs!(Foo); +mod nested; + +//- /nested.rs +structs!(Bar, Baz); +"#, + expect![[r#" + crate + Foo: t + nested: t + + crate::nested + Bar: t + Baz: t + "#]], + ); +} + +#[test] +fn macro_rules_can_define_modules() { + check( + r#" +//- /lib.rs +macro_rules! m { + ($name:ident) => { mod $name; } +} +m!(n1); +mod m { m!(n3) } + +//- /n1.rs +m!(n2) +//- /n1/n2.rs +struct X; +//- /m/n3.rs +struct Y; +"#, + expect![[r#" + crate + m: t + n1: t + + crate::m + n3: t + + crate::m::n3 + Y: t v + + crate::n1 + n2: t + + crate::n1::n2 + X: t v + "#]], + ); +} + +#[test] +fn macro_rules_from_other_crates_are_visible() { + check( + r#" +//- /main.rs crate:main deps:foo +foo::structs!(Foo, Bar) +mod bar; + +//- /bar.rs +use crate::*; + +//- /lib.rs crate:foo +#[macro_export] +macro_rules! structs { + ($($i:ident),*) => { + $(struct $i { field: u32 } )* + } +} +"#, + expect![[r#" + crate + Bar: t + Foo: t + bar: t + + crate::bar + Bar: t + Foo: t + bar: t + "#]], + ); +} + +#[test] +fn macro_rules_export_with_local_inner_macros_are_visible() { + check( + r#" +//- /main.rs crate:main deps:foo +foo::structs!(Foo, Bar) +mod bar; + +//- /bar.rs +use crate::*; + +//- /lib.rs crate:foo +#[macro_export(local_inner_macros)] +macro_rules! structs { + ($($i:ident),*) => { + $(struct $i { field: u32 } )* + } +} +"#, + expect![[r#" + crate + Bar: t + Foo: t + bar: t + + crate::bar + Bar: t + Foo: t + bar: t + "#]], + ); +} + +#[test] +fn local_inner_macros_makes_local_macros_usable() { + check( + r#" +//- /main.rs crate:main deps:foo +foo::structs!(Foo, Bar); +mod bar; + +//- /bar.rs +use crate::*; + +//- /lib.rs crate:foo +#[macro_export(local_inner_macros)] +macro_rules! structs { + ($($i:ident),*) => { + inner!($($i),*); + } +} +#[macro_export] +macro_rules! inner { + ($($i:ident),*) => { + $(struct $i { field: u32 } )* + } +} +"#, + expect![[r#" + crate + Bar: t + Foo: t + bar: t + + crate::bar + Bar: t + Foo: t + bar: t + "#]], + ); +} + +#[test] +fn unexpanded_macro_should_expand_by_fixedpoint_loop() { + check( + r#" +//- /main.rs crate:main deps:foo +macro_rules! baz { + () => { + use foo::bar; + } +} +foo!(); +bar!(); +baz!(); + +//- /lib.rs crate:foo +#[macro_export] +macro_rules! foo { + () => { + struct Foo { field: u32 } + } +} +#[macro_export] +macro_rules! bar { + () => { + use foo::foo; + } +} +"#, + expect![[r#" + crate + Foo: t + bar: m + foo: m + "#]], + ); +} + +#[test] +fn macro_rules_from_other_crates_are_visible_with_macro_use() { + cov_mark::check!(macro_rules_from_other_crates_are_visible_with_macro_use); + check( + r#" +//- /main.rs crate:main deps:foo +structs!(Foo); +structs_priv!(Bar); +structs_not_exported!(MacroNotResolved1); +crate::structs!(MacroNotResolved2); + +mod bar; + +#[macro_use] +extern crate foo; + +//- /bar.rs +structs!(Baz); +crate::structs!(MacroNotResolved3); + +//- /lib.rs crate:foo +#[macro_export] +macro_rules! structs { + ($i:ident) => { struct $i; } +} + +macro_rules! structs_not_exported { + ($i:ident) => { struct $i; } +} + +mod priv_mod { + #[macro_export] + macro_rules! structs_priv { + ($i:ident) => { struct $i; } + } +} +"#, + expect![[r#" + crate + Bar: t v + Foo: t v + bar: t + foo: t + + crate::bar + Baz: t v + "#]], + ); +} + +#[test] +fn prelude_is_macro_use() { + cov_mark::check!(prelude_is_macro_use); + check( + r#" +//- /main.rs crate:main deps:std +structs!(Foo); +structs_priv!(Bar); +structs_outside!(Out); +crate::structs!(MacroNotResolved2); + +mod bar; + +//- /bar.rs +structs!(Baz); +crate::structs!(MacroNotResolved3); + +//- /lib.rs crate:std +pub mod prelude { + pub mod rust_2018 { + #[macro_export] + macro_rules! structs { + ($i:ident) => { struct $i; } + } + + mod priv_mod { + #[macro_export] + macro_rules! structs_priv { + ($i:ident) => { struct $i; } + } + } + } +} + +#[macro_export] +macro_rules! structs_outside { + ($i:ident) => { struct $i; } +} +"#, + expect![[r#" + crate + Bar: t v + Foo: t v + Out: t v + bar: t + + crate::bar + Baz: t v + "#]], + ); +} + +#[test] +fn prelude_cycle() { + check( + r#" +#[prelude_import] +use self::prelude::*; + +declare_mod!(); + +mod prelude { + macro_rules! declare_mod { + () => (mod foo {}) + } +} +"#, + expect![[r#" + crate + prelude: t + + crate::prelude + "#]], + ); +} + +#[test] +fn legacy_macro_use_before_def() { + check( + r#" +m!(); + +macro_rules! m { + () => { + struct S; + } +} +"#, + expect![[r#" + crate + S: t v + "#]], + ); + // FIXME: should not expand. legacy macro scoping is not implemented. +} + +#[test] +fn plain_macros_are_legacy_textual_scoped() { + check( + r#" +//- /main.rs +mod m1; +bar!(NotFoundNotMacroUse); + +mod m2 { foo!(NotFoundBeforeInside2); } + +macro_rules! foo { + ($x:ident) => { struct $x; } +} +foo!(Ok); + +mod m3; +foo!(OkShadowStop); +bar!(NotFoundMacroUseStop); + +#[macro_use] +mod m5 { + #[macro_use] + mod m6 { + macro_rules! foo { + ($x:ident) => { fn $x() {} } + } + } +} +foo!(ok_double_macro_use_shadow); + +baz!(NotFoundBefore); +#[macro_use] +mod m7 { + macro_rules! baz { + ($x:ident) => { struct $x; } + } +} +baz!(OkAfter); + +//- /m1.rs +foo!(NotFoundBeforeInside1); +macro_rules! bar { + ($x:ident) => { struct $x; } +} + +//- /m3/mod.rs +foo!(OkAfterInside); +macro_rules! foo { + ($x:ident) => { fn $x() {} } +} +foo!(ok_shadow); + +#[macro_use] +mod m4; +bar!(OkMacroUse); + +mod m5; +baz!(OkMacroUseInner); + +//- /m3/m4.rs +foo!(ok_shadow_deep); +macro_rules! bar { + ($x:ident) => { struct $x; } +} +//- /m3/m5.rs +#![macro_use] +macro_rules! baz { + ($x:ident) => { struct $x; } +} + + +"#, + expect![[r#" + crate + NotFoundBefore: t v + Ok: t v + OkAfter: t v + OkShadowStop: t v + m1: t + m2: t + m3: t + m5: t + m7: t + ok_double_macro_use_shadow: v + + crate::m1 + + crate::m2 + + crate::m3 + OkAfterInside: t v + OkMacroUse: t v + OkMacroUseInner: t v + m4: t + m5: t + ok_shadow: v + + crate::m3::m4 + ok_shadow_deep: v + + crate::m3::m5 + + crate::m5 + m6: t + + crate::m5::m6 + + crate::m7 + "#]], + ); + // FIXME: should not see `NotFoundBefore` +} + +#[test] +fn type_value_macro_live_in_different_scopes() { + check( + r#" +#[macro_export] +macro_rules! foo { + ($x:ident) => { type $x = (); } +} + +foo!(foo); +use foo as bar; + +use self::foo as baz; +fn baz() {} +"#, + expect![[r#" + crate + bar: t m + baz: t v m + foo: t m + "#]], + ); +} + +#[test] +fn macro_use_can_be_aliased() { + check( + r#" +//- /main.rs crate:main deps:foo +#[macro_use] +extern crate foo; + +foo!(Direct); +bar!(Alias); + +//- /lib.rs crate:foo +use crate::foo as bar; + +mod m { + #[macro_export] + macro_rules! foo { + ($x:ident) => { struct $x; } + } +} +"#, + expect![[r#" + crate + Alias: t v + Direct: t v + foo: t + "#]], + ); +} + +#[test] +fn path_qualified_macros() { + check( + r#" +macro_rules! foo { + ($x:ident) => { struct $x; } +} + +crate::foo!(NotResolved); + +crate::bar!(OkCrate); +bar!(OkPlain); +alias1!(NotHere); +m::alias1!(OkAliasPlain); +m::alias2!(OkAliasSuper); +m::alias3!(OkAliasCrate); +not_found!(NotFound); + +mod m { + #[macro_export] + macro_rules! bar { + ($x:ident) => { struct $x; } + } + pub use bar as alias1; + pub use super::bar as alias2; + pub use crate::bar as alias3; + pub use self::bar as not_found; +} +"#, + expect![[r#" + crate + OkAliasCrate: t v + OkAliasPlain: t v + OkAliasSuper: t v + OkCrate: t v + OkPlain: t v + bar: m + m: t + + crate::m + alias1: m + alias2: m + alias3: m + not_found: _ + "#]], + ); +} + +#[test] +fn macro_dollar_crate_is_correct_in_item() { + cov_mark::check!(macro_dollar_crate_self); + check( + r#" +//- /main.rs crate:main deps:foo +#[macro_use] +extern crate foo; + +#[macro_use] +mod m { + macro_rules! current { + () => { + use $crate::Foo as FooSelf; + } + } +} + +struct Foo; + +current!(); +not_current1!(); +foo::not_current2!(); + +//- /lib.rs crate:foo +mod m { + #[macro_export] + macro_rules! not_current1 { + () => { + use $crate::Bar; + } + } +} + +#[macro_export] +macro_rules! not_current2 { + () => { + use $crate::Baz; + } +} + +pub struct Bar; +pub struct Baz; +"#, + expect![[r#" + crate + Bar: t v + Baz: t v + Foo: t v + FooSelf: t v + foo: t + m: t + + crate::m + "#]], + ); +} + +#[test] +fn macro_dollar_crate_is_correct_in_indirect_deps() { + cov_mark::check!(macro_dollar_crate_other); + // From std + check( + r#" +//- /main.rs crate:main deps:std +foo!(); + +//- /std.rs crate:std deps:core +pub use core::foo; + +pub mod prelude { + pub mod rust_2018 {} +} + +#[macro_use] +mod std_macros; + +//- /core.rs crate:core +#[macro_export] +macro_rules! foo { + () => { + use $crate::bar; + } +} + +pub struct bar; +"#, + expect![[r#" + crate + bar: t v + "#]], + ); +} + +#[test] +fn expand_derive() { + let map = compute_crate_def_map( + r#" +//- /main.rs crate:main deps:core +use core::Copy; + +#[core::derive(Copy, core::Clone)] +struct Foo; + +//- /core.rs crate:core +#[rustc_builtin_macro] +pub macro derive($item:item) {} +#[rustc_builtin_macro] +pub macro Copy {} +#[rustc_builtin_macro] +pub macro Clone {} +"#, + ); + assert_eq!(map.modules[map.root].scope.impls().len(), 2); +} + +#[test] +fn resolve_builtin_derive() { + check( + r#" +//- /main.rs crate:main deps:core +use core::*; + +//- /core.rs crate:core +#[rustc_builtin_macro] +pub macro Clone {} + +pub trait Clone {} +"#, + expect![[r#" + crate + Clone: t m + "#]], + ); +} + +#[test] +fn builtin_derive_with_unresolved_attributes_fall_back() { + // Tests that we still resolve derives after ignoring an unresolved attribute. + cov_mark::check!(unresolved_attribute_fallback); + let map = compute_crate_def_map( + r#" +//- /main.rs crate:main deps:core +use core::{Clone, derive}; + +#[derive(Clone)] +#[unresolved] +struct Foo; + +//- /core.rs crate:core +#[rustc_builtin_macro] +pub macro derive($item:item) {} +#[rustc_builtin_macro] +pub macro Clone {} +"#, + ); + assert_eq!(map.modules[map.root].scope.impls().len(), 1); +} + +#[test] +fn unresolved_attributes_fall_back_track_per_file_moditems() { + // Tests that we track per-file ModItems when ignoring an unresolved attribute. + // Just tracking the `ModItem` leads to `Foo` getting ignored. + + check( + r#" + //- /main.rs crate:main + + mod submod; + + #[unresolved] + struct Foo; + + //- /submod.rs + #[unresolved] + struct Bar; + "#, + expect![[r#" + crate + Foo: t v + submod: t + + crate::submod + Bar: t v + "#]], + ); +} + +#[test] +fn unresolved_attrs_extern_block_hang() { + // Regression test for https://github.com/rust-lang/rust-analyzer/issues/8905 + check( + r#" +#[unresolved] +extern "C" { + #[unresolved] + fn f(); +} + "#, + expect![[r#" + crate + f: v + "#]], + ); +} + +#[test] +fn macros_in_extern_block() { + check( + r#" +macro_rules! m { + () => { static S: u8; }; +} + +extern { + m!(); +} + "#, + expect![[r#" + crate + S: v + "#]], + ); +} + +#[test] +fn resolves_derive_helper() { + cov_mark::check!(resolved_derive_helper); + check( + r#" +//- /main.rs crate:main deps:proc +#[rustc_builtin_macro] +pub macro derive($item:item) {} + +#[derive(proc::Derive)] +#[helper] +#[unresolved] +struct S; + +//- /proc.rs crate:proc +#![crate_type="proc-macro"] +#[proc_macro_derive(Derive, attributes(helper))] +fn derive() {} + "#, + expect![[r#" + crate + S: t v + derive: m + "#]], + ); +} + +#[test] +fn unresolved_attr_with_cfg_attr_hang() { + // Another regression test for https://github.com/rust-lang/rust-analyzer/issues/8905 + check( + r#" +#[cfg_attr(not(off), unresolved, unresolved)] +struct S; + "#, + expect![[r#" + crate + S: t v + "#]], + ); +} + +#[test] +fn macro_expansion_overflow() { + cov_mark::check!(macro_expansion_overflow); + check( + r#" +macro_rules! a { + ($e:expr; $($t:tt)*) => { + b!(static = (); $($t)*); + }; + () => {}; +} + +macro_rules! b { + (static = $e:expr; $($t:tt)*) => { + a!($e; $($t)*); + }; + () => {}; +} + +b! { static = #[] ();} +"#, + expect![[r#" + crate + "#]], + ); +} + +#[test] +fn macros_defining_macros() { + check( + r#" +macro_rules! item { + ($item:item) => { $item } +} + +item! { + macro_rules! indirect_macro { () => { struct S {} } } +} + +indirect_macro!(); + "#, + expect![[r#" + crate + S: t + "#]], + ); +} + +#[test] +fn resolves_proc_macros() { + check( + r#" +#![crate_type="proc-macro"] +struct TokenStream; + +#[proc_macro] +pub fn function_like_macro(args: TokenStream) -> TokenStream { + args +} + +#[proc_macro_attribute] +pub fn attribute_macro(_args: TokenStream, item: TokenStream) -> TokenStream { + item +} + +#[proc_macro_derive(DummyTrait)] +pub fn derive_macro(_item: TokenStream) -> TokenStream { + TokenStream +} + +#[proc_macro_derive(AnotherTrait, attributes(helper_attr))] +pub fn derive_macro_2(_item: TokenStream) -> TokenStream { + TokenStream +} +"#, + expect![[r#" + crate + AnotherTrait: m + DummyTrait: m + TokenStream: t v + attribute_macro: v m + derive_macro: v + derive_macro_2: v + function_like_macro: v m + "#]], + ); +} + +#[test] +fn proc_macro_censoring() { + // Make sure that only proc macros are publicly exported from proc-macro crates. + + check( + r#" +//- /main.rs crate:main deps:macros +pub use macros::*; + +//- /macros.rs crate:macros +#![crate_type="proc-macro"] +pub struct TokenStream; + +#[proc_macro] +pub fn function_like_macro(args: TokenStream) -> TokenStream { + args +} + +#[proc_macro_attribute] +pub fn attribute_macro(_args: TokenStream, item: TokenStream) -> TokenStream { + item +} + +#[proc_macro_derive(DummyTrait)] +pub fn derive_macro(_item: TokenStream) -> TokenStream { + TokenStream +} + +#[macro_export] +macro_rules! mbe { + () => {}; +} +"#, + expect![[r#" + crate + DummyTrait: m + attribute_macro: m + function_like_macro: m + "#]], + ); +} + +#[test] +fn collects_derive_helpers() { + let def_map = compute_crate_def_map( + r#" +#![crate_type="proc-macro"] +struct TokenStream; + +#[proc_macro_derive(AnotherTrait, attributes(helper_attr))] +pub fn derive_macro_2(_item: TokenStream) -> TokenStream { + TokenStream +} +"#, + ); + + assert_eq!(def_map.exported_derives.len(), 1); + match def_map.exported_derives.values().next() { + Some(helpers) => match &**helpers { + [attr] => assert_eq!(attr.to_string(), "helper_attr"), + _ => unreachable!(), + }, + _ => unreachable!(), + } +} + +#[test] +fn resolve_macro_def() { + check( + r#" +pub macro structs($($i:ident),*) { + $(struct $i { field: u32 } )* +} +structs!(Foo); +"#, + expect![[r#" + crate + Foo: t + structs: m + "#]], + ); +} + +#[test] +fn macro_in_prelude() { + check( + r#" +//- /lib.rs crate:lib deps:std +global_asm!(); + +//- /std.rs crate:std +pub mod prelude { + pub mod rust_2018 { + pub macro global_asm() { + pub struct S; + } + } +} + "#, + expect![[r#" + crate + S: t v + "#]], + ) +} + +#[test] +fn issue9358_bad_macro_stack_overflow() { + cov_mark::check!(issue9358_bad_macro_stack_overflow); + check( + r#" +macro_rules! m { + ($cond:expr) => { m!($cond, stringify!($cond)) }; + ($cond:expr, $($arg:tt)*) => { $cond }; +} +m!( +"#, + expect![[r#" + crate + "#]], + ) +} + +#[test] +fn eager_macro_correctly_resolves_contents() { + // Eager macros resolve any contained macros when expanded. This should work correctly with the + // usual name resolution rules, so both of these `include!`s should include the right file. + + check( + r#" +//- /lib.rs +#[rustc_builtin_macro] +macro_rules! include { () => {} } + +include!(inner_a!()); +include!(crate::inner_b!()); + +#[macro_export] +macro_rules! inner_a { + () => { "inc_a.rs" }; +} +#[macro_export] +macro_rules! inner_b { + () => { "inc_b.rs" }; +} +//- /inc_a.rs +struct A; +//- /inc_b.rs +struct B; +"#, + expect![[r#" + crate + A: t v + B: t v + inner_a: m + inner_b: m + "#]], + ); +} + +#[test] +fn eager_macro_correctly_resolves_dollar_crate() { + // MBE -> eager -> $crate::mbe + check( + r#" +//- /lib.rs +#[rustc_builtin_macro] +macro_rules! include { () => {} } + +#[macro_export] +macro_rules! inner { + () => { "inc.rs" }; +} + +macro_rules! m { + () => { include!($crate::inner!()); }; +} + +m!(); + +//- /inc.rs +struct A; +"#, + expect![[r#" + crate + A: t v + inner: m + "#]], + ); + // eager -> MBE -> $crate::mbe + check( + r#" +//- /lib.rs +#[rustc_builtin_macro] +macro_rules! include { () => {} } + +#[macro_export] +macro_rules! inner { + () => { "inc.rs" }; +} + +macro_rules! n { + () => { + $crate::inner!() + }; +} + +include!(n!()); + +//- /inc.rs +struct A; +"#, + expect![[r#" + crate + A: t v + inner: m + "#]], + ); +} + +#[test] +fn macro_use_imports_all_macro_types() { + let def_map = compute_crate_def_map( + r#" +//- /main.rs crate:main deps:lib +#[macro_use] +extern crate lib; + +//- /lib.rs crate:lib deps:proc +pub use proc::*; + +#[macro_export] +macro_rules! legacy { () => () } + +pub macro macro20 {} + +//- /proc.rs crate:proc +#![crate_type="proc-macro"] + +struct TokenStream; + +#[proc_macro_attribute] +fn proc_attr(a: TokenStream, b: TokenStream) -> TokenStream { a } + "#, + ); + + let root = &def_map[def_map.root()].scope; + let actual = root + .legacy_macros() + .sorted_by(|a, b| std::cmp::Ord::cmp(&a.0, &b.0)) + .map(|(name, _)| format!("{name}\n")) + .collect::<String>(); + + expect![[r#" + legacy + macro20 + proc_attr + "#]] + .assert_eq(&actual); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/mod_resolution.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/mod_resolution.rs new file mode 100644 index 000000000..79a74873b --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/mod_resolution.rs @@ -0,0 +1,843 @@ +use super::*; + +#[test] +fn name_res_works_for_broken_modules() { + cov_mark::check!(name_res_works_for_broken_modules); + check( + r" +//- /lib.rs +mod foo // no `;`, no body +use self::foo::Baz; + +//- /foo/mod.rs +pub mod bar; +pub use self::bar::Baz; + +//- /foo/bar.rs +pub struct Baz; +", + expect![[r#" + crate + Baz: _ + foo: t + + crate::foo + "#]], + ); +} + +#[test] +fn nested_module_resolution() { + check( + r#" +//- /lib.rs +mod n1; + +//- /n1.rs +mod n2; + +//- /n1/n2.rs +struct X; +"#, + expect![[r#" + crate + n1: t + + crate::n1 + n2: t + + crate::n1::n2 + X: t v + "#]], + ); +} + +#[test] +fn nested_module_resolution_2() { + check( + r#" +//- /lib.rs +mod prelude; +mod iter; + +//- /prelude.rs +pub use crate::iter::Iterator; + +//- /iter.rs +pub use self::traits::Iterator; +mod traits; + +//- /iter/traits.rs +pub use self::iterator::Iterator; +mod iterator; + +//- /iter/traits/iterator.rs +pub trait Iterator; +"#, + expect![[r#" + crate + iter: t + prelude: t + + crate::iter + Iterator: t + traits: t + + crate::iter::traits + Iterator: t + iterator: t + + crate::iter::traits::iterator + Iterator: t + + crate::prelude + Iterator: t + "#]], + ); +} + +#[test] +fn module_resolution_works_for_non_standard_filenames() { + check( + r#" +//- /my_library.rs crate:my_library +mod foo; +use self::foo::Bar; + +//- /foo/mod.rs +pub struct Bar; +"#, + expect![[r#" + crate + Bar: t v + foo: t + + crate::foo + Bar: t v + "#]], + ); +} + +#[test] +fn module_resolution_works_for_raw_modules() { + check( + r#" +//- /lib.rs +mod r#async; +use self::r#async::Bar; + +//- /async.rs +pub struct Bar; +"#, + expect![[r#" + crate + Bar: t v + async: t + + crate::async + Bar: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_path() { + check( + r#" +//- /lib.rs +#[path = "bar/baz/foo.rs"] +mod foo; +use self::foo::Bar; + +//- /bar/baz/foo.rs +pub struct Bar; +"#, + expect![[r#" + crate + Bar: t v + foo: t + + crate::foo + Bar: t v + "#]], + ); +} + +#[test] +fn module_resolution_module_with_path_in_mod_rs() { + check( + r#" +//- /main.rs +mod foo; + +//- /foo/mod.rs +#[path = "baz.rs"] +pub mod bar; +use self::bar::Baz; + +//- /foo/baz.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + Baz: t v + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_module_with_path_non_crate_root() { + check( + r#" +//- /main.rs +mod foo; + +//- /foo.rs +#[path = "baz.rs"] +pub mod bar; +use self::bar::Baz; + +//- /baz.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + Baz: t v + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_module_decl_path_super() { + check( + r#" +//- /main.rs +#[path = "bar/baz/module.rs"] +mod foo; +pub struct Baz; + +//- /bar/baz/module.rs +use super::Baz; +"#, + expect![[r#" + crate + Baz: t v + foo: t + + crate::foo + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_explicit_path_mod_rs() { + check( + r#" +//- /main.rs +#[path = "module/mod.rs"] +mod foo; + +//- /module/mod.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_relative_path() { + check( + r#" +//- /main.rs +mod foo; + +//- /foo.rs +#[path = "./sub.rs"] +pub mod foo_bar; + +//- /sub.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + foo_bar: t + + crate::foo::foo_bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_relative_path_2() { + check( + r#" +//- /main.rs +mod foo; + +//- /foo/mod.rs +#[path="../sub.rs"] +pub mod foo_bar; + +//- /sub.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + foo_bar: t + + crate::foo::foo_bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_relative_path_outside_root() { + check( + r#" +//- /a/b/c/d/e/main.rs crate:main +#[path="../../../../../outside.rs"] +mod foo; + +//- /outside.rs +mod bar; + +//- /bar.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + bar: t + + crate::foo::bar + Baz: t v +"#]], + ); +} + +#[test] +fn module_resolution_explicit_path_mod_rs_2() { + check( + r#" +//- /main.rs +#[path = "module/bar/mod.rs"] +mod foo; + +//- /module/bar/mod.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_explicit_path_mod_rs_with_win_separator() { + check( + r#" +//- /main.rs +#[path = r"module\bar\mod.rs"] +mod foo; + +//- /module/bar/mod.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_inside_inline_module_with_path_attribute() { + check( + r#" +//- /main.rs +#[path = "models"] +mod foo { mod bar; } + +//- /models/bar.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_inside_inline_module() { + check( + r#" +//- /main.rs +mod foo { mod bar; } + +//- /foo/bar.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_inside_inline_module_2_with_path_attribute() { + check( + r#" +//- /main.rs +#[path = "models/db"] +mod foo { mod bar; } + +//- /models/db/bar.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_inside_inline_module_3() { + check( + r#" +//- /main.rs +#[path = "models/db"] +mod foo { + #[path = "users.rs"] + mod bar; +} + +//- /models/db/users.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_inside_inline_module_empty_path() { + check( + r#" +//- /main.rs +#[path = ""] +mod foo { + #[path = "users.rs"] + mod bar; +} + +//- /users.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_empty_path() { + check( + r#" +//- /main.rs +#[path = ""] // Should try to read `/` (a directory) +mod foo; + +//- /foo.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + "#]], + ); +} + +#[test] +fn module_resolution_decl_inside_inline_module_relative_path() { + check( + r#" +//- /main.rs +#[path = "./models"] +mod foo { mod bar; } + +//- /models/bar.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_inside_inline_module_in_crate_root() { + check( + r#" +//- /main.rs +mod foo { + #[path = "baz.rs"] + mod bar; +} +use self::foo::bar::Baz; + +//- /foo/baz.rs +pub struct Baz; +"#, + expect![[r#" + crate + Baz: t v + foo: t + + crate::foo + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_inside_inline_module_in_mod_rs() { + check( + r#" +//- /main.rs +mod foo; + +//- /foo/mod.rs +mod bar { + #[path = "qwe.rs"] + pub mod baz; +} +use self::bar::baz::Baz; + +//- /foo/bar/qwe.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + Baz: t v + bar: t + + crate::foo::bar + baz: t + + crate::foo::bar::baz + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_inside_inline_module_in_non_crate_root() { + check( + r#" +//- /main.rs +mod foo; + +//- /foo.rs +mod bar { + #[path = "qwe.rs"] + pub mod baz; +} +use self::bar::baz::Baz; + +//- /foo/bar/qwe.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + Baz: t v + bar: t + + crate::foo::bar + baz: t + + crate::foo::bar::baz + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_inside_inline_module_in_non_crate_root_2() { + check( + r#" +//- /main.rs +mod foo; + +//- /foo.rs +#[path = "bar"] +mod bar { + pub mod baz; +} +use self::bar::baz::Baz; + +//- /bar/baz.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + Baz: t v + bar: t + + crate::foo::bar + baz: t + + crate::foo::bar::baz + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_inside_module_in_non_crate_root_2() { + check( + r#" +//- /main.rs +#[path="module/m2.rs"] +mod module; + +//- /module/m2.rs +pub mod submod; + +//- /module/submod.rs +pub struct Baz; +"#, + expect![[r#" + crate + module: t + + crate::module + submod: t + + crate::module::submod + Baz: t v + "#]], + ); +} + +#[test] +fn nested_out_of_line_module() { + check( + r#" +//- /lib.rs +mod a { + mod b { + mod c; + } +} + +//- /a/b/c.rs +struct X; +"#, + expect![[r#" + crate + a: t + + crate::a + b: t + + crate::a::b + c: t + + crate::a::b::c + X: t v + "#]], + ); +} + +#[test] +fn nested_out_of_line_module_with_path() { + check( + r#" +//- /lib.rs +mod a { + #[path = "d/e"] + mod b { + mod c; + } +} + +//- /a/d/e/c.rs +struct X; +"#, + expect![[r#" + crate + a: t + + crate::a + b: t + + crate::a::b + c: t + + crate::a::b::c + X: t v + "#]], + ); +} + +#[test] +fn circular_mods() { + cov_mark::check!(circular_mods); + compute_crate_def_map( + r#" +//- /lib.rs +mod foo; +//- /foo.rs +#[path = "./foo.rs"] +mod foo; +"#, + ); + + compute_crate_def_map( + r#" +//- /lib.rs +mod foo; +//- /foo.rs +#[path = "./bar.rs"] +mod bar; +//- /bar.rs +#[path = "./foo.rs"] +mod foo; +"#, + ); +} + +#[test] +fn abs_path_ignores_local() { + check( + r#" +//- /main.rs crate:main deps:core +pub use ::core::hash::Hash; +pub mod core {} + +//- /lib.rs crate:core +pub mod hash { pub trait Hash {} } +"#, + expect![[r#" + crate + Hash: t + core: t + + crate::core + "#]], + ); +} + +#[test] +fn cfg_in_module_file() { + // Inner `#![cfg]` in a module file makes the whole module disappear. + check( + r#" +//- /main.rs +mod module; + +//- /module.rs +#![cfg(NEVER)] + +struct AlsoShoulntAppear; + "#, + expect![[r#" + crate + "#]], + ) +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/primitives.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/primitives.rs new file mode 100644 index 000000000..215e8952d --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/primitives.rs @@ -0,0 +1,23 @@ +use super::*; + +#[test] +fn primitive_reexport() { + check( + r#" +//- /lib.rs +mod foo; +use foo::int; + +//- /foo.rs +pub use i32 as int; +"#, + expect![[r#" + crate + foo: t + int: t + + crate::foo + int: t + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/path.rs b/src/tools/rust-analyzer/crates/hir-def/src/path.rs new file mode 100644 index 000000000..2f13a9fbf --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/path.rs @@ -0,0 +1,222 @@ +//! A desugared representation of paths like `crate::foo` or `<Type as Trait>::bar`. +mod lower; + +use std::{ + fmt::{self, Display}, + iter, +}; + +use crate::{ + body::LowerCtx, + intern::Interned, + type_ref::{ConstScalarOrPath, LifetimeRef}, +}; +use hir_expand::name::Name; +use syntax::ast; + +use crate::type_ref::{TypeBound, TypeRef}; + +pub use hir_expand::mod_path::{path, ModPath, PathKind}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ImportAlias { + /// Unnamed alias, as in `use Foo as _;` + Underscore, + /// Named alias + Alias(Name), +} + +impl Display for ImportAlias { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ImportAlias::Underscore => f.write_str("_"), + ImportAlias::Alias(name) => f.write_str(&name.to_smol_str()), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Path { + /// Type based path like `<T>::foo`. + /// Note that paths like `<Type as Trait>::foo` are desugard to `Trait::<Self=Type>::foo`. + type_anchor: Option<Interned<TypeRef>>, + mod_path: Interned<ModPath>, + /// Invariant: the same len as `self.mod_path.segments` + generic_args: Box<[Option<Interned<GenericArgs>>]>, +} + +/// Generic arguments to a path segment (e.g. the `i32` in `Option<i32>`). This +/// also includes bindings of associated types, like in `Iterator<Item = Foo>`. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct GenericArgs { + pub args: Vec<GenericArg>, + /// This specifies whether the args contain a Self type as the first + /// element. This is the case for path segments like `<T as Trait>`, where + /// `T` is actually a type parameter for the path `Trait` specifying the + /// Self type. Otherwise, when we have a path `Trait<X, Y>`, the Self type + /// is left out. + pub has_self_type: bool, + /// Associated type bindings like in `Iterator<Item = T>`. + pub bindings: Vec<AssociatedTypeBinding>, + /// Whether these generic args were desugared from `Trait(Arg) -> Output` + /// parenthesis notation typically used for the `Fn` traits. + pub desugared_from_fn: bool, +} + +/// An associated type binding like in `Iterator<Item = T>`. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct AssociatedTypeBinding { + /// The name of the associated type. + pub name: Name, + /// The type bound to this associated type (in `Item = T`, this would be the + /// `T`). This can be `None` if there are bounds instead. + pub type_ref: Option<TypeRef>, + /// Bounds for the associated type, like in `Iterator<Item: + /// SomeOtherTrait>`. (This is the unstable `associated_type_bounds` + /// feature.) + pub bounds: Vec<Interned<TypeBound>>, +} + +/// A single generic argument. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum GenericArg { + Type(TypeRef), + Lifetime(LifetimeRef), + Const(ConstScalarOrPath), +} + +impl Path { + /// Converts an `ast::Path` to `Path`. Works with use trees. + /// It correctly handles `$crate` based path from macro call. + pub fn from_src(path: ast::Path, ctx: &LowerCtx<'_>) -> Option<Path> { + lower::lower_path(path, ctx) + } + + /// Converts a known mod path to `Path`. + pub fn from_known_path( + path: ModPath, + generic_args: impl Into<Box<[Option<Interned<GenericArgs>>]>>, + ) -> Path { + let generic_args = generic_args.into(); + assert_eq!(path.len(), generic_args.len()); + Path { type_anchor: None, mod_path: Interned::new(path), generic_args } + } + + pub fn kind(&self) -> &PathKind { + &self.mod_path.kind + } + + pub fn type_anchor(&self) -> Option<&TypeRef> { + self.type_anchor.as_deref() + } + + pub fn segments(&self) -> PathSegments<'_> { + PathSegments { segments: self.mod_path.segments(), generic_args: &self.generic_args } + } + + pub fn mod_path(&self) -> &ModPath { + &self.mod_path + } + + pub fn qualifier(&self) -> Option<Path> { + if self.mod_path.is_ident() { + return None; + } + let res = Path { + type_anchor: self.type_anchor.clone(), + mod_path: Interned::new(ModPath::from_segments( + self.mod_path.kind, + self.mod_path.segments()[..self.mod_path.segments().len() - 1].iter().cloned(), + )), + generic_args: self.generic_args[..self.generic_args.len() - 1].to_vec().into(), + }; + Some(res) + } + + pub fn is_self_type(&self) -> bool { + self.type_anchor.is_none() && *self.generic_args == [None] && self.mod_path.is_Self() + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct PathSegment<'a> { + pub name: &'a Name, + pub args_and_bindings: Option<&'a GenericArgs>, +} + +pub struct PathSegments<'a> { + segments: &'a [Name], + generic_args: &'a [Option<Interned<GenericArgs>>], +} + +impl<'a> PathSegments<'a> { + pub const EMPTY: PathSegments<'static> = PathSegments { segments: &[], generic_args: &[] }; + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn len(&self) -> usize { + self.segments.len() + } + pub fn first(&self) -> Option<PathSegment<'a>> { + self.get(0) + } + pub fn last(&self) -> Option<PathSegment<'a>> { + self.get(self.len().checked_sub(1)?) + } + pub fn get(&self, idx: usize) -> Option<PathSegment<'a>> { + assert_eq!(self.segments.len(), self.generic_args.len()); + let res = PathSegment { + name: self.segments.get(idx)?, + args_and_bindings: self.generic_args.get(idx).unwrap().as_ref().map(|it| &**it), + }; + Some(res) + } + pub fn skip(&self, len: usize) -> PathSegments<'a> { + assert_eq!(self.segments.len(), self.generic_args.len()); + PathSegments { segments: &self.segments[len..], generic_args: &self.generic_args[len..] } + } + pub fn take(&self, len: usize) -> PathSegments<'a> { + assert_eq!(self.segments.len(), self.generic_args.len()); + PathSegments { segments: &self.segments[..len], generic_args: &self.generic_args[..len] } + } + pub fn iter(&self) -> impl Iterator<Item = PathSegment<'a>> { + self.segments.iter().zip(self.generic_args.iter()).map(|(name, args)| PathSegment { + name, + args_and_bindings: args.as_ref().map(|it| &**it), + }) + } +} + +impl GenericArgs { + pub(crate) fn from_ast( + lower_ctx: &LowerCtx<'_>, + node: ast::GenericArgList, + ) -> Option<GenericArgs> { + lower::lower_generic_args(lower_ctx, node) + } + + pub(crate) fn empty() -> GenericArgs { + GenericArgs { + args: Vec::new(), + has_self_type: false, + bindings: Vec::new(), + desugared_from_fn: false, + } + } +} + +impl From<Name> for Path { + fn from(name: Name) -> Path { + Path { + type_anchor: None, + mod_path: Interned::new(ModPath::from_segments(PathKind::Plain, iter::once(name))), + generic_args: Box::new([None]), + } + } +} + +impl From<Name> for Box<Path> { + fn from(name: Name) -> Box<Path> { + Box::new(Path::from(name)) + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs new file mode 100644 index 000000000..0428f1a39 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs @@ -0,0 +1,230 @@ +//! Transforms syntax into `Path` objects, ideally with accounting for hygiene + +use crate::{intern::Interned, type_ref::ConstScalarOrPath}; + +use either::Either; +use hir_expand::name::{name, AsName}; +use syntax::ast::{self, AstNode, HasTypeBounds}; + +use super::AssociatedTypeBinding; +use crate::{ + body::LowerCtx, + path::{GenericArg, GenericArgs, ModPath, Path, PathKind}, + type_ref::{LifetimeRef, TypeBound, TypeRef}, +}; + +/// Converts an `ast::Path` to `Path`. Works with use trees. +/// It correctly handles `$crate` based path from macro call. +pub(super) fn lower_path(mut path: ast::Path, ctx: &LowerCtx<'_>) -> Option<Path> { + let mut kind = PathKind::Plain; + let mut type_anchor = None; + let mut segments = Vec::new(); + let mut generic_args = Vec::new(); + let hygiene = ctx.hygiene(); + loop { + let segment = path.segment()?; + + if segment.coloncolon_token().is_some() { + kind = PathKind::Abs; + } + + match segment.kind()? { + ast::PathSegmentKind::Name(name_ref) => { + // FIXME: this should just return name + match hygiene.name_ref_to_name(ctx.db.upcast(), name_ref) { + Either::Left(name) => { + let args = segment + .generic_arg_list() + .and_then(|it| lower_generic_args(ctx, it)) + .or_else(|| { + lower_generic_args_from_fn_path( + ctx, + segment.param_list(), + segment.ret_type(), + ) + }) + .map(Interned::new); + segments.push(name); + generic_args.push(args) + } + Either::Right(crate_id) => { + kind = PathKind::DollarCrate(crate_id); + break; + } + } + } + ast::PathSegmentKind::SelfTypeKw => { + segments.push(name![Self]); + generic_args.push(None) + } + ast::PathSegmentKind::Type { type_ref, trait_ref } => { + assert!(path.qualifier().is_none()); // this can only occur at the first segment + + let self_type = TypeRef::from_ast(ctx, type_ref?); + + match trait_ref { + // <T>::foo + None => { + type_anchor = Some(Interned::new(self_type)); + kind = PathKind::Plain; + } + // <T as Trait<A>>::Foo desugars to Trait<Self=T, A>::Foo + Some(trait_ref) => { + let Path { mod_path, generic_args: path_generic_args, .. } = + Path::from_src(trait_ref.path()?, ctx)?; + let num_segments = mod_path.segments().len(); + kind = mod_path.kind; + + segments.extend(mod_path.segments().iter().cloned().rev()); + generic_args.extend(Vec::from(path_generic_args).into_iter().rev()); + + // Insert the type reference (T in the above example) as Self parameter for the trait + let last_segment = + generic_args.iter_mut().rev().nth(num_segments.saturating_sub(1))?; + let mut args_inner = match last_segment { + Some(it) => it.as_ref().clone(), + None => GenericArgs::empty(), + }; + args_inner.has_self_type = true; + args_inner.args.insert(0, GenericArg::Type(self_type)); + *last_segment = Some(Interned::new(args_inner)); + } + } + } + ast::PathSegmentKind::CrateKw => { + kind = PathKind::Crate; + break; + } + ast::PathSegmentKind::SelfKw => { + // don't break out if `self` is the last segment of a path, this mean we got a + // use tree like `foo::{self}` which we want to resolve as `foo` + if !segments.is_empty() { + kind = PathKind::Super(0); + break; + } + } + ast::PathSegmentKind::SuperKw => { + let nested_super_count = if let PathKind::Super(n) = kind { n } else { 0 }; + kind = PathKind::Super(nested_super_count + 1); + } + } + path = match qualifier(&path) { + Some(it) => it, + None => break, + }; + } + segments.reverse(); + generic_args.reverse(); + + if segments.is_empty() && kind == PathKind::Plain && type_anchor.is_none() { + // plain empty paths don't exist, this means we got a single `self` segment as our path + kind = PathKind::Super(0); + } + + // handle local_inner_macros : + // Basically, even in rustc it is quite hacky: + // https://github.com/rust-lang/rust/blob/614f273e9388ddd7804d5cbc80b8865068a3744e/src/librustc_resolve/macros.rs#L456 + // We follow what it did anyway :) + if segments.len() == 1 && kind == PathKind::Plain { + if let Some(_macro_call) = path.syntax().parent().and_then(ast::MacroCall::cast) { + if let Some(crate_id) = hygiene.local_inner_macros(ctx.db.upcast(), path) { + kind = PathKind::DollarCrate(crate_id); + } + } + } + + let mod_path = Interned::new(ModPath::from_segments(kind, segments)); + return Some(Path { type_anchor, mod_path, generic_args: generic_args.into() }); + + fn qualifier(path: &ast::Path) -> Option<ast::Path> { + if let Some(q) = path.qualifier() { + return Some(q); + } + // FIXME: this bottom up traversal is not too precise. + // Should we handle do a top-down analysis, recording results? + let use_tree_list = path.syntax().ancestors().find_map(ast::UseTreeList::cast)?; + let use_tree = use_tree_list.parent_use_tree(); + use_tree.path() + } +} + +pub(super) fn lower_generic_args( + lower_ctx: &LowerCtx<'_>, + node: ast::GenericArgList, +) -> Option<GenericArgs> { + let mut args = Vec::new(); + let mut bindings = Vec::new(); + for generic_arg in node.generic_args() { + match generic_arg { + ast::GenericArg::TypeArg(type_arg) => { + let type_ref = TypeRef::from_ast_opt(lower_ctx, type_arg.ty()); + args.push(GenericArg::Type(type_ref)); + } + ast::GenericArg::AssocTypeArg(assoc_type_arg) => { + if let Some(name_ref) = assoc_type_arg.name_ref() { + let name = name_ref.as_name(); + let type_ref = assoc_type_arg.ty().map(|it| TypeRef::from_ast(lower_ctx, it)); + let bounds = if let Some(l) = assoc_type_arg.type_bound_list() { + l.bounds() + .map(|it| Interned::new(TypeBound::from_ast(lower_ctx, it))) + .collect() + } else { + Vec::new() + }; + bindings.push(AssociatedTypeBinding { name, type_ref, bounds }); + } + } + ast::GenericArg::LifetimeArg(lifetime_arg) => { + if let Some(lifetime) = lifetime_arg.lifetime() { + let lifetime_ref = LifetimeRef::new(&lifetime); + args.push(GenericArg::Lifetime(lifetime_ref)) + } + } + ast::GenericArg::ConstArg(arg) => { + let arg = ConstScalarOrPath::from_expr_opt(arg.expr()); + args.push(GenericArg::Const(arg)) + } + } + } + + if args.is_empty() && bindings.is_empty() { + return None; + } + Some(GenericArgs { args, has_self_type: false, bindings, desugared_from_fn: false }) +} + +/// Collect `GenericArgs` from the parts of a fn-like path, i.e. `Fn(X, Y) +/// -> Z` (which desugars to `Fn<(X, Y), Output=Z>`). +fn lower_generic_args_from_fn_path( + ctx: &LowerCtx<'_>, + params: Option<ast::ParamList>, + ret_type: Option<ast::RetType>, +) -> Option<GenericArgs> { + let mut args = Vec::new(); + let mut bindings = Vec::new(); + let params = params?; + let mut param_types = Vec::new(); + for param in params.params() { + let type_ref = TypeRef::from_ast_opt(ctx, param.ty()); + param_types.push(type_ref); + } + let arg = GenericArg::Type(TypeRef::Tuple(param_types)); + args.push(arg); + if let Some(ret_type) = ret_type { + let type_ref = TypeRef::from_ast_opt(ctx, ret_type.ty()); + bindings.push(AssociatedTypeBinding { + name: name![Output], + type_ref: Some(type_ref), + bounds: Vec::new(), + }); + } else { + // -> () + let type_ref = TypeRef::Tuple(Vec::new()); + bindings.push(AssociatedTypeBinding { + name: name![Output], + type_ref: Some(type_ref), + bounds: Vec::new(), + }); + } + Some(GenericArgs { args, has_self_type: false, bindings, desugared_from_fn: true }) +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/per_ns.rs b/src/tools/rust-analyzer/crates/hir-def/src/per_ns.rs new file mode 100644 index 000000000..bf5bf10c4 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/per_ns.rs @@ -0,0 +1,95 @@ +//! In rust, it is possible to have a value, a type and a macro with the same +//! name without conflicts. +//! +//! `PerNs` (per namespace) captures this. + +use crate::{item_scope::ItemInNs, visibility::Visibility, MacroId, ModuleDefId}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct PerNs { + pub types: Option<(ModuleDefId, Visibility)>, + pub values: Option<(ModuleDefId, Visibility)>, + pub macros: Option<(MacroId, Visibility)>, +} + +impl Default for PerNs { + fn default() -> Self { + PerNs { types: None, values: None, macros: None } + } +} + +impl PerNs { + pub fn none() -> PerNs { + PerNs { types: None, values: None, macros: None } + } + + pub fn values(t: ModuleDefId, v: Visibility) -> PerNs { + PerNs { types: None, values: Some((t, v)), macros: None } + } + + pub fn types(t: ModuleDefId, v: Visibility) -> PerNs { + PerNs { types: Some((t, v)), values: None, macros: None } + } + + pub fn both(types: ModuleDefId, values: ModuleDefId, v: Visibility) -> PerNs { + PerNs { types: Some((types, v)), values: Some((values, v)), macros: None } + } + + pub fn macros(macro_: MacroId, v: Visibility) -> PerNs { + PerNs { types: None, values: None, macros: Some((macro_, v)) } + } + + pub fn is_none(&self) -> bool { + self.types.is_none() && self.values.is_none() && self.macros.is_none() + } + + pub fn take_types(self) -> Option<ModuleDefId> { + self.types.map(|it| it.0) + } + + pub fn take_types_vis(self) -> Option<(ModuleDefId, Visibility)> { + self.types + } + + pub fn take_values(self) -> Option<ModuleDefId> { + self.values.map(|it| it.0) + } + + pub fn take_macros(self) -> Option<MacroId> { + self.macros.map(|it| it.0) + } + + pub fn filter_visibility(self, mut f: impl FnMut(Visibility) -> bool) -> PerNs { + let _p = profile::span("PerNs::filter_visibility"); + PerNs { + types: self.types.filter(|(_, v)| f(*v)), + values: self.values.filter(|(_, v)| f(*v)), + macros: self.macros.filter(|(_, v)| f(*v)), + } + } + + pub fn with_visibility(self, vis: Visibility) -> PerNs { + PerNs { + types: self.types.map(|(it, _)| (it, vis)), + values: self.values.map(|(it, _)| (it, vis)), + macros: self.macros.map(|(it, _)| (it, vis)), + } + } + + pub fn or(self, other: PerNs) -> PerNs { + PerNs { + types: self.types.or(other.types), + values: self.values.or(other.values), + macros: self.macros.or(other.macros), + } + } + + pub fn iter_items(self) -> impl Iterator<Item = ItemInNs> { + let _p = profile::span("PerNs::iter_items"); + self.types + .map(|it| ItemInNs::Types(it.0)) + .into_iter() + .chain(self.values.map(|it| ItemInNs::Values(it.0)).into_iter()) + .chain(self.macros.map(|it| ItemInNs::Macros(it.0)).into_iter()) + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs b/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs new file mode 100644 index 000000000..3163fa0f9 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs @@ -0,0 +1,912 @@ +//! Name resolution façade. +use std::{hash::BuildHasherDefault, sync::Arc}; + +use base_db::CrateId; +use hir_expand::name::{name, Name}; +use indexmap::IndexMap; +use rustc_hash::FxHashSet; +use smallvec::{smallvec, SmallVec}; + +use crate::{ + body::scope::{ExprScopes, ScopeId}, + builtin_type::BuiltinType, + db::DefDatabase, + expr::{ExprId, LabelId, PatId}, + generics::{GenericParams, TypeOrConstParamData}, + intern::Interned, + item_scope::{BuiltinShadowMode, BUILTIN_SCOPE}, + nameres::DefMap, + path::{ModPath, PathKind}, + per_ns::PerNs, + visibility::{RawVisibility, Visibility}, + AdtId, AssocItemId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExternBlockId, + FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, ItemContainerId, LifetimeParamId, + LocalModuleId, Lookup, Macro2Id, MacroId, MacroRulesId, ModuleDefId, ModuleId, ProcMacroId, + StaticId, StructId, TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, VariantId, +}; + +#[derive(Debug, Clone)] +pub struct Resolver { + /// The stack of scopes, where the inner-most scope is the last item. + /// + /// When using, you generally want to process the scopes in reverse order, + /// there's `scopes` *method* for that. + /// + /// Invariant: There exists at least one Scope::ModuleScope at the start of the vec. + scopes: Vec<Scope>, +} + +// FIXME how to store these best +#[derive(Debug, Clone)] +struct ModuleItemMap { + def_map: Arc<DefMap>, + module_id: LocalModuleId, +} + +#[derive(Debug, Clone)] +struct ExprScope { + owner: DefWithBodyId, + expr_scopes: Arc<ExprScopes>, + scope_id: ScopeId, +} + +#[derive(Debug, Clone)] +enum Scope { + /// All the items and imported names of a module + ModuleScope(ModuleItemMap), + /// Brings the generic parameters of an item into scope + GenericParams { def: GenericDefId, params: Interned<GenericParams> }, + /// Brings `Self` in `impl` block into scope + ImplDefScope(ImplId), + /// Brings `Self` in enum, struct and union definitions into scope + AdtScope(AdtId), + /// Local bindings + ExprScope(ExprScope), +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum TypeNs { + SelfType(ImplId), + GenericParam(TypeParamId), + AdtId(AdtId), + AdtSelfType(AdtId), + // Yup, enum variants are added to the types ns, but any usage of variant as + // type is an error. + EnumVariantId(EnumVariantId), + TypeAliasId(TypeAliasId), + BuiltinType(BuiltinType), + TraitId(TraitId), + // Module belong to type ns, but the resolver is used when all module paths + // are fully resolved. + // ModuleId(ModuleId) +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum ResolveValueResult { + ValueNs(ValueNs), + Partial(TypeNs, usize), +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum ValueNs { + ImplSelf(ImplId), + LocalBinding(PatId), + FunctionId(FunctionId), + ConstId(ConstId), + StaticId(StaticId), + StructId(StructId), + EnumVariantId(EnumVariantId), + GenericParam(ConstParamId), +} + +impl Resolver { + /// Resolve known trait from std, like `std::futures::Future` + pub fn resolve_known_trait(&self, db: &dyn DefDatabase, path: &ModPath) -> Option<TraitId> { + let res = self.resolve_module_path(db, path, BuiltinShadowMode::Other).take_types()?; + match res { + ModuleDefId::TraitId(it) => Some(it), + _ => None, + } + } + + /// Resolve known struct from std, like `std::boxed::Box` + pub fn resolve_known_struct(&self, db: &dyn DefDatabase, path: &ModPath) -> Option<StructId> { + let res = self.resolve_module_path(db, path, BuiltinShadowMode::Other).take_types()?; + match res { + ModuleDefId::AdtId(AdtId::StructId(it)) => Some(it), + _ => None, + } + } + + /// Resolve known enum from std, like `std::result::Result` + pub fn resolve_known_enum(&self, db: &dyn DefDatabase, path: &ModPath) -> Option<EnumId> { + let res = self.resolve_module_path(db, path, BuiltinShadowMode::Other).take_types()?; + match res { + ModuleDefId::AdtId(AdtId::EnumId(it)) => Some(it), + _ => None, + } + } + + fn scopes(&self) -> impl Iterator<Item = &Scope> { + self.scopes.iter().rev() + } + + fn resolve_module_path( + &self, + db: &dyn DefDatabase, + path: &ModPath, + shadow: BuiltinShadowMode, + ) -> PerNs { + let (item_map, module) = self.module_scope(); + let (module_res, segment_index) = item_map.resolve_path(db, module, path, shadow); + if segment_index.is_some() { + return PerNs::none(); + } + module_res + } + + pub fn resolve_module_path_in_items(&self, db: &dyn DefDatabase, path: &ModPath) -> PerNs { + self.resolve_module_path(db, path, BuiltinShadowMode::Module) + } + + // FIXME: This shouldn't exist + pub fn resolve_module_path_in_trait_assoc_items( + &self, + db: &dyn DefDatabase, + path: &ModPath, + ) -> Option<PerNs> { + let (item_map, module) = self.module_scope(); + let (module_res, idx) = item_map.resolve_path(db, module, path, BuiltinShadowMode::Module); + match module_res.take_types()? { + ModuleDefId::TraitId(it) => { + let idx = idx?; + let unresolved = &path.segments()[idx..]; + let assoc = match unresolved { + [it] => it, + _ => return None, + }; + let &(_, assoc) = db.trait_data(it).items.iter().find(|(n, _)| n == assoc)?; + Some(match assoc { + AssocItemId::FunctionId(it) => PerNs::values(it.into(), Visibility::Public), + AssocItemId::ConstId(it) => PerNs::values(it.into(), Visibility::Public), + AssocItemId::TypeAliasId(it) => PerNs::types(it.into(), Visibility::Public), + }) + } + _ => None, + } + } + + pub fn resolve_path_in_type_ns( + &self, + db: &dyn DefDatabase, + path: &ModPath, + ) -> Option<(TypeNs, Option<usize>)> { + let first_name = path.segments().first()?; + let skip_to_mod = path.kind != PathKind::Plain; + for scope in self.scopes() { + match scope { + Scope::ExprScope(_) => continue, + Scope::GenericParams { .. } | Scope::ImplDefScope(_) if skip_to_mod => continue, + + Scope::GenericParams { params, def } => { + if let Some(id) = params.find_type_by_name(first_name, *def) { + let idx = if path.segments().len() == 1 { None } else { Some(1) }; + return Some((TypeNs::GenericParam(id), idx)); + } + } + Scope::ImplDefScope(impl_) => { + if first_name == &name![Self] { + let idx = if path.segments().len() == 1 { None } else { Some(1) }; + return Some((TypeNs::SelfType(*impl_), idx)); + } + } + Scope::AdtScope(adt) => { + if first_name == &name![Self] { + let idx = if path.segments().len() == 1 { None } else { Some(1) }; + return Some((TypeNs::AdtSelfType(*adt), idx)); + } + } + Scope::ModuleScope(m) => { + if let Some(res) = m.resolve_path_in_type_ns(db, path) { + return Some(res); + } + } + } + } + None + } + + pub fn resolve_path_in_type_ns_fully( + &self, + db: &dyn DefDatabase, + path: &ModPath, + ) -> Option<TypeNs> { + let (res, unresolved) = self.resolve_path_in_type_ns(db, path)?; + if unresolved.is_some() { + return None; + } + Some(res) + } + + pub fn resolve_visibility( + &self, + db: &dyn DefDatabase, + visibility: &RawVisibility, + ) -> Option<Visibility> { + match visibility { + RawVisibility::Module(_) => { + let (item_map, module) = self.module_scope(); + item_map.resolve_visibility(db, module, visibility) + } + RawVisibility::Public => Some(Visibility::Public), + } + } + + pub fn resolve_path_in_value_ns( + &self, + db: &dyn DefDatabase, + path: &ModPath, + ) -> Option<ResolveValueResult> { + let n_segments = path.segments().len(); + let tmp = name![self]; + let first_name = if path.is_self() { &tmp } else { path.segments().first()? }; + let skip_to_mod = path.kind != PathKind::Plain && !path.is_self(); + for scope in self.scopes() { + match scope { + Scope::AdtScope(_) + | Scope::ExprScope(_) + | Scope::GenericParams { .. } + | Scope::ImplDefScope(_) + if skip_to_mod => + { + continue + } + + Scope::ExprScope(scope) if n_segments <= 1 => { + let entry = scope + .expr_scopes + .entries(scope.scope_id) + .iter() + .find(|entry| entry.name() == first_name); + + if let Some(e) = entry { + return Some(ResolveValueResult::ValueNs(ValueNs::LocalBinding(e.pat()))); + } + } + Scope::ExprScope(_) => continue, + + Scope::GenericParams { params, def } if n_segments > 1 => { + if let Some(id) = params.find_type_by_name(first_name, *def) { + let ty = TypeNs::GenericParam(id); + return Some(ResolveValueResult::Partial(ty, 1)); + } + } + Scope::GenericParams { params, def } if n_segments == 1 => { + if let Some(id) = params.find_const_by_name(first_name, *def) { + let val = ValueNs::GenericParam(id); + return Some(ResolveValueResult::ValueNs(val)); + } + } + Scope::GenericParams { .. } => continue, + + Scope::ImplDefScope(impl_) => { + if first_name == &name![Self] { + if n_segments > 1 { + let ty = TypeNs::SelfType(*impl_); + return Some(ResolveValueResult::Partial(ty, 1)); + } else { + return Some(ResolveValueResult::ValueNs(ValueNs::ImplSelf(*impl_))); + } + } + } + Scope::AdtScope(adt) => { + if n_segments == 1 { + // bare `Self` doesn't work in the value namespace in a struct/enum definition + continue; + } + if first_name == &name![Self] { + let ty = TypeNs::AdtSelfType(*adt); + return Some(ResolveValueResult::Partial(ty, 1)); + } + } + + Scope::ModuleScope(m) => { + if let Some(def) = m.resolve_path_in_value_ns(db, path) { + return Some(def); + } + } + } + } + + // If a path of the shape `u16::from_le_bytes` failed to resolve at all, then we fall back + // to resolving to the primitive type, to allow this to still work in the presence of + // `use core::u16;`. + if path.kind == PathKind::Plain && path.segments().len() > 1 { + match BuiltinType::by_name(&path.segments()[0]) { + Some(builtin) => { + return Some(ResolveValueResult::Partial(TypeNs::BuiltinType(builtin), 1)); + } + None => {} + } + } + + None + } + + pub fn resolve_path_in_value_ns_fully( + &self, + db: &dyn DefDatabase, + path: &ModPath, + ) -> Option<ValueNs> { + match self.resolve_path_in_value_ns(db, path)? { + ResolveValueResult::ValueNs(it) => Some(it), + ResolveValueResult::Partial(..) => None, + } + } + + pub fn resolve_path_as_macro(&self, db: &dyn DefDatabase, path: &ModPath) -> Option<MacroId> { + let (item_map, module) = self.module_scope(); + item_map.resolve_path(db, module, path, BuiltinShadowMode::Other).0.take_macros() + } + + /// Returns a set of names available in the current scope. + /// + /// Note that this is a somewhat fuzzy concept -- internally, the compiler + /// doesn't necessary follow a strict scoping discipline. Rather, it just + /// tells for each ident what it resolves to. + /// + /// A good example is something like `str::from_utf8`. From scopes point of + /// view, this code is erroneous -- both `str` module and `str` type occupy + /// the same type namespace. + /// + /// We don't try to model that super-correctly -- this functionality is + /// primarily exposed for completions. + /// + /// Note that in Rust one name can be bound to several items: + /// + /// ``` + /// macro_rules! t { () => (()) } + /// type t = t!(); + /// const t: t = t!() + /// ``` + /// + /// That's why we return a multimap. + /// + /// The shadowing is accounted for: in + /// + /// ``` + /// let x = 92; + /// { + /// let x = 92; + /// $0 + /// } + /// ``` + /// + /// there will be only one entry for `x` in the result. + /// + /// The result is ordered *roughly* from the innermost scope to the + /// outermost: when the name is introduced in two namespaces in two scopes, + /// we use the position of the first scope. + pub fn names_in_scope( + &self, + db: &dyn DefDatabase, + ) -> FxIndexMap<Name, SmallVec<[ScopeDef; 1]>> { + let mut res = ScopeNames::default(); + for scope in self.scopes() { + scope.process_names(&mut res, db); + } + res.map + } + + pub fn traits_in_scope(&self, db: &dyn DefDatabase) -> FxHashSet<TraitId> { + let mut traits = FxHashSet::default(); + for scope in self.scopes() { + match scope { + Scope::ModuleScope(m) => { + if let Some(prelude) = m.def_map.prelude() { + let prelude_def_map = prelude.def_map(db); + traits.extend(prelude_def_map[prelude.local_id].scope.traits()); + } + traits.extend(m.def_map[m.module_id].scope.traits()); + + // Add all traits that are in scope because of the containing DefMaps + m.def_map.with_ancestor_maps(db, m.module_id, &mut |def_map, module| { + if let Some(prelude) = def_map.prelude() { + let prelude_def_map = prelude.def_map(db); + traits.extend(prelude_def_map[prelude.local_id].scope.traits()); + } + traits.extend(def_map[module].scope.traits()); + None::<()> + }); + } + &Scope::ImplDefScope(impl_) => { + if let Some(target_trait) = &db.impl_data(impl_).target_trait { + if let Some(TypeNs::TraitId(trait_)) = + self.resolve_path_in_type_ns_fully(db, target_trait.path.mod_path()) + { + traits.insert(trait_); + } + } + } + _ => (), + } + } + traits + } + + fn module_scope(&self) -> (&DefMap, LocalModuleId) { + self.scopes() + .find_map(|scope| match scope { + Scope::ModuleScope(m) => Some((&*m.def_map, m.module_id)), + _ => None, + }) + .expect("module scope invariant violated") + } + + pub fn module(&self) -> ModuleId { + let (def_map, local_id) = self.module_scope(); + def_map.module_id(local_id) + } + + pub fn krate(&self) -> CrateId { + self.def_map().krate() + } + + pub fn def_map(&self) -> &DefMap { + self.scopes + .get(0) + .and_then(|scope| match scope { + Scope::ModuleScope(m) => Some(&m.def_map), + _ => None, + }) + .expect("module scope invariant violated") + } + + pub fn where_predicates_in_scope( + &self, + ) -> impl Iterator<Item = &crate::generics::WherePredicate> { + self.scopes() + .filter_map(|scope| match scope { + Scope::GenericParams { params, .. } => Some(params), + _ => None, + }) + .flat_map(|params| params.where_predicates.iter()) + } + + pub fn generic_def(&self) -> Option<GenericDefId> { + self.scopes().find_map(|scope| match scope { + Scope::GenericParams { def, .. } => Some(*def), + _ => None, + }) + } + + pub fn body_owner(&self) -> Option<DefWithBodyId> { + self.scopes().find_map(|scope| match scope { + Scope::ExprScope(it) => Some(it.owner), + _ => None, + }) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ScopeDef { + ModuleDef(ModuleDefId), + Unknown, + ImplSelfType(ImplId), + AdtSelfType(AdtId), + GenericParam(GenericParamId), + Local(PatId), + Label(LabelId), +} + +impl Scope { + fn process_names(&self, acc: &mut ScopeNames, db: &dyn DefDatabase) { + match self { + Scope::ModuleScope(m) => { + // FIXME: should we provide `self` here? + // f( + // Name::self_param(), + // PerNs::types(Resolution::Def { + // def: m.module.into(), + // }), + // ); + m.def_map[m.module_id].scope.entries().for_each(|(name, def)| { + acc.add_per_ns(name, def); + }); + m.def_map[m.module_id].scope.legacy_macros().for_each(|(name, macs)| { + macs.iter().for_each(|&mac| { + acc.add( + name, + ScopeDef::ModuleDef(ModuleDefId::MacroId(MacroId::from(mac))), + ); + }) + }); + m.def_map.extern_prelude().for_each(|(name, &def)| { + acc.add(name, ScopeDef::ModuleDef(ModuleDefId::ModuleId(def))); + }); + BUILTIN_SCOPE.iter().for_each(|(name, &def)| { + acc.add_per_ns(name, def); + }); + if let Some(prelude) = m.def_map.prelude() { + let prelude_def_map = prelude.def_map(db); + for (name, def) in prelude_def_map[prelude.local_id].scope.entries() { + acc.add_per_ns(name, def) + } + } + } + Scope::GenericParams { params, def: parent } => { + let parent = *parent; + for (local_id, param) in params.type_or_consts.iter() { + if let Some(name) = ¶m.name() { + let id = TypeOrConstParamId { parent, local_id }; + let data = &db.generic_params(parent).type_or_consts[local_id]; + acc.add( + name, + ScopeDef::GenericParam(match data { + TypeOrConstParamData::TypeParamData(_) => { + GenericParamId::TypeParamId(TypeParamId::from_unchecked(id)) + } + TypeOrConstParamData::ConstParamData(_) => { + GenericParamId::ConstParamId(ConstParamId::from_unchecked(id)) + } + }), + ); + } + } + for (local_id, param) in params.lifetimes.iter() { + let id = LifetimeParamId { parent, local_id }; + acc.add(¶m.name, ScopeDef::GenericParam(id.into())) + } + } + Scope::ImplDefScope(i) => { + acc.add(&name![Self], ScopeDef::ImplSelfType(*i)); + } + Scope::AdtScope(i) => { + acc.add(&name![Self], ScopeDef::AdtSelfType(*i)); + } + Scope::ExprScope(scope) => { + if let Some((label, name)) = scope.expr_scopes.label(scope.scope_id) { + acc.add(&name, ScopeDef::Label(label)) + } + scope.expr_scopes.entries(scope.scope_id).iter().for_each(|e| { + acc.add_local(e.name(), e.pat()); + }); + } + } + } +} + +// needs arbitrary_self_types to be a method... or maybe move to the def? +pub fn resolver_for_expr(db: &dyn DefDatabase, owner: DefWithBodyId, expr_id: ExprId) -> Resolver { + let scopes = db.expr_scopes(owner); + resolver_for_scope(db, owner, scopes.scope_for(expr_id)) +} + +pub fn resolver_for_scope( + db: &dyn DefDatabase, + owner: DefWithBodyId, + scope_id: Option<ScopeId>, +) -> Resolver { + let mut r = owner.resolver(db); + let scopes = db.expr_scopes(owner); + let scope_chain = scopes.scope_chain(scope_id).collect::<Vec<_>>(); + r.scopes.reserve(scope_chain.len()); + + for scope in scope_chain.into_iter().rev() { + if let Some(block) = scopes.block(scope) { + if let Some(def_map) = db.block_def_map(block) { + let root = def_map.root(); + r = r.push_module_scope(def_map, root); + // FIXME: This adds as many module scopes as there are blocks, but resolving in each + // already traverses all parents, so this is O(n²). I think we could only store the + // innermost module scope instead? + } + } + + r = r.push_expr_scope(owner, Arc::clone(&scopes), scope); + } + r +} + +impl Resolver { + fn push_scope(mut self, scope: Scope) -> Resolver { + self.scopes.push(scope); + self + } + + fn push_generic_params_scope(self, db: &dyn DefDatabase, def: GenericDefId) -> Resolver { + let params = db.generic_params(def); + self.push_scope(Scope::GenericParams { def, params }) + } + + fn push_impl_def_scope(self, impl_def: ImplId) -> Resolver { + self.push_scope(Scope::ImplDefScope(impl_def)) + } + + fn push_module_scope(self, def_map: Arc<DefMap>, module_id: LocalModuleId) -> Resolver { + self.push_scope(Scope::ModuleScope(ModuleItemMap { def_map, module_id })) + } + + fn push_expr_scope( + self, + owner: DefWithBodyId, + expr_scopes: Arc<ExprScopes>, + scope_id: ScopeId, + ) -> Resolver { + self.push_scope(Scope::ExprScope(ExprScope { owner, expr_scopes, scope_id })) + } +} + +impl ModuleItemMap { + fn resolve_path_in_value_ns( + &self, + db: &dyn DefDatabase, + path: &ModPath, + ) -> Option<ResolveValueResult> { + let (module_def, idx) = + self.def_map.resolve_path_locally(db, self.module_id, path, BuiltinShadowMode::Other); + match idx { + None => { + let value = to_value_ns(module_def)?; + Some(ResolveValueResult::ValueNs(value)) + } + Some(idx) => { + let ty = match module_def.take_types()? { + ModuleDefId::AdtId(it) => TypeNs::AdtId(it), + ModuleDefId::TraitId(it) => TypeNs::TraitId(it), + ModuleDefId::TypeAliasId(it) => TypeNs::TypeAliasId(it), + ModuleDefId::BuiltinType(it) => TypeNs::BuiltinType(it), + + ModuleDefId::ModuleId(_) + | ModuleDefId::FunctionId(_) + | ModuleDefId::EnumVariantId(_) + | ModuleDefId::ConstId(_) + | ModuleDefId::MacroId(_) + | ModuleDefId::StaticId(_) => return None, + }; + Some(ResolveValueResult::Partial(ty, idx)) + } + } + } + + fn resolve_path_in_type_ns( + &self, + db: &dyn DefDatabase, + path: &ModPath, + ) -> Option<(TypeNs, Option<usize>)> { + let (module_def, idx) = + self.def_map.resolve_path_locally(db, self.module_id, path, BuiltinShadowMode::Other); + let res = to_type_ns(module_def)?; + Some((res, idx)) + } +} + +fn to_value_ns(per_ns: PerNs) -> Option<ValueNs> { + let res = match per_ns.take_values()? { + ModuleDefId::FunctionId(it) => ValueNs::FunctionId(it), + ModuleDefId::AdtId(AdtId::StructId(it)) => ValueNs::StructId(it), + ModuleDefId::EnumVariantId(it) => ValueNs::EnumVariantId(it), + ModuleDefId::ConstId(it) => ValueNs::ConstId(it), + ModuleDefId::StaticId(it) => ValueNs::StaticId(it), + + ModuleDefId::AdtId(AdtId::EnumId(_) | AdtId::UnionId(_)) + | ModuleDefId::TraitId(_) + | ModuleDefId::TypeAliasId(_) + | ModuleDefId::BuiltinType(_) + | ModuleDefId::MacroId(_) + | ModuleDefId::ModuleId(_) => return None, + }; + Some(res) +} + +fn to_type_ns(per_ns: PerNs) -> Option<TypeNs> { + let res = match per_ns.take_types()? { + ModuleDefId::AdtId(it) => TypeNs::AdtId(it), + ModuleDefId::EnumVariantId(it) => TypeNs::EnumVariantId(it), + + ModuleDefId::TypeAliasId(it) => TypeNs::TypeAliasId(it), + ModuleDefId::BuiltinType(it) => TypeNs::BuiltinType(it), + + ModuleDefId::TraitId(it) => TypeNs::TraitId(it), + + ModuleDefId::FunctionId(_) + | ModuleDefId::ConstId(_) + | ModuleDefId::MacroId(_) + | ModuleDefId::StaticId(_) + | ModuleDefId::ModuleId(_) => return None, + }; + Some(res) +} + +type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<rustc_hash::FxHasher>>; +#[derive(Default)] +struct ScopeNames { + map: FxIndexMap<Name, SmallVec<[ScopeDef; 1]>>, +} + +impl ScopeNames { + fn add(&mut self, name: &Name, def: ScopeDef) { + let set = self.map.entry(name.clone()).or_default(); + if !set.contains(&def) { + set.push(def) + } + } + fn add_per_ns(&mut self, name: &Name, def: PerNs) { + if let &Some((ty, _)) = &def.types { + self.add(name, ScopeDef::ModuleDef(ty)) + } + if let &Some((def, _)) = &def.values { + self.add(name, ScopeDef::ModuleDef(def)) + } + if let &Some((mac, _)) = &def.macros { + self.add(name, ScopeDef::ModuleDef(ModuleDefId::MacroId(mac))) + } + if def.is_none() { + self.add(name, ScopeDef::Unknown) + } + } + fn add_local(&mut self, name: &Name, pat: PatId) { + let set = self.map.entry(name.clone()).or_default(); + // XXX: hack, account for local (and only local) shadowing. + // + // This should be somewhat more principled and take namespaces into + // accounts, but, alas, scoping rules are a hoax. `str` type and `str` + // module can be both available in the same scope. + if set.iter().any(|it| matches!(it, &ScopeDef::Local(_))) { + cov_mark::hit!(shadowing_shows_single_completion); + return; + } + set.push(ScopeDef::Local(pat)) + } +} + +pub trait HasResolver: Copy { + /// Builds a resolver for type references inside this def. + fn resolver(self, db: &dyn DefDatabase) -> Resolver; +} + +impl HasResolver for ModuleId { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + let mut def_map = self.def_map(db); + let mut modules: SmallVec<[_; 2]> = smallvec![(def_map.clone(), self.local_id)]; + while let Some(parent) = def_map.parent() { + def_map = parent.def_map(db); + modules.push((def_map.clone(), parent.local_id)); + } + let mut resolver = Resolver { scopes: Vec::with_capacity(modules.len()) }; + for (def_map, module) in modules.into_iter().rev() { + resolver = resolver.push_module_scope(def_map, module); + } + resolver + } +} + +impl HasResolver for TraitId { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + self.lookup(db).container.resolver(db).push_generic_params_scope(db, self.into()) + } +} + +impl<T: Into<AdtId> + Copy> HasResolver for T { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + let def = self.into(); + def.module(db) + .resolver(db) + .push_generic_params_scope(db, def.into()) + .push_scope(Scope::AdtScope(def)) + } +} + +impl HasResolver for FunctionId { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + self.lookup(db).container.resolver(db).push_generic_params_scope(db, self.into()) + } +} + +impl HasResolver for ConstId { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + self.lookup(db).container.resolver(db) + } +} + +impl HasResolver for StaticId { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + self.lookup(db).container.resolver(db) + } +} + +impl HasResolver for TypeAliasId { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + self.lookup(db).container.resolver(db).push_generic_params_scope(db, self.into()) + } +} + +impl HasResolver for ImplId { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + self.lookup(db) + .container + .resolver(db) + .push_generic_params_scope(db, self.into()) + .push_impl_def_scope(self) + } +} + +impl HasResolver for ExternBlockId { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + // Same as parent's + self.lookup(db).container.resolver(db) + } +} + +impl HasResolver for DefWithBodyId { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + match self { + DefWithBodyId::ConstId(c) => c.resolver(db), + DefWithBodyId::FunctionId(f) => f.resolver(db), + DefWithBodyId::StaticId(s) => s.resolver(db), + } + } +} + +impl HasResolver for ItemContainerId { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + match self { + ItemContainerId::ModuleId(it) => it.resolver(db), + ItemContainerId::TraitId(it) => it.resolver(db), + ItemContainerId::ImplId(it) => it.resolver(db), + ItemContainerId::ExternBlockId(it) => it.resolver(db), + } + } +} + +impl HasResolver for GenericDefId { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + match self { + GenericDefId::FunctionId(inner) => inner.resolver(db), + GenericDefId::AdtId(adt) => adt.resolver(db), + GenericDefId::TraitId(inner) => inner.resolver(db), + GenericDefId::TypeAliasId(inner) => inner.resolver(db), + GenericDefId::ImplId(inner) => inner.resolver(db), + GenericDefId::EnumVariantId(inner) => inner.parent.resolver(db), + GenericDefId::ConstId(inner) => inner.resolver(db), + } + } +} + +impl HasResolver for VariantId { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + match self { + VariantId::EnumVariantId(it) => it.parent.resolver(db), + VariantId::StructId(it) => it.resolver(db), + VariantId::UnionId(it) => it.resolver(db), + } + } +} + +impl HasResolver for MacroId { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + match self { + MacroId::Macro2Id(it) => it.resolver(db), + MacroId::MacroRulesId(it) => it.resolver(db), + MacroId::ProcMacroId(it) => it.resolver(db), + } + } +} + +impl HasResolver for Macro2Id { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + self.lookup(db).container.resolver(db) + } +} + +impl HasResolver for ProcMacroId { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + self.lookup(db).container.resolver(db) + } +} + +impl HasResolver for MacroRulesId { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + self.lookup(db).container.resolver(db) + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/src.rs b/src/tools/rust-analyzer/crates/hir-def/src/src.rs new file mode 100644 index 000000000..f69356cac --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/src.rs @@ -0,0 +1,85 @@ +//! Utilities for mapping between hir IDs and the surface syntax. + +use hir_expand::InFile; +use la_arena::ArenaMap; +use syntax::ast; + +use crate::{ + db::DefDatabase, item_tree::ItemTreeNode, AssocItemLoc, ItemLoc, Macro2Loc, MacroRulesLoc, + ProcMacroLoc, +}; + +pub trait HasSource { + type Value; + fn source(&self, db: &dyn DefDatabase) -> InFile<Self::Value>; +} + +impl<N: ItemTreeNode> HasSource for AssocItemLoc<N> { + type Value = N::Source; + + fn source(&self, db: &dyn DefDatabase) -> InFile<N::Source> { + let tree = self.id.item_tree(db); + let ast_id_map = db.ast_id_map(self.id.file_id()); + let root = db.parse_or_expand(self.id.file_id()).unwrap(); + let node = &tree[self.id.value]; + + InFile::new(self.id.file_id(), ast_id_map.get(node.ast_id()).to_node(&root)) + } +} + +impl<N: ItemTreeNode> HasSource for ItemLoc<N> { + type Value = N::Source; + + fn source(&self, db: &dyn DefDatabase) -> InFile<N::Source> { + let tree = self.id.item_tree(db); + let ast_id_map = db.ast_id_map(self.id.file_id()); + let root = db.parse_or_expand(self.id.file_id()).unwrap(); + let node = &tree[self.id.value]; + + InFile::new(self.id.file_id(), ast_id_map.get(node.ast_id()).to_node(&root)) + } +} + +impl HasSource for Macro2Loc { + type Value = ast::MacroDef; + + fn source(&self, db: &dyn DefDatabase) -> InFile<Self::Value> { + let tree = self.id.item_tree(db); + let ast_id_map = db.ast_id_map(self.id.file_id()); + let root = db.parse_or_expand(self.id.file_id()).unwrap(); + let node = &tree[self.id.value]; + + InFile::new(self.id.file_id(), ast_id_map.get(node.ast_id()).to_node(&root)) + } +} + +impl HasSource for MacroRulesLoc { + type Value = ast::MacroRules; + + fn source(&self, db: &dyn DefDatabase) -> InFile<Self::Value> { + let tree = self.id.item_tree(db); + let ast_id_map = db.ast_id_map(self.id.file_id()); + let root = db.parse_or_expand(self.id.file_id()).unwrap(); + let node = &tree[self.id.value]; + + InFile::new(self.id.file_id(), ast_id_map.get(node.ast_id()).to_node(&root)) + } +} + +impl HasSource for ProcMacroLoc { + type Value = ast::Fn; + + fn source(&self, db: &dyn DefDatabase) -> InFile<Self::Value> { + let tree = self.id.item_tree(db); + let ast_id_map = db.ast_id_map(self.id.file_id()); + let root = db.parse_or_expand(self.id.file_id()).unwrap(); + let node = &tree[self.id.value]; + + InFile::new(self.id.file_id(), ast_id_map.get(node.ast_id()).to_node(&root)) + } +} + +pub trait HasChildSource<ChildId> { + type Value; + fn child_source(&self, db: &dyn DefDatabase) -> InFile<ArenaMap<ChildId, Self::Value>>; +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs b/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs new file mode 100644 index 000000000..9cdc18d6b --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs @@ -0,0 +1,245 @@ +//! Database used for testing `hir_def`. + +use std::{ + fmt, panic, + sync::{Arc, Mutex}, +}; + +use base_db::{ + salsa, AnchoredPath, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition, + SourceDatabase, Upcast, +}; +use hir_expand::{db::AstDatabase, InFile}; +use rustc_hash::FxHashSet; +use syntax::{algo, ast, AstNode}; + +use crate::{ + db::DefDatabase, + nameres::{DefMap, ModuleSource}, + src::HasSource, + LocalModuleId, Lookup, ModuleDefId, ModuleId, +}; + +#[salsa::database( + base_db::SourceDatabaseExtStorage, + base_db::SourceDatabaseStorage, + hir_expand::db::AstDatabaseStorage, + crate::db::InternDatabaseStorage, + crate::db::DefDatabaseStorage +)] +pub(crate) struct TestDB { + storage: salsa::Storage<TestDB>, + events: Mutex<Option<Vec<salsa::Event>>>, +} + +impl Default for TestDB { + fn default() -> Self { + let mut this = Self { storage: Default::default(), events: Default::default() }; + this.set_enable_proc_attr_macros(true); + this + } +} + +impl Upcast<dyn AstDatabase> for TestDB { + fn upcast(&self) -> &(dyn AstDatabase + 'static) { + &*self + } +} + +impl Upcast<dyn DefDatabase> for TestDB { + fn upcast(&self) -> &(dyn DefDatabase + 'static) { + &*self + } +} + +impl salsa::Database for TestDB { + fn salsa_event(&self, event: salsa::Event) { + let mut events = self.events.lock().unwrap(); + if let Some(events) = &mut *events { + events.push(event); + } + } +} + +impl fmt::Debug for TestDB { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TestDB").finish() + } +} + +impl panic::RefUnwindSafe for TestDB {} + +impl FileLoader for TestDB { + fn file_text(&self, file_id: FileId) -> Arc<String> { + FileLoaderDelegate(self).file_text(file_id) + } + fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> { + FileLoaderDelegate(self).resolve_path(path) + } + fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> { + FileLoaderDelegate(self).relevant_crates(file_id) + } +} + +impl TestDB { + pub(crate) fn module_for_file(&self, file_id: FileId) -> ModuleId { + for &krate in self.relevant_crates(file_id).iter() { + let crate_def_map = self.crate_def_map(krate); + for (local_id, data) in crate_def_map.modules() { + if data.origin.file_id() == Some(file_id) { + return crate_def_map.module_id(local_id); + } + } + } + panic!("Can't find module for file") + } + + pub(crate) fn module_at_position(&self, position: FilePosition) -> ModuleId { + let file_module = self.module_for_file(position.file_id); + let mut def_map = file_module.def_map(self); + let module = self.mod_at_position(&def_map, position); + + def_map = match self.block_at_position(&def_map, position) { + Some(it) => it, + None => return def_map.module_id(module), + }; + loop { + let new_map = self.block_at_position(&def_map, position); + match new_map { + Some(new_block) if !Arc::ptr_eq(&new_block, &def_map) => { + def_map = new_block; + } + _ => { + // FIXME: handle `mod` inside block expression + return def_map.module_id(def_map.root()); + } + } + } + } + + /// Finds the smallest/innermost module in `def_map` containing `position`. + fn mod_at_position(&self, def_map: &DefMap, position: FilePosition) -> LocalModuleId { + let mut size = None; + let mut res = def_map.root(); + for (module, data) in def_map.modules() { + let src = data.definition_source(self); + if src.file_id != position.file_id.into() { + continue; + } + + let range = match src.value { + ModuleSource::SourceFile(it) => it.syntax().text_range(), + ModuleSource::Module(it) => it.syntax().text_range(), + ModuleSource::BlockExpr(it) => it.syntax().text_range(), + }; + + if !range.contains(position.offset) { + continue; + } + + let new_size = match size { + None => range.len(), + Some(size) => { + if range.len() < size { + range.len() + } else { + size + } + } + }; + + if size != Some(new_size) { + cov_mark::hit!(submodule_in_testdb); + size = Some(new_size); + res = module; + } + } + + res + } + + fn block_at_position(&self, def_map: &DefMap, position: FilePosition) -> Option<Arc<DefMap>> { + // Find the smallest (innermost) function in `def_map` containing the cursor. + let mut size = None; + let mut fn_def = None; + for (_, module) in def_map.modules() { + let file_id = module.definition_source(self).file_id; + if file_id != position.file_id.into() { + continue; + } + for decl in module.scope.declarations() { + if let ModuleDefId::FunctionId(it) = decl { + let range = it.lookup(self).source(self).value.syntax().text_range(); + + if !range.contains(position.offset) { + continue; + } + + let new_size = match size { + None => range.len(), + Some(size) => { + if range.len() < size { + range.len() + } else { + size + } + } + }; + if size != Some(new_size) { + size = Some(new_size); + fn_def = Some(it); + } + } + } + } + + // Find the innermost block expression that has a `DefMap`. + let def_with_body = fn_def?.into(); + let (_, source_map) = self.body_with_source_map(def_with_body); + let scopes = self.expr_scopes(def_with_body); + let root = self.parse(position.file_id); + + let scope_iter = algo::ancestors_at_offset(&root.syntax_node(), position.offset) + .filter_map(|node| { + let block = ast::BlockExpr::cast(node)?; + let expr = ast::Expr::from(block); + let expr_id = source_map.node_expr(InFile::new(position.file_id.into(), &expr))?; + let scope = scopes.scope_for(expr_id).unwrap(); + Some(scope) + }); + + for scope in scope_iter { + let containing_blocks = + scopes.scope_chain(Some(scope)).filter_map(|scope| scopes.block(scope)); + + for block in containing_blocks { + if let Some(def_map) = self.block_def_map(block) { + return Some(def_map); + } + } + } + + None + } + + pub(crate) fn log(&self, f: impl FnOnce()) -> Vec<salsa::Event> { + *self.events.lock().unwrap() = Some(Vec::new()); + f(); + self.events.lock().unwrap().take().unwrap() + } + + pub(crate) fn log_executed(&self, f: impl FnOnce()) -> Vec<String> { + let events = self.log(f); + events + .into_iter() + .filter_map(|e| match e.kind { + // This is pretty horrible, but `Debug` is the only way to inspect + // QueryDescriptor at the moment. + salsa::EventKind::WillExecute { database_key } => { + Some(format!("{:?}", database_key.debug(self))) + } + _ => None, + }) + .collect() + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/trace.rs b/src/tools/rust-analyzer/crates/hir-def/src/trace.rs new file mode 100644 index 000000000..6e6ceb8e4 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/trace.rs @@ -0,0 +1,51 @@ +//! Trace is a pretty niche data structure which is used when lowering a CST +//! into HIR. +//! +//! Lowering process calculates two bits of information: +//! * the lowered syntax itself +//! * a mapping between lowered syntax and original syntax +//! +//! Due to the way salsa works, the mapping is usually hot lava, as it contains +//! absolute offsets. The `Trace` structure (inspired, at least in name, by +//! Kotlin's `BindingTrace`) allows use the same code to compute both +//! projections. +use la_arena::{Arena, ArenaMap, Idx, RawIdx}; + +pub(crate) struct Trace<T, V> { + arena: Option<Arena<T>>, + map: Option<ArenaMap<Idx<T>, V>>, + len: u32, +} + +impl<T, V> Trace<T, V> { + pub(crate) fn new_for_arena() -> Trace<T, V> { + Trace { arena: Some(Arena::default()), map: None, len: 0 } + } + + pub(crate) fn new_for_map() -> Trace<T, V> { + Trace { arena: None, map: Some(ArenaMap::default()), len: 0 } + } + + pub(crate) fn alloc(&mut self, value: impl FnOnce() -> V, data: impl FnOnce() -> T) -> Idx<T> { + let id = if let Some(arena) = &mut self.arena { + arena.alloc(data()) + } else { + let id = Idx::<T>::from_raw(RawIdx::from(self.len)); + self.len += 1; + id + }; + + if let Some(map) = &mut self.map { + map.insert(id, value()); + } + id + } + + pub(crate) fn into_arena(mut self) -> Arena<T> { + self.arena.take().unwrap() + } + + pub(crate) fn into_map(mut self) -> ArenaMap<Idx<T>, V> { + self.map.take().unwrap() + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/type_ref.rs b/src/tools/rust-analyzer/crates/hir-def/src/type_ref.rs new file mode 100644 index 000000000..924805962 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/type_ref.rs @@ -0,0 +1,486 @@ +//! HIR for references to types. Paths in these are not yet resolved. They can +//! be directly created from an ast::TypeRef, without further queries. + +use std::fmt::Write; + +use hir_expand::{ + name::{AsName, Name}, + AstId, +}; +use syntax::ast::{self, HasName}; + +use crate::{ + body::LowerCtx, + builtin_type::{BuiltinInt, BuiltinType, BuiltinUint}, + expr::Literal, + intern::Interned, + path::Path, +}; + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum Mutability { + Shared, + Mut, +} + +impl Mutability { + pub fn from_mutable(mutable: bool) -> Mutability { + if mutable { + Mutability::Mut + } else { + Mutability::Shared + } + } + + pub fn as_keyword_for_ref(self) -> &'static str { + match self { + Mutability::Shared => "", + Mutability::Mut => "mut ", + } + } + + pub fn as_keyword_for_ptr(self) -> &'static str { + match self { + Mutability::Shared => "const ", + Mutability::Mut => "mut ", + } + } + + /// Returns `true` if the mutability is [`Mut`]. + /// + /// [`Mut`]: Mutability::Mut + #[must_use] + pub fn is_mut(&self) -> bool { + matches!(self, Self::Mut) + } + + /// Returns `true` if the mutability is [`Shared`]. + /// + /// [`Shared`]: Mutability::Shared + #[must_use] + pub fn is_shared(&self) -> bool { + matches!(self, Self::Shared) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum Rawness { + RawPtr, + Ref, +} + +impl Rawness { + pub fn from_raw(is_raw: bool) -> Rawness { + if is_raw { + Rawness::RawPtr + } else { + Rawness::Ref + } + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub struct TraitRef { + pub path: Path, +} + +impl TraitRef { + /// Converts an `ast::PathType` to a `hir::TraitRef`. + pub(crate) fn from_ast(ctx: &LowerCtx<'_>, node: ast::Type) -> Option<Self> { + // FIXME: Use `Path::from_src` + match node { + ast::Type::PathType(path) => { + path.path().and_then(|it| ctx.lower_path(it)).map(|path| TraitRef { path }) + } + _ => None, + } + } +} + +/// Compare ty::Ty +/// +/// Note: Most users of `TypeRef` that end up in the salsa database intern it using +/// `Interned<TypeRef>` to save space. But notably, nested `TypeRef`s are not interned, since that +/// does not seem to save any noticeable amount of memory. +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub enum TypeRef { + Never, + Placeholder, + Tuple(Vec<TypeRef>), + Path(Path), + RawPtr(Box<TypeRef>, Mutability), + Reference(Box<TypeRef>, Option<LifetimeRef>, Mutability), + // FIXME: for full const generics, the latter element (length) here is going to have to be an + // expression that is further lowered later in hir_ty. + Array(Box<TypeRef>, ConstScalarOrPath), + Slice(Box<TypeRef>), + /// A fn pointer. Last element of the vector is the return type. + Fn(Vec<(Option<Name>, TypeRef)>, bool /*varargs*/), + ImplTrait(Vec<Interned<TypeBound>>), + DynTrait(Vec<Interned<TypeBound>>), + Macro(AstId<ast::MacroCall>), + Error, +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub struct LifetimeRef { + pub name: Name, +} + +impl LifetimeRef { + pub(crate) fn new_name(name: Name) -> Self { + LifetimeRef { name } + } + + pub(crate) fn new(lifetime: &ast::Lifetime) -> Self { + LifetimeRef { name: Name::new_lifetime(lifetime) } + } + + pub fn missing() -> LifetimeRef { + LifetimeRef { name: Name::missing() } + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub enum TypeBound { + Path(Path, TraitBoundModifier), + ForLifetime(Box<[Name]>, Path), + Lifetime(LifetimeRef), + Error, +} + +/// A modifier on a bound, currently this is only used for `?Sized`, where the +/// modifier is `Maybe`. +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub enum TraitBoundModifier { + None, + Maybe, +} + +impl TypeRef { + /// Converts an `ast::TypeRef` to a `hir::TypeRef`. + pub fn from_ast(ctx: &LowerCtx<'_>, node: ast::Type) -> Self { + match node { + ast::Type::ParenType(inner) => TypeRef::from_ast_opt(ctx, inner.ty()), + ast::Type::TupleType(inner) => { + TypeRef::Tuple(inner.fields().map(|it| TypeRef::from_ast(ctx, it)).collect()) + } + ast::Type::NeverType(..) => TypeRef::Never, + ast::Type::PathType(inner) => { + // FIXME: Use `Path::from_src` + inner + .path() + .and_then(|it| ctx.lower_path(it)) + .map(TypeRef::Path) + .unwrap_or(TypeRef::Error) + } + ast::Type::PtrType(inner) => { + let inner_ty = TypeRef::from_ast_opt(ctx, inner.ty()); + let mutability = Mutability::from_mutable(inner.mut_token().is_some()); + TypeRef::RawPtr(Box::new(inner_ty), mutability) + } + ast::Type::ArrayType(inner) => { + // FIXME: This is a hack. We should probably reuse the machinery of + // `hir_def::body::lower` to lower this into an `Expr` and then evaluate it at the + // `hir_ty` level, which would allow knowing the type of: + // let v: [u8; 2 + 2] = [0u8; 4]; + let len = ConstScalarOrPath::from_expr_opt(inner.expr()); + TypeRef::Array(Box::new(TypeRef::from_ast_opt(ctx, inner.ty())), len) + } + ast::Type::SliceType(inner) => { + TypeRef::Slice(Box::new(TypeRef::from_ast_opt(ctx, inner.ty()))) + } + ast::Type::RefType(inner) => { + let inner_ty = TypeRef::from_ast_opt(ctx, inner.ty()); + let lifetime = inner.lifetime().map(|lt| LifetimeRef::new(<)); + let mutability = Mutability::from_mutable(inner.mut_token().is_some()); + TypeRef::Reference(Box::new(inner_ty), lifetime, mutability) + } + ast::Type::InferType(_inner) => TypeRef::Placeholder, + ast::Type::FnPtrType(inner) => { + let ret_ty = inner + .ret_type() + .and_then(|rt| rt.ty()) + .map(|it| TypeRef::from_ast(ctx, it)) + .unwrap_or_else(|| TypeRef::Tuple(Vec::new())); + let mut is_varargs = false; + let mut params = if let Some(pl) = inner.param_list() { + if let Some(param) = pl.params().last() { + is_varargs = param.dotdotdot_token().is_some(); + } + + pl.params() + .map(|it| { + let type_ref = TypeRef::from_ast_opt(ctx, it.ty()); + let name = match it.pat() { + Some(ast::Pat::IdentPat(it)) => Some( + it.name().map(|nr| nr.as_name()).unwrap_or_else(Name::missing), + ), + _ => None, + }; + (name, type_ref) + }) + .collect() + } else { + Vec::new() + }; + params.push((None, ret_ty)); + TypeRef::Fn(params, is_varargs) + } + // for types are close enough for our purposes to the inner type for now... + ast::Type::ForType(inner) => TypeRef::from_ast_opt(ctx, inner.ty()), + ast::Type::ImplTraitType(inner) => { + TypeRef::ImplTrait(type_bounds_from_ast(ctx, inner.type_bound_list())) + } + ast::Type::DynTraitType(inner) => { + TypeRef::DynTrait(type_bounds_from_ast(ctx, inner.type_bound_list())) + } + ast::Type::MacroType(mt) => match mt.macro_call() { + Some(mc) => ctx.ast_id(ctx.db, &mc).map(TypeRef::Macro).unwrap_or(TypeRef::Error), + None => TypeRef::Error, + }, + } + } + + pub(crate) fn from_ast_opt(ctx: &LowerCtx<'_>, node: Option<ast::Type>) -> Self { + match node { + Some(node) => TypeRef::from_ast(ctx, node), + None => TypeRef::Error, + } + } + + pub(crate) fn unit() -> TypeRef { + TypeRef::Tuple(Vec::new()) + } + + pub fn walk(&self, f: &mut impl FnMut(&TypeRef)) { + go(self, f); + + fn go(type_ref: &TypeRef, f: &mut impl FnMut(&TypeRef)) { + f(type_ref); + match type_ref { + TypeRef::Fn(params, _) => { + params.iter().for_each(|(_, param_type)| go(param_type, f)) + } + TypeRef::Tuple(types) => types.iter().for_each(|t| go(t, f)), + TypeRef::RawPtr(type_ref, _) + | TypeRef::Reference(type_ref, ..) + | TypeRef::Array(type_ref, _) + | TypeRef::Slice(type_ref) => go(type_ref, f), + TypeRef::ImplTrait(bounds) | TypeRef::DynTrait(bounds) => { + for bound in bounds { + match bound.as_ref() { + TypeBound::Path(path, _) | TypeBound::ForLifetime(_, path) => { + go_path(path, f) + } + TypeBound::Lifetime(_) | TypeBound::Error => (), + } + } + } + TypeRef::Path(path) => go_path(path, f), + TypeRef::Never | TypeRef::Placeholder | TypeRef::Macro(_) | TypeRef::Error => {} + }; + } + + fn go_path(path: &Path, f: &mut impl FnMut(&TypeRef)) { + if let Some(type_ref) = path.type_anchor() { + go(type_ref, f); + } + for segment in path.segments().iter() { + if let Some(args_and_bindings) = segment.args_and_bindings { + for arg in &args_and_bindings.args { + match arg { + crate::path::GenericArg::Type(type_ref) => { + go(type_ref, f); + } + crate::path::GenericArg::Const(_) + | crate::path::GenericArg::Lifetime(_) => {} + } + } + for binding in &args_and_bindings.bindings { + if let Some(type_ref) = &binding.type_ref { + go(type_ref, f); + } + for bound in &binding.bounds { + match bound.as_ref() { + TypeBound::Path(path, _) | TypeBound::ForLifetime(_, path) => { + go_path(path, f) + } + TypeBound::Lifetime(_) | TypeBound::Error => (), + } + } + } + } + } + } + } +} + +pub(crate) fn type_bounds_from_ast( + lower_ctx: &LowerCtx<'_>, + type_bounds_opt: Option<ast::TypeBoundList>, +) -> Vec<Interned<TypeBound>> { + if let Some(type_bounds) = type_bounds_opt { + type_bounds.bounds().map(|it| Interned::new(TypeBound::from_ast(lower_ctx, it))).collect() + } else { + vec![] + } +} + +impl TypeBound { + pub(crate) fn from_ast(ctx: &LowerCtx<'_>, node: ast::TypeBound) -> Self { + let lower_path_type = |path_type: ast::PathType| ctx.lower_path(path_type.path()?); + + match node.kind() { + ast::TypeBoundKind::PathType(path_type) => { + let m = match node.question_mark_token() { + Some(_) => TraitBoundModifier::Maybe, + None => TraitBoundModifier::None, + }; + lower_path_type(path_type) + .map(|p| TypeBound::Path(p, m)) + .unwrap_or(TypeBound::Error) + } + ast::TypeBoundKind::ForType(for_type) => { + let lt_refs = match for_type.generic_param_list() { + Some(gpl) => gpl + .lifetime_params() + .flat_map(|lp| lp.lifetime().map(|lt| Name::new_lifetime(<))) + .collect(), + None => Box::default(), + }; + let path = for_type.ty().and_then(|ty| match ty { + ast::Type::PathType(path_type) => lower_path_type(path_type), + _ => None, + }); + match path { + Some(p) => TypeBound::ForLifetime(lt_refs, p), + None => TypeBound::Error, + } + } + ast::TypeBoundKind::Lifetime(lifetime) => { + TypeBound::Lifetime(LifetimeRef::new(&lifetime)) + } + } + } + + pub fn as_path(&self) -> Option<(&Path, &TraitBoundModifier)> { + match self { + TypeBound::Path(p, m) => Some((p, m)), + TypeBound::ForLifetime(_, p) => Some((p, &TraitBoundModifier::None)), + TypeBound::Lifetime(_) | TypeBound::Error => None, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum ConstScalarOrPath { + Scalar(ConstScalar), + Path(Name), +} + +impl std::fmt::Display for ConstScalarOrPath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ConstScalarOrPath::Scalar(s) => s.fmt(f), + ConstScalarOrPath::Path(n) => n.fmt(f), + } + } +} + +impl ConstScalarOrPath { + pub(crate) fn from_expr_opt(expr: Option<ast::Expr>) -> Self { + match expr { + Some(x) => Self::from_expr(x), + None => Self::Scalar(ConstScalar::Unknown), + } + } + + // FIXME: as per the comments on `TypeRef::Array`, this evaluation should not happen at this + // parse stage. + fn from_expr(expr: ast::Expr) -> Self { + match expr { + ast::Expr::PathExpr(p) => { + match p.path().and_then(|x| x.segment()).and_then(|x| x.name_ref()) { + Some(x) => Self::Path(x.as_name()), + None => Self::Scalar(ConstScalar::Unknown), + } + } + ast::Expr::PrefixExpr(prefix_expr) => match prefix_expr.op_kind() { + Some(ast::UnaryOp::Neg) => { + let unsigned = Self::from_expr_opt(prefix_expr.expr()); + // Add sign + match unsigned { + Self::Scalar(ConstScalar::UInt(num)) => { + Self::Scalar(ConstScalar::Int(-(num as i128))) + } + other => other, + } + } + _ => Self::from_expr_opt(prefix_expr.expr()), + }, + ast::Expr::Literal(literal) => Self::Scalar(match literal.kind() { + ast::LiteralKind::IntNumber(num) => { + num.value().map(ConstScalar::UInt).unwrap_or(ConstScalar::Unknown) + } + ast::LiteralKind::Char(c) => { + c.value().map(ConstScalar::Char).unwrap_or(ConstScalar::Unknown) + } + ast::LiteralKind::Bool(f) => ConstScalar::Bool(f), + _ => ConstScalar::Unknown, + }), + _ => Self::Scalar(ConstScalar::Unknown), + } + } +} + +/// A concrete constant value +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ConstScalar { + Int(i128), + UInt(u128), + Bool(bool), + Char(char), + + /// Case of an unknown value that rustc might know but we don't + // FIXME: this is a hack to get around chalk not being able to represent unevaluatable + // constants + // https://github.com/rust-lang/rust-analyzer/pull/8813#issuecomment-840679177 + // https://rust-lang.zulipchat.com/#narrow/stream/144729-wg-traits/topic/Handling.20non.20evaluatable.20constants'.20equality/near/238386348 + Unknown, +} + +impl ConstScalar { + pub fn builtin_type(&self) -> BuiltinType { + match self { + ConstScalar::UInt(_) | ConstScalar::Unknown => BuiltinType::Uint(BuiltinUint::U128), + ConstScalar::Int(_) => BuiltinType::Int(BuiltinInt::I128), + ConstScalar::Char(_) => BuiltinType::Char, + ConstScalar::Bool(_) => BuiltinType::Bool, + } + } +} + +impl From<Literal> for ConstScalar { + fn from(literal: Literal) -> Self { + match literal { + Literal::Char(c) => Self::Char(c), + Literal::Bool(flag) => Self::Bool(flag), + Literal::Int(num, _) => Self::Int(num), + Literal::Uint(num, _) => Self::UInt(num), + _ => Self::Unknown, + } + } +} + +impl std::fmt::Display for ConstScalar { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + ConstScalar::Int(num) => num.fmt(f), + ConstScalar::UInt(num) => num.fmt(f), + ConstScalar::Bool(flag) => flag.fmt(f), + ConstScalar::Char(c) => write!(f, "'{c}'"), + ConstScalar::Unknown => f.write_char('_'), + } + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs b/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs new file mode 100644 index 000000000..6e22a877a --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs @@ -0,0 +1,242 @@ +//! Defines hir-level representation of visibility (e.g. `pub` and `pub(crate)`). + +use std::{iter, sync::Arc}; + +use hir_expand::{hygiene::Hygiene, InFile}; +use la_arena::ArenaMap; +use syntax::ast; + +use crate::{ + db::DefDatabase, + nameres::DefMap, + path::{ModPath, PathKind}, + resolver::HasResolver, + ConstId, FunctionId, HasModule, LocalFieldId, ModuleId, VariantId, +}; + +/// Visibility of an item, not yet resolved. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum RawVisibility { + /// `pub(in module)`, `pub(crate)` or `pub(super)`. Also private, which is + /// equivalent to `pub(self)`. + Module(ModPath), + /// `pub`. + Public, +} + +impl RawVisibility { + pub(crate) const fn private() -> RawVisibility { + RawVisibility::Module(ModPath::from_kind(PathKind::Super(0))) + } + + pub(crate) fn from_ast( + db: &dyn DefDatabase, + node: InFile<Option<ast::Visibility>>, + ) -> RawVisibility { + Self::from_ast_with_hygiene(db, node.value, &Hygiene::new(db.upcast(), node.file_id)) + } + + pub(crate) fn from_ast_with_hygiene( + db: &dyn DefDatabase, + node: Option<ast::Visibility>, + hygiene: &Hygiene, + ) -> RawVisibility { + Self::from_ast_with_hygiene_and_default(db, node, RawVisibility::private(), hygiene) + } + + pub(crate) fn from_ast_with_hygiene_and_default( + db: &dyn DefDatabase, + node: Option<ast::Visibility>, + default: RawVisibility, + hygiene: &Hygiene, + ) -> RawVisibility { + let node = match node { + None => return default, + Some(node) => node, + }; + match node.kind() { + ast::VisibilityKind::In(path) => { + let path = ModPath::from_src(db.upcast(), path, hygiene); + let path = match path { + None => return RawVisibility::private(), + Some(path) => path, + }; + RawVisibility::Module(path) + } + ast::VisibilityKind::PubCrate => { + let path = ModPath::from_kind(PathKind::Crate); + RawVisibility::Module(path) + } + ast::VisibilityKind::PubSuper => { + let path = ModPath::from_kind(PathKind::Super(1)); + RawVisibility::Module(path) + } + ast::VisibilityKind::PubSelf => { + let path = ModPath::from_kind(PathKind::Plain); + RawVisibility::Module(path) + } + ast::VisibilityKind::Pub => RawVisibility::Public, + } + } + + pub fn resolve( + &self, + db: &dyn DefDatabase, + resolver: &crate::resolver::Resolver, + ) -> Visibility { + // we fall back to public visibility (i.e. fail open) if the path can't be resolved + resolver.resolve_visibility(db, self).unwrap_or(Visibility::Public) + } +} + +/// Visibility of an item, with the path resolved. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum Visibility { + /// Visibility is restricted to a certain module. + Module(ModuleId), + /// Visibility is unrestricted. + Public, +} + +impl Visibility { + pub fn is_visible_from(self, db: &dyn DefDatabase, from_module: ModuleId) -> bool { + let to_module = match self { + Visibility::Module(m) => m, + Visibility::Public => return true, + }; + // if they're not in the same crate, it can't be visible + if from_module.krate != to_module.krate { + return false; + } + let def_map = from_module.def_map(db); + self.is_visible_from_def_map(db, &def_map, from_module.local_id) + } + + pub(crate) fn is_visible_from_other_crate(self) -> bool { + matches!(self, Visibility::Public) + } + + pub(crate) fn is_visible_from_def_map( + self, + db: &dyn DefDatabase, + def_map: &DefMap, + mut from_module: crate::LocalModuleId, + ) -> bool { + let mut to_module = match self { + Visibility::Module(m) => m, + Visibility::Public => return true, + }; + + // `to_module` might be the root module of a block expression. Those have the same + // visibility as the containing module (even though no items are directly nameable from + // there, getting this right is important for method resolution). + // In that case, we adjust the visibility of `to_module` to point to the containing module. + // Additional complication: `to_module` might be in `from_module`'s `DefMap`, which we're + // currently computing, so we must not call the `def_map` query for it. + let arc; + let to_module_def_map = + if to_module.krate == def_map.krate() && to_module.block == def_map.block_id() { + cov_mark::hit!(is_visible_from_same_block_def_map); + def_map + } else { + arc = to_module.def_map(db); + &arc + }; + let is_block_root = matches!(to_module.block, Some(_) if to_module_def_map[to_module.local_id].parent.is_none()); + if is_block_root { + to_module = to_module_def_map.containing_module(to_module.local_id).unwrap(); + } + + // from_module needs to be a descendant of to_module + let mut def_map = def_map; + let mut parent_arc; + loop { + if def_map.module_id(from_module) == to_module { + return true; + } + match def_map[from_module].parent { + Some(parent) => from_module = parent, + None => { + match def_map.parent() { + Some(module) => { + parent_arc = module.def_map(db); + def_map = &*parent_arc; + from_module = module.local_id; + } + // Reached the root module, nothing left to check. + None => return false, + } + } + } + } + } + + /// Returns the most permissive visibility of `self` and `other`. + /// + /// If there is no subset relation between `self` and `other`, returns `None` (ie. they're only + /// visible in unrelated modules). + pub(crate) fn max(self, other: Visibility, def_map: &DefMap) -> Option<Visibility> { + match (self, other) { + (Visibility::Module(_) | Visibility::Public, Visibility::Public) + | (Visibility::Public, Visibility::Module(_)) => Some(Visibility::Public), + (Visibility::Module(mod_a), Visibility::Module(mod_b)) => { + if mod_a.krate != mod_b.krate { + return None; + } + + let mut a_ancestors = iter::successors(Some(mod_a.local_id), |&m| { + let parent_id = def_map[m].parent?; + Some(parent_id) + }); + let mut b_ancestors = iter::successors(Some(mod_b.local_id), |&m| { + let parent_id = def_map[m].parent?; + Some(parent_id) + }); + + if a_ancestors.any(|m| m == mod_b.local_id) { + // B is above A + return Some(Visibility::Module(mod_b)); + } + + if b_ancestors.any(|m| m == mod_a.local_id) { + // A is above B + return Some(Visibility::Module(mod_a)); + } + + None + } + } + } +} + +/// Resolve visibility of all specific fields of a struct or union variant. +pub(crate) fn field_visibilities_query( + db: &dyn DefDatabase, + variant_id: VariantId, +) -> Arc<ArenaMap<LocalFieldId, Visibility>> { + let var_data = match variant_id { + VariantId::StructId(it) => db.struct_data(it).variant_data.clone(), + VariantId::UnionId(it) => db.union_data(it).variant_data.clone(), + VariantId::EnumVariantId(it) => { + db.enum_data(it.parent).variants[it.local_id].variant_data.clone() + } + }; + let resolver = variant_id.module(db).resolver(db); + let mut res = ArenaMap::default(); + for (field_id, field_data) in var_data.fields().iter() { + res.insert(field_id, field_data.visibility.resolve(db, &resolver)) + } + Arc::new(res) +} + +/// Resolve visibility of a function. +pub(crate) fn function_visibility_query(db: &dyn DefDatabase, def: FunctionId) -> Visibility { + let resolver = def.resolver(db); + db.function_data(def).visibility.resolve(db, &resolver) +} + +/// Resolve visibility of a const. +pub(crate) fn const_visibility_query(db: &dyn DefDatabase, def: ConstId) -> Visibility { + let resolver = def.resolver(db); + db.const_data(def).visibility.resolve(db, &resolver) +} |