From 698f8c2f01ea549d77d7dc3338a12e04c11057b9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:02:58 +0200 Subject: Adding upstream version 1.64.0+dfsg1. Signed-off-by: Daniel Baumann --- src/tools/rust-analyzer/crates/hir-def/Cargo.toml | 43 + src/tools/rust-analyzer/crates/hir-def/src/adt.rs | 365 ++++ src/tools/rust-analyzer/crates/hir-def/src/attr.rs | 1002 +++++++++ src/tools/rust-analyzer/crates/hir-def/src/body.rs | 471 +++++ .../rust-analyzer/crates/hir-def/src/body/lower.rs | 1023 +++++++++ .../rust-analyzer/crates/hir-def/src/body/scope.rs | 571 +++++ .../rust-analyzer/crates/hir-def/src/body/tests.rs | 127 ++ .../crates/hir-def/src/body/tests/block.rs | 397 ++++ .../crates/hir-def/src/builtin_attr.rs | 654 ++++++ .../crates/hir-def/src/builtin_type.rs | 158 ++ .../crates/hir-def/src/child_by_source.rs | 207 ++ src/tools/rust-analyzer/crates/hir-def/src/data.rs | 579 +++++ src/tools/rust-analyzer/crates/hir-def/src/db.rs | 243 +++ .../rust-analyzer/crates/hir-def/src/dyn_map.rs | 116 ++ src/tools/rust-analyzer/crates/hir-def/src/expr.rs | 444 ++++ .../rust-analyzer/crates/hir-def/src/find_path.rs | 1134 ++++++++++ .../rust-analyzer/crates/hir-def/src/generics.rs | 522 +++++ .../rust-analyzer/crates/hir-def/src/import_map.rs | 1108 ++++++++++ .../rust-analyzer/crates/hir-def/src/intern.rs | 227 ++ .../rust-analyzer/crates/hir-def/src/item_scope.rs | 464 +++++ .../rust-analyzer/crates/hir-def/src/item_tree.rs | 961 +++++++++ .../crates/hir-def/src/item_tree/lower.rs | 773 +++++++ .../crates/hir-def/src/item_tree/pretty.rs | 754 +++++++ .../crates/hir-def/src/item_tree/tests.rs | 360 ++++ src/tools/rust-analyzer/crates/hir-def/src/keys.rs | 70 + .../rust-analyzer/crates/hir-def/src/lang_item.rs | 174 ++ src/tools/rust-analyzer/crates/hir-def/src/lib.rs | 980 +++++++++ .../crates/hir-def/src/macro_expansion_tests.rs | 354 ++++ .../macro_expansion_tests/builtin_derive_macro.rs | 95 + .../src/macro_expansion_tests/builtin_fn_macro.rs | 377 ++++ .../hir-def/src/macro_expansion_tests/mbe.rs | 1632 +++++++++++++++ .../src/macro_expansion_tests/mbe/matching.rs | 138 ++ .../src/macro_expansion_tests/mbe/meta_syntax.rs | 154 ++ .../src/macro_expansion_tests/mbe/regression.rs | 911 ++++++++ .../src/macro_expansion_tests/mbe/tt_conversion.rs | 200 ++ .../src/macro_expansion_tests/proc_macros.rs | 130 ++ .../rust-analyzer/crates/hir-def/src/nameres.rs | 545 +++++ .../crates/hir-def/src/nameres/attr_resolution.rs | 98 + .../crates/hir-def/src/nameres/collector.rs | 2202 ++++++++++++++++++++ .../crates/hir-def/src/nameres/diagnostics.rs | 137 ++ .../crates/hir-def/src/nameres/mod_resolution.rs | 161 ++ .../crates/hir-def/src/nameres/path_resolution.rs | 448 ++++ .../crates/hir-def/src/nameres/proc_macro.rs | 81 + .../crates/hir-def/src/nameres/tests.rs | 933 +++++++++ .../crates/hir-def/src/nameres/tests/globs.rs | 338 +++ .../hir-def/src/nameres/tests/incremental.rs | 237 +++ .../crates/hir-def/src/nameres/tests/macros.rs | 1187 +++++++++++ .../hir-def/src/nameres/tests/mod_resolution.rs | 843 ++++++++ .../crates/hir-def/src/nameres/tests/primitives.rs | 23 + src/tools/rust-analyzer/crates/hir-def/src/path.rs | 222 ++ .../rust-analyzer/crates/hir-def/src/path/lower.rs | 230 ++ .../rust-analyzer/crates/hir-def/src/per_ns.rs | 95 + .../rust-analyzer/crates/hir-def/src/resolver.rs | 912 ++++++++ src/tools/rust-analyzer/crates/hir-def/src/src.rs | 85 + .../rust-analyzer/crates/hir-def/src/test_db.rs | 245 +++ .../rust-analyzer/crates/hir-def/src/trace.rs | 51 + .../rust-analyzer/crates/hir-def/src/type_ref.rs | 486 +++++ .../rust-analyzer/crates/hir-def/src/visibility.rs | 242 +++ 58 files changed, 27719 insertions(+) create mode 100644 src/tools/rust-analyzer/crates/hir-def/Cargo.toml create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/adt.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/attr.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/body.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/body/scope.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/body/tests.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/builtin_attr.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/builtin_type.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/data.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/db.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/expr.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/find_path.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/generics.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/import_map.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/intern.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/item_tree/tests.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/keys.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/lib.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/tt_conversion.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/proc_macros.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/nameres.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/nameres/attr_resolution.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/nameres/diagnostics.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/nameres/mod_resolution.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/nameres/proc_macro.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/globs.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/mod_resolution.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/primitives.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/path.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/per_ns.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/resolver.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/src.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/test_db.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/trace.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/type_ref.rs create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/visibility.rs (limited to 'src/tools/rust-analyzer/crates/hir-def') 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, + pub repr: Option, + pub visibility: RawVisibility, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EnumData { + pub name: Name, + pub variants: Arena, + pub visibility: RawVisibility, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EnumVariantData { + pub name: Name, + pub variant_data: Arc, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum VariantData { + Record(Arena), + Tuple(Arena), + 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, + 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 { + item_tree.attrs(db, krate, of).by_key("repr").tt_values().find_map(parse_repr_tt) +} + +fn parse_repr_tt(tt: &Subtree) -> Option { + 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 { + 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 { + 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 { + 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 { + let (id, _) = self.variants.iter().find(|(_id, data)| &data.name == name)?; + Some(id) + } +} + +impl HasChildSource for EnumId { + type Value = ast::Variant; + fn child_source( + &self, + db: &dyn DefDatabase, + ) -> InFile> { + 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, + ast: &InFile, + 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, 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 { + const EMPTY: &Arena = &Arena::new(); + match &self { + VariantData::Record(fields) | VariantData::Tuple(fields) => fields, + _ => EMPTY, + } + } + + pub fn field(&self, name: &Name) -> Option { + 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 for VariantId { + type Value = Either; + + fn child_source(&self, db: &dyn DefDatabase) -> InFile> { + 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>, + ast: &InFile, +) -> 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, +) -> 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, +) -> 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 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>, +} + +#[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::>(); + + 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> { + // 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> { + // 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 { + 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 { + 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>> { + 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>, + 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> { + self.source_of_id(attr.id) + } + + fn source_of_id(&self, id: AttrId) -> InFile<&Either> { + 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> { + 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 { + 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, + pub(crate) input: Option>, +} + +#[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 { + 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 { + 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 + '_> { + 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 { + 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 + 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(id: ItemTreeId, 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)> { + 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>> { + 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, AstPtr>>> { + 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, + 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( + &mut self, + db: &dyn DefDatabase, + macro_call: ast::MacroCall, + ) -> Result>, 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( + &mut self, + db: &dyn DefDatabase, + call_id: MacroCallId, + ) -> ExpandResult> { + self.enter_expand_inner(db, call_id, None) + } + + fn enter_expand_inner( + &mut self, + db: &dyn DefDatabase, + call_id: MacroCallId, + mut err: Option, + ) -> ExpandResult> { + 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(&self, value: T) -> InFile { + 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 { + 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 { + 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, + pub pats: Arena, + pub or_pats: FxHashMap>, + pub labels: Arena