diff --git a/Cargo.lock b/Cargo.lock index f72e698f6063..6b5f901f8a77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -767,6 +767,7 @@ dependencies = [ name = "hir-expand" version = "0.0.0" dependencies = [ + "arrayvec", "base-db", "cfg", "cov-mark", @@ -785,6 +786,7 @@ dependencies = [ "stdx", "syntax", "syntax-bridge", + "thin-vec", "tracing", "triomphe", "tt", @@ -2040,9 +2042,9 @@ dependencies = [ [[package]] name = "rowan" -version = "0.15.15" +version = "0.15.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a58fa8a7ccff2aec4f39cc45bf5f985cec7125ab271cf681c279fd00192b49" +checksum = "d4f1e4a001f863f41ea8d0e6a0c34b356d5b733db50dadab3efef640bafb779b" dependencies = [ "countme", "hashbrown 0.14.5", diff --git a/Cargo.toml b/Cargo.toml index bb8444ed7342..fa0092fe8fb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ debug = 2 [workspace.dependencies] # local crates base-db = { path = "./crates/base-db", version = "0.0.0" } -cfg = { path = "./crates/cfg", version = "0.0.0", features = ["tt"] } +cfg = { path = "./crates/cfg", version = "0.0.0", features = ["tt", "syntax"] } hir = { path = "./crates/hir", version = "0.0.0" } hir-def = { path = "./crates/hir-def", version = "0.0.0" } hir-expand = { path = "./crates/hir-expand", version = "0.0.0" } @@ -138,7 +138,7 @@ process-wrap = { version = "8.2.1", features = ["std"] } pulldown-cmark-to-cmark = "10.0.4" pulldown-cmark = { version = "0.9.6", default-features = false } rayon = "1.10.0" -rowan = "=0.15.15" +rowan = "=0.15.17" # Ideally we'd not enable the macros feature but unfortunately the `tracked` attribute does not work # on impls without it salsa = { version = "0.23.0", default-features = true, features = [ @@ -174,6 +174,7 @@ tracing-subscriber = { version = "0.3.19", default-features = false, features = triomphe = { version = "0.1.14", default-features = false, features = ["std"] } url = "2.5.4" xshell = "0.2.7" +thin-vec = "0.2.14" # We need to freeze the version of the crate, as the raw-api feature is considered unstable dashmap = { version = "=6.1.0", features = ["raw-api", "inline"] } diff --git a/crates/base-db/src/editioned_file_id.rs b/crates/base-db/src/editioned_file_id.rs new file mode 100644 index 000000000000..b276ab5e10aa --- /dev/null +++ b/crates/base-db/src/editioned_file_id.rs @@ -0,0 +1,233 @@ +//! Defines [`EditionedFileId`], an interned wrapper around [`span::EditionedFileId`] that +//! is interned (so queries can take it) and remembers its crate. + +use ::core::num::NonZeroUsize; +use core::fmt; +use std::hash::{Hash, Hasher}; + +use span::Edition; +use vfs::FileId; + +use crate::{Crate, RootQueryDb}; + +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct EditionedFileId( + salsa::Id, + std::marker::PhantomData<&'static salsa::plumbing::interned::Value>, +); + +const _: () = { + use salsa::plumbing as zalsa_; + use zalsa_::interned as zalsa_struct_; + type Configuration_ = EditionedFileId; + + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct EditionedFileIdData { + editioned_file_id: span::EditionedFileId, + krate: Crate, + } + + /// We like to include the origin crate in an `EditionedFileId` (for use in the item tree), + /// but this poses us a problem. + /// + /// Spans contain `EditionedFileId`s, and we don't want to make them store the crate too + /// because that will increase their size, which will increase memory usage significantly. + /// Furthermore, things using spans do not generally need the crate: they are using the + /// file id for queries like `ast_id_map` or `parse`, which do not care about the crate. + /// + /// To solve this, we hash **only the `span::EditionedFileId`**, but on still compare + /// the crate in equality check. This preserves the invariant of `Hash` and `Eq` - + /// although same hashes can be used for different items, same file ids used for multiple + /// crates is a rare thing, and different items always have different hashes. Then, + /// when we only have a `span::EditionedFileId`, we use the `intern()` method to + /// reuse existing file ids, and create new one only if needed. See [`from_span_guess_origin`]. + /// + /// See this for more info: https://rust-lang.zulipchat.com/#narrow/channel/185405-t-compiler.2Frust-analyzer/topic/Letting.20EditionedFileId.20know.20its.20crate/near/530189401 + /// + /// [`from_span_guess_origin`]: EditionedFileId::from_span_guess_origin + #[derive(Hash, PartialEq, Eq)] + struct WithoutCrate { + editioned_file_id: span::EditionedFileId, + } + + impl Hash for EditionedFileIdData { + #[inline] + fn hash(&self, state: &mut H) { + let EditionedFileIdData { editioned_file_id, krate: _ } = *self; + editioned_file_id.hash(state); + } + } + + impl zalsa_struct_::HashEqLike for EditionedFileIdData { + #[inline] + fn hash(&self, state: &mut H) { + Hash::hash(self, state); + } + + #[inline] + fn eq(&self, data: &WithoutCrate) -> bool { + let EditionedFileIdData { editioned_file_id, krate: _ } = *self; + editioned_file_id == data.editioned_file_id + } + } + + impl zalsa_struct_::Configuration for EditionedFileId { + const LOCATION: zalsa_::Location = zalsa_::Location { file: file!(), line: 0u32 }; + const DEBUG_NAME: &'static str = "EditionedFileId"; + const REVISIONS: NonZeroUsize = NonZeroUsize::new(usize::MAX).unwrap(); + type Fields<'a> = EditionedFileIdData; + type Struct<'db> = EditionedFileId; + } + + impl Configuration_ { + pub fn ingredient(db: &dyn salsa::Database) -> &zalsa_struct_::IngredientImpl { + static CACHE: zalsa_::IngredientCache> = + zalsa_::IngredientCache::new(); + let zalsa = db.zalsa(); + CACHE.get_or_create(zalsa, || { + zalsa.lookup_jar_by_type::>().get_or_create() + }) + } + } + + impl zalsa_::AsId for EditionedFileId { + fn as_id(&self) -> salsa::Id { + self.0.as_id() + } + } + impl zalsa_::FromId for EditionedFileId { + fn from_id(id: salsa::Id) -> Self { + Self(::from_id(id), std::marker::PhantomData) + } + } + + unsafe impl Send for EditionedFileId {} + unsafe impl Sync for EditionedFileId {} + + impl std::fmt::Debug for EditionedFileId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Self::default_debug_fmt(*self, f) + } + } + + impl zalsa_::SalsaStructInDb for EditionedFileId { + type MemoIngredientMap = zalsa_::MemoIngredientSingletonIndex; + fn lookup_or_create_ingredient_index(aux: &zalsa_::Zalsa) -> zalsa_::IngredientIndices { + aux.lookup_jar_by_type::>() + .get_or_create() + .into() + } + #[inline] + fn cast(id: zalsa_::Id, type_id: zalsa_::TypeId) -> zalsa_::Option { + if type_id == zalsa_::TypeId::of::() { + zalsa_::Some(::from_id(id)) + } else { + zalsa_::None + } + } + } + + unsafe impl zalsa_::Update for EditionedFileId { + unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { + if unsafe { *old_pointer } != new_value { + unsafe { *old_pointer = new_value }; + true + } else { + false + } + } + } + + impl EditionedFileId { + pub fn from_span( + db: &dyn salsa::Database, + editioned_file_id: span::EditionedFileId, + krate: Crate, + ) -> Self { + Configuration_::ingredient(db).intern( + db, + EditionedFileIdData { editioned_file_id, krate }, + |_, data| data, + ) + } + + /// Guesses the crate for the file. + /// + /// Only use this if you cannot precisely determine the origin. This can happen in one of two cases: + /// + /// 1. The file is not in the module tree. + /// 2. You are latency sensitive and cannot afford calling the def map to precisely compute the origin + /// (e.g. on enter feature, folding, etc.). + pub fn from_span_guess_origin( + db: &dyn RootQueryDb, + editioned_file_id: span::EditionedFileId, + ) -> Self { + Configuration_::ingredient(db).intern(db, WithoutCrate { editioned_file_id }, |_, _| { + // FileId not in the database. + let krate = db + .relevant_crates(editioned_file_id.file_id()) + .first() + .copied() + .unwrap_or_else(|| db.all_crates()[0]); + EditionedFileIdData { editioned_file_id, krate } + }) + } + + pub fn editioned_file_id(self, db: &dyn salsa::Database) -> span::EditionedFileId { + let fields = Configuration_::ingredient(db).fields(db, self); + fields.editioned_file_id + } + + pub fn krate(self, db: &dyn salsa::Database) -> Crate { + let fields = Configuration_::ingredient(db).fields(db, self); + fields.krate + } + + /// Default debug formatting for this struct (may be useful if you define your own `Debug` impl) + pub fn default_debug_fmt(this: Self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + zalsa_::with_attached_database(|db| { + let fields = Configuration_::ingredient(db).fields(db, this); + fmt::Debug::fmt(fields, f) + }) + .unwrap_or_else(|| { + f.debug_tuple("EditionedFileId").field(&zalsa_::AsId::as_id(&this)).finish() + }) + } + } +}; + +impl EditionedFileId { + #[inline] + pub fn new(db: &dyn salsa::Database, file_id: FileId, edition: Edition, krate: Crate) -> Self { + EditionedFileId::from_span(db, span::EditionedFileId::new(file_id, edition), krate) + } + + /// Attaches the current edition and guesses the crate for the file. + /// + /// Only use this if you cannot precisely determine the origin. This can happen in one of two cases: + /// + /// 1. The file is not in the module tree. + /// 2. You are latency sensitive and cannot afford calling the def map to precisely compute the origin + /// (e.g. on enter feature, folding, etc.). + #[inline] + pub fn current_edition_guess_origin(db: &dyn RootQueryDb, file_id: FileId) -> Self { + Self::from_span_guess_origin(db, span::EditionedFileId::current_edition(file_id)) + } + + #[inline] + pub fn file_id(self, db: &dyn salsa::Database) -> vfs::FileId { + let id = self.editioned_file_id(db); + id.file_id() + } + + #[inline] + pub fn unpack(self, db: &dyn salsa::Database) -> (vfs::FileId, span::Edition) { + let id = self.editioned_file_id(db); + (id.file_id(), id.edition()) + } + + #[inline] + pub fn edition(self, db: &dyn salsa::Database) -> Edition { + self.editioned_file_id(db).edition() + } +} diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs index 0bf4fbdfbd69..9a1a82c34ef2 100644 --- a/crates/base-db/src/input.rs +++ b/crates/base-db/src/input.rs @@ -831,9 +831,10 @@ pub(crate) fn transitive_rev_deps(db: &dyn RootQueryDb, of: Crate) -> FxHashSet< rev_deps } -impl BuiltCrateData { - pub fn root_file_id(&self, db: &dyn salsa::Database) -> EditionedFileId { - EditionedFileId::new(db, self.root_file_id, self.edition) +impl Crate { + pub fn root_file_id(self, db: &dyn salsa::Database) -> EditionedFileId { + let data = self.data(db); + EditionedFileId::new(db, data.root_file_id, data.edition, self) } } diff --git a/crates/base-db/src/lib.rs b/crates/base-db/src/lib.rs index b8eadb608fea..23ac09b8fda3 100644 --- a/crates/base-db/src/lib.rs +++ b/crates/base-db/src/lib.rs @@ -5,12 +5,14 @@ pub use salsa_macros; // FIXME: Rename this crate, base db is non descriptive mod change; +mod editioned_file_id; mod input; use std::{cell::RefCell, hash::BuildHasherDefault, panic, sync::Once}; pub use crate::{ change::FileChange, + editioned_file_id::EditionedFileId, input::{ BuiltCrateData, BuiltDependency, Crate, CrateBuilder, CrateBuilderId, CrateDataBuilder, CrateDisplayName, CrateGraphBuilder, CrateName, CrateOrigin, CratesIdMap, CratesMap, @@ -24,7 +26,6 @@ pub use query_group::{self}; use rustc_hash::{FxHashSet, FxHasher}; use salsa::{Durability, Setter}; pub use semver::{BuildMetadata, Prerelease, Version, VersionReq}; -use span::Edition; use syntax::{Parse, SyntaxError, ast}; use triomphe::Arc; pub use vfs::{AnchoredPath, AnchoredPathBuf, FileId, VfsPath, file_set::FileSet}; @@ -168,42 +169,6 @@ impl Files { } } -#[salsa_macros::interned(no_lifetime, debug, constructor=from_span, revisions = usize::MAX)] -#[derive(PartialOrd, Ord)] -pub struct EditionedFileId { - pub editioned_file_id: span::EditionedFileId, -} - -impl EditionedFileId { - // Salsa already uses the name `new`... - #[inline] - pub fn new(db: &dyn salsa::Database, file_id: FileId, edition: Edition) -> Self { - EditionedFileId::from_span(db, span::EditionedFileId::new(file_id, edition)) - } - - #[inline] - pub fn current_edition(db: &dyn salsa::Database, file_id: FileId) -> Self { - EditionedFileId::new(db, file_id, Edition::CURRENT) - } - - #[inline] - pub fn file_id(self, db: &dyn salsa::Database) -> vfs::FileId { - let id = self.editioned_file_id(db); - id.file_id() - } - - #[inline] - pub fn unpack(self, db: &dyn salsa::Database) -> (vfs::FileId, span::Edition) { - let id = self.editioned_file_id(db); - (id.file_id(), id.edition()) - } - - #[inline] - pub fn edition(self, db: &dyn SourceDatabase) -> Edition { - self.editioned_file_id(db).edition() - } -} - #[salsa_macros::input(debug)] pub struct FileText { #[returns(ref)] diff --git a/crates/cfg/Cargo.toml b/crates/cfg/Cargo.toml index ba3496661453..d59f2ec58b46 100644 --- a/crates/cfg/Cargo.toml +++ b/crates/cfg/Cargo.toml @@ -18,6 +18,7 @@ tracing.workspace = true # locals deps tt = { workspace = true, optional = true } +syntax = { workspace = true, optional = true } intern.workspace = true [dev-dependencies] diff --git a/crates/cfg/src/cfg_expr.rs b/crates/cfg/src/cfg_expr.rs index f83c21eb8d64..f8783f2de34d 100644 --- a/crates/cfg/src/cfg_expr.rs +++ b/crates/cfg/src/cfg_expr.rs @@ -63,6 +63,8 @@ impl From for CfgExpr { } impl CfgExpr { + // FIXME: Parsing from `tt` is only used in a handful of places, reconsider + // if we should switch them to AST. #[cfg(feature = "tt")] pub fn parse(tt: &tt::TopSubtree) -> CfgExpr { next_cfg_expr(&mut tt.iter()).unwrap_or(CfgExpr::Invalid) @@ -73,6 +75,13 @@ impl CfgExpr { next_cfg_expr(tt).unwrap_or(CfgExpr::Invalid) } + #[cfg(feature = "syntax")] + pub fn parse_from_ast( + ast: &mut std::iter::Peekable, + ) -> CfgExpr { + next_cfg_expr_from_ast(ast).unwrap_or(CfgExpr::Invalid) + } + /// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates. pub fn fold(&self, query: &dyn Fn(&CfgAtom) -> bool) -> Option { match self { @@ -89,6 +98,56 @@ impl CfgExpr { } } +#[cfg(feature = "syntax")] +fn next_cfg_expr_from_ast( + it: &mut std::iter::Peekable, +) -> Option { + use intern::sym; + use syntax::{NodeOrToken, SyntaxKind, T, ast}; + + let name = match it.next() { + None => return None, + Some(NodeOrToken::Token(ident)) if ident.kind().is_any_identifier() => { + Symbol::intern(ident.text()) + } + Some(_) => return Some(CfgExpr::Invalid), + }; + + let ret = match it.peek() { + Some(NodeOrToken::Token(eq)) if eq.kind() == T![=] => { + it.next(); + if let Some(NodeOrToken::Token(literal)) = it.peek() + && matches!(literal.kind(), SyntaxKind::STRING) + { + let literal = tt::token_to_literal(literal.text(), ()).symbol; + it.next(); + CfgAtom::KeyValue { key: name, value: literal.clone() }.into() + } else { + return Some(CfgExpr::Invalid); + } + } + Some(NodeOrToken::Node(subtree)) => { + let mut subtree_iter = ast::TokenTreeChildren::new(subtree).peekable(); + it.next(); + let mut subs = std::iter::from_fn(|| next_cfg_expr_from_ast(&mut subtree_iter)); + match name { + s if s == sym::all => CfgExpr::All(subs.collect()), + s if s == sym::any => CfgExpr::Any(subs.collect()), + s if s == sym::not => { + CfgExpr::Not(Box::new(subs.next().unwrap_or(CfgExpr::Invalid))) + } + _ => CfgExpr::Invalid, + } + } + _ => CfgAtom::Flag(name).into(), + }; + + // Eat comma separator + while it.next().is_some_and(|it| it.as_token().is_none_or(|it| it.kind() != T![,])) {} + + Some(ret) +} + #[cfg(feature = "tt")] fn next_cfg_expr(it: &mut tt::iter::TtIter<'_, S>) -> Option { use intern::sym; diff --git a/crates/cfg/src/tests.rs b/crates/cfg/src/tests.rs index 6766748097f0..52c581dbbd3a 100644 --- a/crates/cfg/src/tests.rs +++ b/crates/cfg/src/tests.rs @@ -1,7 +1,10 @@ use arbitrary::{Arbitrary, Unstructured}; use expect_test::{Expect, expect}; use intern::Symbol; -use syntax::{AstNode, Edition, ast}; +use syntax::{ + AstNode, Edition, + ast::{self, TokenTreeChildren}, +}; use syntax_bridge::{ DocCommentDesugarMode, dummy_test_span_utils::{DUMMY, DummyTestSpanMap}, @@ -10,24 +13,33 @@ use syntax_bridge::{ use crate::{CfgAtom, CfgExpr, CfgOptions, DnfExpr}; +#[track_caller] +fn parse_ast_cfg(tt: &ast::TokenTree) -> CfgExpr { + CfgExpr::parse_from_ast(&mut TokenTreeChildren::new(tt).peekable()) +} + +#[track_caller] fn assert_parse_result(input: &str, expected: CfgExpr) { let source_file = ast::SourceFile::parse(input, Edition::CURRENT).ok().unwrap(); - let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); + let tt_ast = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); let tt = syntax_node_to_token_tree( - tt.syntax(), + tt_ast.syntax(), DummyTestSpanMap, DUMMY, DocCommentDesugarMode::ProcMacro, ); let cfg = CfgExpr::parse(&tt); assert_eq!(cfg, expected); + let cfg = parse_ast_cfg(&tt_ast); + assert_eq!(cfg, expected); } +#[track_caller] fn check_dnf(input: &str, expect: Expect) { let source_file = ast::SourceFile::parse(input, Edition::CURRENT).ok().unwrap(); - let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); + let tt_ast = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); let tt = syntax_node_to_token_tree( - tt.syntax(), + tt_ast.syntax(), DummyTestSpanMap, DUMMY, DocCommentDesugarMode::ProcMacro, @@ -35,13 +47,17 @@ fn check_dnf(input: &str, expect: Expect) { let cfg = CfgExpr::parse(&tt); let actual = format!("#![cfg({})]", DnfExpr::new(&cfg)); expect.assert_eq(&actual); + let cfg = parse_ast_cfg(&tt_ast); + let actual = format!("#![cfg({})]", DnfExpr::new(&cfg)); + expect.assert_eq(&actual); } +#[track_caller] fn check_why_inactive(input: &str, opts: &CfgOptions, expect: Expect) { let source_file = ast::SourceFile::parse(input, Edition::CURRENT).ok().unwrap(); - let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); + let tt_ast = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); let tt = syntax_node_to_token_tree( - tt.syntax(), + tt_ast.syntax(), DummyTestSpanMap, DUMMY, DocCommentDesugarMode::ProcMacro, @@ -50,14 +66,18 @@ fn check_why_inactive(input: &str, opts: &CfgOptions, expect: Expect) { let dnf = DnfExpr::new(&cfg); let why_inactive = dnf.why_inactive(opts).unwrap().to_string(); expect.assert_eq(&why_inactive); + let cfg = parse_ast_cfg(&tt_ast); + let dnf = DnfExpr::new(&cfg); + let why_inactive = dnf.why_inactive(opts).unwrap().to_string(); + expect.assert_eq(&why_inactive); } #[track_caller] fn check_enable_hints(input: &str, opts: &CfgOptions, expected_hints: &[&str]) { let source_file = ast::SourceFile::parse(input, Edition::CURRENT).ok().unwrap(); - let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); + let tt_ast = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); let tt = syntax_node_to_token_tree( - tt.syntax(), + tt_ast.syntax(), DummyTestSpanMap, DUMMY, DocCommentDesugarMode::ProcMacro, @@ -66,6 +86,10 @@ fn check_enable_hints(input: &str, opts: &CfgOptions, expected_hints: &[&str]) { let dnf = DnfExpr::new(&cfg); let hints = dnf.compute_enable_hints(opts).map(|diff| diff.to_string()).collect::>(); assert_eq!(hints, expected_hints); + let cfg = parse_ast_cfg(&tt_ast); + let dnf = DnfExpr::new(&cfg); + let hints = dnf.compute_enable_hints(opts).map(|diff| diff.to_string()).collect::>(); + assert_eq!(hints, expected_hints); } #[test] diff --git a/crates/hir-def/Cargo.toml b/crates/hir-def/Cargo.toml index abb4819a7672..e1f60742d324 100644 --- a/crates/hir-def/Cargo.toml +++ b/crates/hir-def/Cargo.toml @@ -45,7 +45,8 @@ mbe.workspace = true cfg.workspace = true tt.workspace = true span.workspace = true -thin-vec = "0.2.14" +thin-vec.workspace = true +syntax-bridge.workspace = true [dev-dependencies] expect-test.workspace = true @@ -53,7 +54,6 @@ expect-test.workspace = true # local deps test-utils.workspace = true test-fixture.workspace = true -syntax-bridge.workspace = true [features] in-rust-tree = ["hir-expand/in-rust-tree"] diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs deleted file mode 100644 index b4fcfa11aea7..000000000000 --- a/crates/hir-def/src/attr.rs +++ /dev/null @@ -1,901 +0,0 @@ -//! A higher level attributes based on TokenTree, with also some shortcuts. - -use std::{borrow::Cow, convert::identity, hash::Hash, ops}; - -use base_db::Crate; -use cfg::{CfgExpr, CfgOptions}; -use either::Either; -use hir_expand::{ - HirFileId, InFile, - attrs::{Attr, AttrId, RawAttrs, collect_attrs}, - span_map::SpanMapRef, -}; -use intern::{Symbol, sym}; -use la_arena::{ArenaMap, Idx, RawIdx}; -use mbe::DelimiterKind; -use rustc_abi::ReprOptions; -use span::AstIdNode; -use syntax::{ - AstPtr, - ast::{self, HasAttrs}, -}; -use triomphe::Arc; -use tt::iter::{TtElement, TtIter}; - -use crate::{ - AdtId, AstIdLoc, AttrDefId, GenericParamId, HasModule, LocalFieldId, Lookup, MacroId, - VariantId, - db::DefDatabase, - item_tree::block_item_tree_query, - lang_item::LangItem, - nameres::{ModuleOrigin, ModuleSource}, - src::{HasChildSource, HasSource}, -}; - -/// Desugared attributes of an item post `cfg_attr` expansion. -#[derive(Default, Debug, Clone, PartialEq, Eq)] -pub struct Attrs(RawAttrs); - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct AttrsWithOwner { - attrs: Attrs, - owner: AttrDefId, -} - -impl Attrs { - pub fn new( - db: &dyn DefDatabase, - owner: &dyn ast::HasAttrs, - span_map: SpanMapRef<'_>, - cfg_options: &CfgOptions, - ) -> Self { - Attrs(RawAttrs::new_expanded(db, owner, span_map, cfg_options)) - } - - pub fn get(&self, id: AttrId) -> Option<&Attr> { - (**self).iter().find(|attr| attr.id == id) - } - - pub(crate) fn expand_cfg_attr( - db: &dyn DefDatabase, - krate: Crate, - raw_attrs: RawAttrs, - ) -> Attrs { - Attrs(raw_attrs.expand_cfg_attr(db, krate)) - } - - pub(crate) fn is_cfg_enabled_for( - db: &dyn DefDatabase, - owner: &dyn ast::HasAttrs, - span_map: SpanMapRef<'_>, - cfg_options: &CfgOptions, - ) -> Result<(), CfgExpr> { - RawAttrs::attrs_iter_expanded::(db, owner, span_map, cfg_options) - .filter_map(|attr| attr.cfg()) - .find_map(|cfg| match cfg_options.check(&cfg).is_none_or(identity) { - true => None, - false => Some(cfg), - }) - .map_or(Ok(()), Err) - } -} - -impl ops::Deref for Attrs { - type Target = [Attr]; - - fn deref(&self) -> &[Attr] { - &self.0 - } -} - -impl ops::Deref for AttrsWithOwner { - type Target = Attrs; - - fn deref(&self) -> &Attrs { - &self.attrs - } -} - -impl Attrs { - pub const EMPTY: Self = Self(RawAttrs::EMPTY); - - pub(crate) fn fields_attrs_query( - db: &dyn DefDatabase, - v: VariantId, - ) -> Arc> { - let _p = tracing::info_span!("fields_attrs_query").entered(); - let mut res = ArenaMap::default(); - let (fields, file_id, krate) = match v { - VariantId::EnumVariantId(it) => { - let loc = it.lookup(db); - let krate = loc.parent.lookup(db).container.krate; - let source = loc.source(db); - (source.value.field_list(), source.file_id, krate) - } - VariantId::StructId(it) => { - let loc = it.lookup(db); - let krate = loc.container.krate; - let source = loc.source(db); - (source.value.field_list(), source.file_id, krate) - } - VariantId::UnionId(it) => { - let loc = it.lookup(db); - let krate = loc.container.krate; - let source = loc.source(db); - ( - source.value.record_field_list().map(ast::FieldList::RecordFieldList), - source.file_id, - krate, - ) - } - }; - let Some(fields) = fields else { - return Arc::new(res); - }; - - let cfg_options = krate.cfg_options(db); - let span_map = db.span_map(file_id); - - match fields { - ast::FieldList::RecordFieldList(fields) => { - let mut idx = 0; - for field in fields.fields() { - let attrs = - Attrs(RawAttrs::new_expanded(db, &field, span_map.as_ref(), cfg_options)); - if attrs.is_cfg_enabled(cfg_options).is_ok() { - res.insert(Idx::from_raw(RawIdx::from(idx)), attrs); - idx += 1; - } - } - } - ast::FieldList::TupleFieldList(fields) => { - let mut idx = 0; - for field in fields.fields() { - let attrs = - Attrs(RawAttrs::new_expanded(db, &field, span_map.as_ref(), cfg_options)); - if attrs.is_cfg_enabled(cfg_options).is_ok() { - res.insert(Idx::from_raw(RawIdx::from(idx)), attrs); - idx += 1; - } - } - } - } - - res.shrink_to_fit(); - Arc::new(res) - } -} - -impl Attrs { - #[inline] - pub fn by_key(&self, key: Symbol) -> AttrQuery<'_> { - AttrQuery { attrs: self, key } - } - - #[inline] - pub fn rust_analyzer_tool(&self) -> impl Iterator { - self.iter() - .filter(|&attr| attr.path.segments().first().is_some_and(|s| *s == sym::rust_analyzer)) - } - - #[inline] - pub fn cfg(&self) -> Option { - let mut cfgs = self.by_key(sym::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), - } - } - - #[inline] - pub fn cfgs(&self) -> impl Iterator + '_ { - self.by_key(sym::cfg).tt_values().map(CfgExpr::parse) - } - - #[inline] - pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> Result<(), CfgExpr> { - self.cfgs().try_for_each(|cfg| { - if cfg_options.check(&cfg) != Some(false) { Ok(()) } else { Err(cfg) } - }) - } - - #[inline] - pub fn lang(&self) -> Option<&Symbol> { - self.by_key(sym::lang).string_value() - } - - #[inline] - pub fn lang_item(&self) -> Option { - self.by_key(sym::lang).string_value().and_then(LangItem::from_symbol) - } - - #[inline] - pub fn has_doc_hidden(&self) -> bool { - self.by_key(sym::doc).tt_values().any(|tt| { - tt.top_subtree().delimiter.kind == DelimiterKind::Parenthesis && - matches!(tt.token_trees().flat_tokens(), [tt::TokenTree::Leaf(tt::Leaf::Ident(ident))] if ident.sym == sym::hidden) - }) - } - - #[inline] - pub fn has_doc_notable_trait(&self) -> bool { - self.by_key(sym::doc).tt_values().any(|tt| { - tt.top_subtree().delimiter.kind == DelimiterKind::Parenthesis && - matches!(tt.token_trees().flat_tokens(), [tt::TokenTree::Leaf(tt::Leaf::Ident(ident))] if ident.sym == sym::notable_trait) - }) - } - - #[inline] - pub fn doc_exprs(&self) -> impl Iterator + '_ { - self.by_key(sym::doc).tt_values().map(DocExpr::parse) - } - - #[inline] - pub fn doc_aliases(&self) -> impl Iterator + '_ { - self.doc_exprs().flat_map(|doc_expr| doc_expr.aliases().to_vec()) - } - - #[inline] - pub fn export_name(&self) -> Option<&Symbol> { - self.by_key(sym::export_name).string_value() - } - - #[inline] - pub fn is_proc_macro(&self) -> bool { - self.by_key(sym::proc_macro).exists() - } - - #[inline] - pub fn is_proc_macro_attribute(&self) -> bool { - self.by_key(sym::proc_macro_attribute).exists() - } - - #[inline] - pub fn is_proc_macro_derive(&self) -> bool { - self.by_key(sym::proc_macro_derive).exists() - } - - #[inline] - pub fn is_test(&self) -> bool { - self.iter().any(|it| { - it.path() - .segments() - .iter() - .rev() - .zip([sym::core, sym::prelude, sym::v1, sym::test].iter().rev()) - .all(|it| it.0 == it.1) - }) - } - - #[inline] - pub fn is_ignore(&self) -> bool { - self.by_key(sym::ignore).exists() - } - - #[inline] - pub fn is_bench(&self) -> bool { - self.by_key(sym::bench).exists() - } - - #[inline] - pub fn is_unstable(&self) -> bool { - self.by_key(sym::unstable).exists() - } - - #[inline] - pub fn rustc_legacy_const_generics(&self) -> Option>> { - self.by_key(sym::rustc_legacy_const_generics) - .tt_values() - .next() - .map(parse_rustc_legacy_const_generics) - .filter(|it| !it.is_empty()) - .map(Box::new) - } - - #[inline] - pub fn repr(&self) -> Option { - self.by_key(sym::repr).tt_values().filter_map(parse_repr_tt).fold(None, |acc, repr| { - acc.map_or(Some(repr), |mut acc| { - merge_repr(&mut acc, repr); - Some(acc) - }) - }) - } -} - -fn parse_rustc_legacy_const_generics(tt: &crate::tt::TopSubtree) -> Box<[u32]> { - let mut indices = Vec::new(); - let mut iter = tt.iter(); - while let (Some(first), second) = (iter.next(), iter.next()) { - match first { - TtElement::Leaf(tt::Leaf::Literal(lit)) => match lit.symbol.as_str().parse() { - Ok(index) => indices.push(index), - Err(_) => break, - }, - _ => break, - } - - if let Some(comma) = second { - match comma { - TtElement::Leaf(tt::Leaf::Punct(punct)) if punct.char == ',' => {} - _ => break, - } - } - } - - indices.into_boxed_slice() -} - -fn merge_repr(this: &mut ReprOptions, other: ReprOptions) { - let ReprOptions { int, align, pack, flags, field_shuffle_seed: _ } = this; - flags.insert(other.flags); - *align = (*align).max(other.align); - *pack = match (*pack, other.pack) { - (Some(pack), None) | (None, Some(pack)) => Some(pack), - _ => (*pack).min(other.pack), - }; - if other.int.is_some() { - *int = other.int; - } -} - -fn parse_repr_tt(tt: &crate::tt::TopSubtree) -> Option { - use crate::builtin_type::{BuiltinInt, BuiltinUint}; - use rustc_abi::{Align, Integer, IntegerType, ReprFlags, ReprOptions}; - - match tt.top_subtree().delimiter { - tt::Delimiter { kind: DelimiterKind::Parenthesis, .. } => {} - _ => return None, - } - - let mut acc = ReprOptions::default(); - let mut tts = tt.iter(); - while let Some(tt) = tts.next() { - let TtElement::Leaf(tt::Leaf::Ident(ident)) = tt else { - continue; - }; - let repr = match &ident.sym { - s if *s == sym::packed => { - let pack = if let Some(TtElement::Subtree(_, mut tt_iter)) = tts.peek() { - tts.next(); - if let Some(TtElement::Leaf(tt::Leaf::Literal(lit))) = tt_iter.next() { - lit.symbol.as_str().parse().unwrap_or_default() - } else { - 0 - } - } else { - 0 - }; - let pack = Some(Align::from_bytes(pack).unwrap_or(Align::ONE)); - ReprOptions { pack, ..Default::default() } - } - s if *s == sym::align => { - let mut align = None; - if let Some(TtElement::Subtree(_, mut tt_iter)) = tts.peek() { - tts.next(); - if let Some(TtElement::Leaf(tt::Leaf::Literal(lit))) = tt_iter.next() - && let Ok(a) = lit.symbol.as_str().parse() - { - align = Align::from_bytes(a).ok(); - } - } - ReprOptions { align, ..Default::default() } - } - s if *s == sym::C => ReprOptions { flags: ReprFlags::IS_C, ..Default::default() }, - s if *s == sym::transparent => { - ReprOptions { flags: ReprFlags::IS_TRANSPARENT, ..Default::default() } - } - s if *s == sym::simd => ReprOptions { flags: ReprFlags::IS_SIMD, ..Default::default() }, - repr => { - let mut int = None; - if let Some(builtin) = BuiltinInt::from_suffix_sym(repr) - .map(Either::Left) - .or_else(|| BuiltinUint::from_suffix_sym(repr).map(Either::Right)) - { - int = Some(match builtin { - Either::Left(bi) => match bi { - BuiltinInt::Isize => IntegerType::Pointer(true), - BuiltinInt::I8 => IntegerType::Fixed(Integer::I8, true), - BuiltinInt::I16 => IntegerType::Fixed(Integer::I16, true), - BuiltinInt::I32 => IntegerType::Fixed(Integer::I32, true), - BuiltinInt::I64 => IntegerType::Fixed(Integer::I64, true), - BuiltinInt::I128 => IntegerType::Fixed(Integer::I128, true), - }, - Either::Right(bu) => match bu { - BuiltinUint::Usize => IntegerType::Pointer(false), - BuiltinUint::U8 => IntegerType::Fixed(Integer::I8, false), - BuiltinUint::U16 => IntegerType::Fixed(Integer::I16, false), - BuiltinUint::U32 => IntegerType::Fixed(Integer::I32, false), - BuiltinUint::U64 => IntegerType::Fixed(Integer::I64, false), - BuiltinUint::U128 => IntegerType::Fixed(Integer::I128, false), - }, - }); - } - ReprOptions { int, ..Default::default() } - } - }; - merge_repr(&mut acc, repr); - } - - Some(acc) -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum DocAtom { - /// eg. `#[doc(hidden)]` - Flag(Symbol), - /// eg. `#[doc(alias = "it")]` - /// - /// Note that a key can have multiple values that are all considered "active" at the same time. - /// For example, `#[doc(alias = "x")]` and `#[doc(alias = "y")]`. - KeyValue { key: Symbol, value: Symbol }, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum DocExpr { - Invalid, - /// eg. `#[doc(hidden)]`, `#[doc(alias = "x")]` - Atom(DocAtom), - /// eg. `#[doc(alias("x", "y"))]` - Alias(Vec), -} - -impl From for DocExpr { - fn from(atom: DocAtom) -> Self { - DocExpr::Atom(atom) - } -} - -impl DocExpr { - fn parse(tt: &tt::TopSubtree) -> DocExpr { - next_doc_expr(tt.iter()).unwrap_or(DocExpr::Invalid) - } - - pub fn aliases(&self) -> &[Symbol] { - match self { - DocExpr::Atom(DocAtom::KeyValue { key, value }) if *key == sym::alias => { - std::slice::from_ref(value) - } - DocExpr::Alias(aliases) => aliases, - _ => &[], - } - } -} - -fn next_doc_expr(mut it: TtIter<'_, S>) -> Option { - let name = match it.next() { - None => return None, - Some(TtElement::Leaf(tt::Leaf::Ident(ident))) => ident.sym.clone(), - Some(_) => return Some(DocExpr::Invalid), - }; - - // Peek - let ret = match it.peek() { - Some(TtElement::Leaf(tt::Leaf::Punct(punct))) if punct.char == '=' => { - it.next(); - match it.next() { - Some(TtElement::Leaf(tt::Leaf::Literal(tt::Literal { - symbol: text, - kind: tt::LitKind::Str, - .. - }))) => DocAtom::KeyValue { key: name, value: text.clone() }.into(), - _ => return Some(DocExpr::Invalid), - } - } - Some(TtElement::Subtree(_, subtree_iter)) => { - it.next(); - let subs = parse_comma_sep(subtree_iter); - match &name { - s if *s == sym::alias => DocExpr::Alias(subs), - _ => DocExpr::Invalid, - } - } - _ => DocAtom::Flag(name).into(), - }; - Some(ret) -} - -fn parse_comma_sep(iter: TtIter<'_, S>) -> Vec { - iter.filter_map(|tt| match tt { - TtElement::Leaf(tt::Leaf::Literal(tt::Literal { - kind: tt::LitKind::Str, symbol, .. - })) => Some(symbol.clone()), - _ => None, - }) - .collect() -} - -impl AttrsWithOwner { - pub fn new(db: &dyn DefDatabase, owner: AttrDefId) -> Self { - Self { attrs: db.attrs(owner), owner } - } - - pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Attrs { - let _p = tracing::info_span!("attrs_query").entered(); - // FIXME: this should use `Trace` to avoid duplication in `source_map` below - match def { - AttrDefId::ModuleId(module) => { - let def_map = module.def_map(db); - let mod_data = &def_map[module.local_id]; - - let raw_attrs = match mod_data.origin { - ModuleOrigin::File { definition, declaration_tree_id, declaration, .. } => { - let decl_attrs = declaration_tree_id - .item_tree(db) - .raw_attrs(declaration.upcast()) - .clone(); - let tree = db.file_item_tree(definition.into()); - let def_attrs = tree.top_level_raw_attrs().clone(); - decl_attrs.merge(def_attrs) - } - ModuleOrigin::CrateRoot { definition } => { - let tree = db.file_item_tree(definition.into()); - tree.top_level_raw_attrs().clone() - } - ModuleOrigin::Inline { definition_tree_id, definition } => { - definition_tree_id.item_tree(db).raw_attrs(definition.upcast()).clone() - } - ModuleOrigin::BlockExpr { id, .. } => { - let tree = block_item_tree_query(db, id); - tree.top_level_raw_attrs().clone() - } - }; - Attrs::expand_cfg_attr(db, module.krate, raw_attrs) - } - AttrDefId::FieldId(it) => db.fields_attrs(it.parent)[it.local_id].clone(), - AttrDefId::EnumVariantId(it) => attrs_from_ast_id_loc(db, it), - AttrDefId::AdtId(it) => match it { - AdtId::StructId(it) => attrs_from_ast_id_loc(db, it), - AdtId::EnumId(it) => attrs_from_ast_id_loc(db, it), - AdtId::UnionId(it) => attrs_from_ast_id_loc(db, it), - }, - AttrDefId::TraitId(it) => attrs_from_ast_id_loc(db, it), - AttrDefId::MacroId(it) => match it { - MacroId::Macro2Id(it) => attrs_from_ast_id_loc(db, it), - MacroId::MacroRulesId(it) => attrs_from_ast_id_loc(db, it), - MacroId::ProcMacroId(it) => attrs_from_ast_id_loc(db, it), - }, - AttrDefId::ImplId(it) => attrs_from_ast_id_loc(db, it), - AttrDefId::ConstId(it) => attrs_from_ast_id_loc(db, it), - AttrDefId::StaticId(it) => attrs_from_ast_id_loc(db, it), - AttrDefId::FunctionId(it) => attrs_from_ast_id_loc(db, it), - AttrDefId::TypeAliasId(it) => attrs_from_ast_id_loc(db, it), - AttrDefId::GenericParamId(it) => match it { - GenericParamId::ConstParamId(it) => { - let src = it.parent().child_source(db); - // FIXME: We should be never getting `None` here. - Attrs(match src.value.get(it.local_id()) { - Some(val) => RawAttrs::new_expanded( - db, - val, - db.span_map(src.file_id).as_ref(), - def.krate(db).cfg_options(db), - ), - None => RawAttrs::EMPTY, - }) - } - GenericParamId::TypeParamId(it) => { - let src = it.parent().child_source(db); - // FIXME: We should be never getting `None` here. - Attrs(match src.value.get(it.local_id()) { - Some(val) => RawAttrs::new_expanded( - db, - val, - db.span_map(src.file_id).as_ref(), - def.krate(db).cfg_options(db), - ), - None => RawAttrs::EMPTY, - }) - } - GenericParamId::LifetimeParamId(it) => { - let src = it.parent.child_source(db); - // FIXME: We should be never getting `None` here. - Attrs(match src.value.get(it.local_id) { - Some(val) => RawAttrs::new_expanded( - db, - val, - db.span_map(src.file_id).as_ref(), - def.krate(db).cfg_options(db), - ), - None => RawAttrs::EMPTY, - }) - } - }, - AttrDefId::ExternBlockId(it) => attrs_from_ast_id_loc(db, it), - AttrDefId::ExternCrateId(it) => attrs_from_ast_id_loc(db, it), - AttrDefId::UseId(it) => attrs_from_ast_id_loc(db, it), - } - } - - 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); - let owner = ast::AnyHasAttrs::new(map[id.local_id].to_node(&root)); - InFile::new(file_id, owner) - } - AttrDefId::AdtId(adt) => match adt { - AdtId::StructId(id) => any_has_attrs(db, id), - AdtId::UnionId(id) => any_has_attrs(db, id), - AdtId::EnumId(id) => any_has_attrs(db, id), - }, - AttrDefId::FunctionId(id) => any_has_attrs(db, id), - AttrDefId::EnumVariantId(id) => any_has_attrs(db, id), - AttrDefId::StaticId(id) => any_has_attrs(db, id), - AttrDefId::ConstId(id) => any_has_attrs(db, id), - AttrDefId::TraitId(id) => any_has_attrs(db, id), - AttrDefId::TypeAliasId(id) => any_has_attrs(db, id), - AttrDefId::MacroId(id) => match id { - MacroId::Macro2Id(id) => any_has_attrs(db, id), - MacroId::MacroRulesId(id) => any_has_attrs(db, id), - MacroId::ProcMacroId(id) => any_has_attrs(db, id), - }, - AttrDefId::ImplId(id) => any_has_attrs(db, id), - AttrDefId::GenericParamId(id) => match id { - GenericParamId::ConstParamId(id) => id - .parent() - .child_source(db) - .map(|source| ast::AnyHasAttrs::new(source[id.local_id()].clone())), - GenericParamId::TypeParamId(id) => id - .parent() - .child_source(db) - .map(|source| ast::AnyHasAttrs::new(source[id.local_id()].clone())), - GenericParamId::LifetimeParamId(id) => id - .parent - .child_source(db) - .map(|source| ast::AnyHasAttrs::new(source[id.local_id].clone())), - }, - AttrDefId::ExternBlockId(id) => any_has_attrs(db, id), - AttrDefId::ExternCrateId(id) => any_has_attrs(db, id), - AttrDefId::UseId(id) => any_has_attrs(db, id), - }; - - AttrSourceMap::new(owner.as_ref().map(|node| node as &dyn HasAttrs)) - } -} - -#[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) - } - - pub fn source_of_id(&self, id: AttrId) -> InFile<&Either> { - let ast_idx = id.ast_index(); - 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:?}")) - } -} - -#[derive(Debug, Clone)] -pub struct AttrQuery<'attr> { - attrs: &'attr Attrs, - key: Symbol, -} - -impl<'attr> AttrQuery<'attr> { - #[inline] - pub fn tt_values(self) -> impl Iterator { - self.attrs().filter_map(|attr| attr.token_tree_value()) - } - - #[inline] - pub fn string_value(self) -> Option<&'attr Symbol> { - self.attrs().find_map(|attr| attr.string_value()) - } - - #[inline] - pub fn string_value_with_span(self) -> Option<(&'attr Symbol, span::Span)> { - self.attrs().find_map(|attr| attr.string_value_with_span()) - } - - #[inline] - pub fn string_value_unescape(self) -> Option> { - self.attrs().find_map(|attr| attr.string_value_unescape()) - } - - #[inline] - pub fn exists(self) -> bool { - self.attrs().next().is_some() - } - - #[inline] - pub fn attrs(self) -> impl Iterator + Clone { - let key = self.key; - self.attrs.iter().filter(move |attr| attr.path.as_ident().is_some_and(|s| *s == key)) - } - - /// Find string value for a specific key inside token tree - /// - /// ```ignore - /// #[doc(html_root_url = "url")] - /// ^^^^^^^^^^^^^ key - /// ``` - #[inline] - pub fn find_string_value_in_tt(self, key: Symbol) -> Option<&'attr str> { - self.tt_values().find_map(|tt| { - let name = tt.iter() - .skip_while(|tt| !matches!(tt, TtElement::Leaf(tt::Leaf::Ident(tt::Ident { sym, ..} )) if *sym == key)) - .nth(2); - - match name { - Some(TtElement::Leaf(tt::Leaf::Literal(tt::Literal{ symbol: text, kind: tt::LitKind::Str | tt::LitKind::StrRaw(_) , ..}))) => Some(text.as_str()), - _ => None - } - }) - } -} - -fn any_has_attrs<'db>( - db: &(dyn DefDatabase + 'db), - id: impl Lookup>, -) -> InFile { - id.lookup(db).source(db).map(ast::AnyHasAttrs::new) -} - -fn attrs_from_ast_id_loc<'db, N: AstIdNode + HasAttrs>( - db: &(dyn DefDatabase + 'db), - lookup: impl Lookup + HasModule>, -) -> Attrs { - let loc = lookup.lookup(db); - let source = loc.source(db); - let span_map = db.span_map(source.file_id); - let cfg_options = loc.krate(db).cfg_options(db); - Attrs(RawAttrs::new_expanded(db, &source.value, span_map.as_ref(), cfg_options)) -} - -pub(crate) fn fields_attrs_source_map( - db: &dyn DefDatabase, - def: VariantId, -) -> Arc>>> { - 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| AstPtr::new(l).wrap_left(), |r| AstPtr::new(r).wrap_right()), - ); - } - - Arc::new(res) -} - -#[cfg(test)] -mod tests { - //! This module contains tests for doc-expression parsing. - //! Currently, it tests `#[doc(hidden)]` and `#[doc(alias)]`. - - use intern::Symbol; - use span::EditionedFileId; - use triomphe::Arc; - - use hir_expand::span_map::{RealSpanMap, SpanMap}; - use span::FileId; - use syntax::{AstNode, TextRange, ast}; - use syntax_bridge::{DocCommentDesugarMode, syntax_node_to_token_tree}; - - use crate::attr::{DocAtom, DocExpr}; - - fn assert_parse_result(input: &str, expected: DocExpr) { - let source_file = ast::SourceFile::parse(input, span::Edition::CURRENT).ok().unwrap(); - let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); - let map = SpanMap::RealSpanMap(Arc::new(RealSpanMap::absolute( - EditionedFileId::current_edition(FileId::from_raw(0)), - ))); - let tt = syntax_node_to_token_tree( - tt.syntax(), - map.as_ref(), - map.span_for_range(TextRange::empty(0.into())), - DocCommentDesugarMode::ProcMacro, - ); - let cfg = DocExpr::parse(&tt); - assert_eq!(cfg, expected); - } - - #[test] - fn test_doc_expr_parser() { - assert_parse_result("#![doc(hidden)]", DocAtom::Flag(Symbol::intern("hidden")).into()); - - assert_parse_result( - r#"#![doc(alias = "foo")]"#, - DocAtom::KeyValue { key: Symbol::intern("alias"), value: Symbol::intern("foo") }.into(), - ); - - assert_parse_result( - r#"#![doc(alias("foo"))]"#, - DocExpr::Alias([Symbol::intern("foo")].into()), - ); - assert_parse_result( - r#"#![doc(alias("foo", "bar", "baz"))]"#, - DocExpr::Alias( - [Symbol::intern("foo"), Symbol::intern("bar"), Symbol::intern("baz")].into(), - ), - ); - - assert_parse_result( - r#" - #[doc(alias("Bar", "Qux"))] - struct Foo;"#, - DocExpr::Alias([Symbol::intern("Bar"), Symbol::intern("Qux")].into()), - ); - } -} diff --git a/crates/hir-def/src/attrs.rs b/crates/hir-def/src/attrs.rs new file mode 100644 index 000000000000..86e00b7123c1 --- /dev/null +++ b/crates/hir-def/src/attrs.rs @@ -0,0 +1,1611 @@ +//! Attributes for anything that is not name resolution. +//! +//! The fundamental idea of this module stems from the observation that most "interesting" +//! attributes have a more memory-compact form than storing their full syntax, and +//! that most of the attributes are flags, and those that are not are rare. Therefore, +//! this module defines [`AttrFlags`], which is a bitflag enum that contains only a yes/no +//! answer to whether an attribute is present on an item. For most attributes, that's all +//! that is interesting us; for the rest of them, we define another query that extracts +//! their data. A key part is that every one of those queries will have a wrapper method +//! that queries (or is given) the `AttrFlags` and checks for the presence of the attribute; +//! if it is not present, we do not call the query, to prevent Salsa from needing to record +//! its value. This way, queries are only called on items that have the attribute, which is +//! usually only a few. +//! +//! An exception to this model that is also defined in this module is documentation (doc +//! comments and `#[doc = "..."]` attributes). But it also has a more compact form than +//! the attribute: a concatenated string of the full docs as well as a source map +//! to map it back to AST (which is needed for things like resolving links in doc comments +//! and highlight injection). The lowering and upmapping of doc comments is a bit complicated, +//! but it is encapsulated in the [`Docs`] struct. + +use std::{ + convert::Infallible, + iter::Peekable, + ops::{ControlFlow, Range}, +}; + +use base_db::Crate; +use cfg::{CfgExpr, CfgOptions}; +use either::Either; +use hir_expand::{ + HirFileId, InFile, Lookup, + attrs::{Meta, expand_cfg_attr, expand_cfg_attr_with_doc_comments}, +}; +use intern::Symbol; +use itertools::Itertools; +use la_arena::ArenaMap; +use rustc_abi::ReprOptions; +use rustc_hash::FxHashSet; +use smallvec::SmallVec; +use syntax::{ + AstNode, AstToken, NodeOrToken, SmolStr, SyntaxNode, SyntaxToken, T, + ast::{self, AttrDocCommentIter, HasAttrs, IsString, TokenTreeChildren}, +}; +use tt::{TextRange, TextSize}; + +use crate::{ + AdtId, AstIdLoc, AttrDefId, FieldId, FunctionId, GenericDefId, HasModule, InternedModuleId, + LifetimeParamId, LocalFieldId, MacroId, TypeOrConstParamId, VariantId, + db::DefDatabase, + hir::generics::{GenericParams, LocalLifetimeParamId, LocalTypeOrConstParamId}, + lang_item::LangItem, + nameres::ModuleOrigin, + src::{HasChildSource, HasSource}, +}; + +#[inline] +fn attrs_from_ast_id_loc>( + db: &dyn DefDatabase, + lookup: impl Lookup + HasModule>, +) -> (InFile, Crate) { + let loc = lookup.lookup(db); + let source = loc.source(db); + let krate = loc.krate(db); + (source.map(|it| it.into()), krate) +} + +#[inline] +fn extract_doc_tt_attr(attr_flags: &mut AttrFlags, tt: ast::TokenTree) { + for atom in DocAtom::parse(tt) { + match atom { + DocAtom::Flag(flag) => match &*flag { + "notable_trait" => attr_flags.insert(AttrFlags::IS_DOC_NOTABLE_TRAIT), + "hidden" => attr_flags.insert(AttrFlags::IS_DOC_HIDDEN), + _ => {} + }, + DocAtom::KeyValue { key, value: _ } => match &*key { + "alias" => attr_flags.insert(AttrFlags::HAS_DOC_ALIASES), + "keyword" => attr_flags.insert(AttrFlags::HAS_DOC_KEYWORD), + _ => {} + }, + DocAtom::Alias(_) => attr_flags.insert(AttrFlags::HAS_DOC_ALIASES), + } + } +} + +fn extract_ra_completions(attr_flags: &mut AttrFlags, tt: ast::TokenTree) { + let tt = TokenTreeChildren::new(&tt); + if let Ok(NodeOrToken::Token(option)) = tt.exactly_one() + && option.kind().is_any_identifier() + { + match option.text() { + "ignore_flyimport" => attr_flags.insert(AttrFlags::COMPLETE_IGNORE_FLYIMPORT), + "ignore_methods" => attr_flags.insert(AttrFlags::COMPLETE_IGNORE_METHODS), + "ignore_flyimport_methods" => { + attr_flags.insert(AttrFlags::COMPLETE_IGNORE_FLYIMPORT_METHODS) + } + _ => {} + } + } +} + +fn extract_rustc_skip_during_method_dispatch(attr_flags: &mut AttrFlags, tt: ast::TokenTree) { + let iter = TokenTreeChildren::new(&tt); + for kind in iter { + if let NodeOrToken::Token(kind) = kind + && kind.kind().is_any_identifier() + { + match kind.text() { + "array" => attr_flags.insert(AttrFlags::RUSTC_SKIP_ARRAY_DURING_METHOD_DISPATCH), + "boxed_slice" => { + attr_flags.insert(AttrFlags::RUSTC_SKIP_BOXED_SLICE_DURING_METHOD_DISPATCH) + } + _ => {} + } + } + } +} + +#[inline] +fn match_attr_flags(attr_flags: &mut AttrFlags, attr: Meta) -> ControlFlow { + match attr { + Meta::NamedKeyValue { name: Some(name), value, .. } => match name.text() { + "deprecated" => attr_flags.insert(AttrFlags::IS_DEPRECATED), + "lang" => attr_flags.insert(AttrFlags::LANG_ITEM), + "path" => attr_flags.insert(AttrFlags::HAS_PATH), + "unstable" => attr_flags.insert(AttrFlags::IS_UNSTABLE), + "export_name" => { + if let Some(value) = value + && let Some(value) = ast::String::cast(value) + && let Ok(value) = value.value() + && *value == *"main" + { + attr_flags.insert(AttrFlags::IS_EXPORT_NAME_MAIN); + } + } + _ => {} + }, + Meta::TokenTree { path, tt } => match path.segments.len() { + 1 => match path.segments[0].text() { + "deprecated" => attr_flags.insert(AttrFlags::IS_DEPRECATED), + "cfg" => attr_flags.insert(AttrFlags::HAS_CFG), + "doc" => extract_doc_tt_attr(attr_flags, tt), + "repr" => attr_flags.insert(AttrFlags::HAS_REPR), + "target_feature" => attr_flags.insert(AttrFlags::HAS_TARGET_FEATURE), + "proc_macro_derive" | "rustc_builtin_macro" => { + attr_flags.insert(AttrFlags::IS_DERIVE_OR_BUILTIN_MACRO) + } + "unstable" => attr_flags.insert(AttrFlags::IS_UNSTABLE), + "rustc_layout_scalar_valid_range_start" | "rustc_layout_scalar_valid_range_end" => { + attr_flags.insert(AttrFlags::RUSTC_LAYOUT_SCALAR_VALID_RANGE) + } + "rustc_legacy_const_generics" => { + attr_flags.insert(AttrFlags::HAS_LEGACY_CONST_GENERICS) + } + "rustc_skip_during_method_dispatch" => { + extract_rustc_skip_during_method_dispatch(attr_flags, tt) + } + _ => {} + }, + 2 => match path.segments[0].text() { + "rust_analyzer" => match path.segments[1].text() { + "completions" => extract_ra_completions(attr_flags, tt), + _ => {} + }, + _ => {} + }, + _ => {} + }, + Meta::Path { path } => { + match path.segments.len() { + 1 => match path.segments[0].text() { + "rustc_has_incoherent_inherent_impls" => { + attr_flags.insert(AttrFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS) + } + "rustc_allow_incoherent_impl" => { + attr_flags.insert(AttrFlags::RUSTC_ALLOW_INCOHERENT_IMPL) + } + "fundamental" => attr_flags.insert(AttrFlags::FUNDAMENTAL), + "no_std" => attr_flags.insert(AttrFlags::IS_NO_STD), + "may_dangle" => attr_flags.insert(AttrFlags::MAY_DANGLE), + "rustc_paren_sugar" => attr_flags.insert(AttrFlags::RUSTC_PAREN_SUGAR), + "rustc_coinductive" => attr_flags.insert(AttrFlags::RUSTC_COINDUCTIVE), + "unstable" => attr_flags.insert(AttrFlags::IS_UNSTABLE), + "deprecated" => attr_flags.insert(AttrFlags::IS_DEPRECATED), + "macro_export" => attr_flags.insert(AttrFlags::IS_MACRO_EXPORT), + "no_mangle" => attr_flags.insert(AttrFlags::NO_MANGLE), + "non_exhaustive" => attr_flags.insert(AttrFlags::NON_EXHAUSTIVE), + "ignore" => attr_flags.insert(AttrFlags::IS_IGNORE), + "bench" => attr_flags.insert(AttrFlags::IS_BENCH), + "rustc_const_panic_str" => attr_flags.insert(AttrFlags::RUSTC_CONST_PANIC_STR), + "rustc_intrinsic" => attr_flags.insert(AttrFlags::RUSTC_INTRINSIC), + "rustc_safe_intrinsic" => attr_flags.insert(AttrFlags::RUSTC_SAFE_INTRINSIC), + "rustc_intrinsic_must_be_overridden" => { + attr_flags.insert(AttrFlags::RUSTC_INTRINSIC_MUST_BE_OVERRIDDEN) + } + "rustc_allocator" => attr_flags.insert(AttrFlags::RUSTC_ALLOCATOR), + "rustc_deallocator" => attr_flags.insert(AttrFlags::RUSTC_DEALLOCATOR), + "rustc_reallocator" => attr_flags.insert(AttrFlags::RUSTC_REALLOCATOR), + "rustc_allocator_zeroed" => { + attr_flags.insert(AttrFlags::RUSTC_ALLOCATOR_ZEROED) + } + "rustc_reservation_impl" => { + attr_flags.insert(AttrFlags::RUSTC_RESERVATION_IMPL) + } + "rustc_deprecated_safe_2024" => { + attr_flags.insert(AttrFlags::RUSTC_DEPRECATED_SAFE_2024) + } + "rustc_skip_array_during_method_dispatch" => { + attr_flags.insert(AttrFlags::RUSTC_SKIP_ARRAY_DURING_METHOD_DISPATCH) + } + _ => {} + }, + 2 => match path.segments[0].text() { + "rust_analyzer" => match path.segments[1].text() { + "skip" => attr_flags.insert(AttrFlags::RUST_ANALYZER_SKIP), + _ => {} + }, + _ => {} + }, + _ => {} + } + + if path.is_test { + attr_flags.insert(AttrFlags::IS_TEST); + } + } + _ => {} + }; + ControlFlow::Continue(()) +} + +bitflags::bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub struct AttrFlags: u64 { + const RUST_ANALYZER_SKIP = 1 << 0; + + const LANG_ITEM = 1 << 1; + + const HAS_DOC_ALIASES = 1 << 2; + const HAS_DOC_KEYWORD = 1 << 3; + const IS_DOC_NOTABLE_TRAIT = 1 << 4; + const IS_DOC_HIDDEN = 1 << 5; + + const RUSTC_HAS_INCOHERENT_INHERENT_IMPLS = 1 << 6; + const RUSTC_ALLOW_INCOHERENT_IMPL = 1 << 7; + const FUNDAMENTAL = 1 << 8; + const RUSTC_SKIP_ARRAY_DURING_METHOD_DISPATCH = 1 << 9; + const RUSTC_SKIP_BOXED_SLICE_DURING_METHOD_DISPATCH = 1 << 10; + const HAS_REPR = 1 << 11; + const HAS_TARGET_FEATURE = 1 << 12; + const RUSTC_DEPRECATED_SAFE_2024 = 1 << 13; + const HAS_LEGACY_CONST_GENERICS = 1 << 14; + const NO_MANGLE = 1 << 15; + const NON_EXHAUSTIVE = 1 << 16; + const RUSTC_RESERVATION_IMPL = 1 << 17; + const RUSTC_CONST_PANIC_STR = 1 << 18; + const MAY_DANGLE = 1 << 19; + + const RUSTC_INTRINSIC = 1 << 20; + const RUSTC_SAFE_INTRINSIC = 1 << 21; + const RUSTC_INTRINSIC_MUST_BE_OVERRIDDEN = 1 << 22; + const RUSTC_ALLOCATOR = 1 << 23; + const RUSTC_DEALLOCATOR = 1 << 24; + const RUSTC_REALLOCATOR = 1 << 25; + const RUSTC_ALLOCATOR_ZEROED = 1 << 26; + + const IS_UNSTABLE = 1 << 27; + const IS_IGNORE = 1 << 28; + // FIXME: `IS_TEST` and `IS_BENCH` should be based on semantic information, not textual match. + const IS_BENCH = 1 << 29; + const IS_TEST = 1 << 30; + const IS_EXPORT_NAME_MAIN = 1 << 31; + const IS_MACRO_EXPORT = 1 << 32; + const IS_NO_STD = 1 << 33; + const IS_DERIVE_OR_BUILTIN_MACRO = 1 << 34; + const IS_DEPRECATED = 1 << 35; + const HAS_PATH = 1 << 36; + const HAS_CFG = 1 << 37; + + const COMPLETE_IGNORE_FLYIMPORT = 1 << 38; + const COMPLETE_IGNORE_FLYIMPORT_METHODS = 1 << 39; + const COMPLETE_IGNORE_METHODS = 1 << 40; + + const RUSTC_LAYOUT_SCALAR_VALID_RANGE = 1 << 41; + const RUSTC_PAREN_SUGAR = 1 << 42; + const RUSTC_COINDUCTIVE = 1 << 43; + } +} + +fn attrs_source( + db: &dyn DefDatabase, + owner: AttrDefId, +) -> (InFile, Option>, Crate) { + let (owner, krate) = match owner { + AttrDefId::ModuleId(id) => { + let id = id.loc(db); + let def_map = id.def_map(db); + let (definition, declaration) = match def_map[id.local_id].origin { + ModuleOrigin::CrateRoot { definition } => { + let file = db.parse(definition).tree(); + (InFile::new(definition.into(), ast::AnyHasAttrs::from(file)), None) + } + ModuleOrigin::File { declaration, declaration_tree_id, definition, .. } => { + let declaration = InFile::new(declaration_tree_id.file_id(), declaration); + let declaration = declaration.with_value(declaration.to_node(db)); + let definition_source = db.parse(definition).tree(); + (InFile::new(definition.into(), definition_source.into()), Some(declaration)) + } + ModuleOrigin::Inline { definition_tree_id, definition } => { + let definition = InFile::new(definition_tree_id.file_id(), definition); + let definition = definition.with_value(definition.to_node(db).into()); + (definition, None) + } + ModuleOrigin::BlockExpr { block, .. } => { + let definition = block.to_node(db); + (block.with_value(definition.into()), None) + } + }; + return (definition, declaration, id.krate); + } + AttrDefId::AdtId(AdtId::StructId(it)) => attrs_from_ast_id_loc(db, it), + AttrDefId::AdtId(AdtId::UnionId(it)) => attrs_from_ast_id_loc(db, it), + AttrDefId::AdtId(AdtId::EnumId(it)) => attrs_from_ast_id_loc(db, it), + AttrDefId::FunctionId(it) => attrs_from_ast_id_loc(db, it), + AttrDefId::EnumVariantId(it) => attrs_from_ast_id_loc(db, it), + AttrDefId::StaticId(it) => attrs_from_ast_id_loc(db, it), + AttrDefId::ConstId(it) => attrs_from_ast_id_loc(db, it), + AttrDefId::TraitId(it) => attrs_from_ast_id_loc(db, it), + AttrDefId::TypeAliasId(it) => attrs_from_ast_id_loc(db, it), + AttrDefId::MacroId(MacroId::MacroRulesId(it)) => attrs_from_ast_id_loc(db, it), + AttrDefId::MacroId(MacroId::Macro2Id(it)) => attrs_from_ast_id_loc(db, it), + AttrDefId::MacroId(MacroId::ProcMacroId(it)) => attrs_from_ast_id_loc(db, it), + AttrDefId::ImplId(it) => attrs_from_ast_id_loc(db, it), + AttrDefId::ExternBlockId(it) => attrs_from_ast_id_loc(db, it), + AttrDefId::ExternCrateId(it) => attrs_from_ast_id_loc(db, it), + AttrDefId::UseId(it) => attrs_from_ast_id_loc(db, it), + }; + (owner, None, krate) +} + +fn collect_attrs( + db: &dyn DefDatabase, + owner: AttrDefId, + mut callback: impl FnMut(Meta) -> ControlFlow, +) -> Option { + let (source, outer_mod_decl, krate) = attrs_source(db, owner); + + let mut cfg_options = None; + expand_cfg_attr( + outer_mod_decl + .into_iter() + .flat_map(|it| it.value.attrs()) + .chain(ast::attrs_including_inner(&source.value)), + || cfg_options.get_or_insert_with(|| krate.cfg_options(db)), + move |meta, _, _, _| callback(meta), + ) +} + +fn collect_field_attrs( + db: &dyn DefDatabase, + variant: VariantId, + mut field_attrs: impl FnMut(&CfgOptions, InFile) -> T, +) -> ArenaMap { + let (variant_syntax, krate) = match variant { + VariantId::EnumVariantId(it) => attrs_from_ast_id_loc(db, it), + VariantId::StructId(it) => attrs_from_ast_id_loc(db, it), + VariantId::UnionId(it) => attrs_from_ast_id_loc(db, it), + }; + let cfg_options = krate.cfg_options(db); + let variant_syntax = variant_syntax + .with_value(ast::VariantDef::cast(variant_syntax.value.syntax().clone()).unwrap()); + let fields = match &variant_syntax.value { + ast::VariantDef::Struct(it) => it.field_list(), + ast::VariantDef::Union(it) => it.record_field_list().map(ast::FieldList::RecordFieldList), + ast::VariantDef::Variant(it) => it.field_list(), + }; + let Some(fields) = fields else { + return ArenaMap::new(); + }; + + let mut result = ArenaMap::new(); + let mut idx = 0; + match fields { + ast::FieldList::RecordFieldList(fields) => { + for field in fields.fields() { + if AttrFlags::is_cfg_enabled_for(&field, cfg_options).is_ok() { + result.insert( + la_arena::Idx::from_raw(la_arena::RawIdx::from_u32(idx)), + field_attrs(cfg_options, variant_syntax.with_value(field.into())), + ); + idx += 1; + } + } + } + ast::FieldList::TupleFieldList(fields) => { + for field in fields.fields() { + if AttrFlags::is_cfg_enabled_for(&field, cfg_options).is_ok() { + result.insert( + la_arena::Idx::from_raw(la_arena::RawIdx::from_u32(idx)), + field_attrs(cfg_options, variant_syntax.with_value(field.into())), + ); + idx += 1; + } + } + } + } + result.shrink_to_fit(); + result +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +pub struct RustcLayoutScalarValidRange { + pub start: Option, + pub end: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +struct DocsSourceMapLine { + /// The offset in [`Docs::docs`]. + string_offset: TextSize, + /// The offset in the AST of the text. + ast_offset: TextSize, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Docs { + /// The concatenated string of all `#[doc = "..."]` attributes and documentation comments. + docs: String, + /// A sorted map from an offset in `docs` to an offset in the source code. + docs_source_map: Vec, + /// If the item is an outlined module (`mod foo;`), `docs_source_map` store the concatenated + /// list of the outline and inline docs (outline first). Then, this field contains the [`HirFileId`] + /// of the outline declaration, and the index in `docs` from which the inline docs + /// begin. + outline_mod: Option<(HirFileId, usize)>, + inline_file: HirFileId, + /// The size the prepended prefix, which does not map to real doc comments. + prefix_len: TextSize, + /// The offset in `docs` from which the docs are inner attributes/comments. + inline_inner_docs_start: Option, + /// Like `inline_inner_docs_start`, but for `outline_mod`. This can happen only when merging `Docs` + /// (as outline modules don't have inner attributes). + outline_inner_docs_start: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum IsInnerDoc { + No, + Yes, +} + +impl IsInnerDoc { + #[inline] + pub fn yes(self) -> bool { + self == IsInnerDoc::Yes + } +} + +impl Docs { + #[inline] + pub fn docs(&self) -> &str { + &self.docs + } + + #[inline] + pub fn into_docs(self) -> String { + self.docs + } + + pub fn find_ast_range( + &self, + mut string_range: TextRange, + ) -> Option<(InFile, IsInnerDoc)> { + if string_range.start() < self.prefix_len { + return None; + } + string_range -= self.prefix_len; + + let mut file = self.inline_file; + let mut inner_docs_start = self.inline_inner_docs_start; + // Check whether the range is from the outline, the inline, or both. + let source_map = if let Some((outline_mod_file, outline_mod_end)) = self.outline_mod { + if let Some(first_inline) = self.docs_source_map.get(outline_mod_end) { + if string_range.end() <= first_inline.string_offset { + // The range is completely in the outline. + file = outline_mod_file; + inner_docs_start = self.outline_inner_docs_start; + &self.docs_source_map[..outline_mod_end] + } else if string_range.start() >= first_inline.string_offset { + // The range is completely in the inline. + &self.docs_source_map[outline_mod_end..] + } else { + // The range is combined from the outline and the inline - cannot map it back. + return None; + } + } else { + // There is no inline. + file = outline_mod_file; + inner_docs_start = self.outline_inner_docs_start; + &self.docs_source_map + } + } else { + // There is no outline. + &self.docs_source_map + }; + + let after_range = + source_map.partition_point(|line| line.string_offset <= string_range.start()) - 1; + let after_range = &source_map[after_range..]; + let line = after_range.first()?; + if after_range.get(1).is_some_and(|next_line| next_line.string_offset < string_range.end()) + { + // The range is combined from two lines - cannot map it back. + return None; + } + let ast_range = string_range - line.string_offset + line.ast_offset; + let is_inner = if inner_docs_start + .is_some_and(|inner_docs_start| string_range.start() >= inner_docs_start) + { + IsInnerDoc::Yes + } else { + IsInnerDoc::No + }; + Some((InFile::new(file, ast_range), is_inner)) + } + + #[inline] + pub fn shift_by(&mut self, offset: TextSize) { + self.prefix_len += offset; + } + + pub fn prepend_str(&mut self, s: &str) { + self.prefix_len += TextSize::of(s); + self.docs.insert_str(0, s); + } + + pub fn append_str(&mut self, s: &str) { + self.docs.push_str(s); + } + + pub fn append(&mut self, other: &Docs) { + let other_offset = TextSize::of(&self.docs); + + assert!( + self.outline_mod.is_none() && other.outline_mod.is_none(), + "cannot merge `Docs` that have `outline_mod` set" + ); + self.outline_mod = Some((self.inline_file, self.docs_source_map.len())); + self.inline_file = other.inline_file; + self.outline_inner_docs_start = self.inline_inner_docs_start; + self.inline_inner_docs_start = other.inline_inner_docs_start.map(|it| it + other_offset); + + self.docs.push_str(&other.docs); + self.docs_source_map.extend(other.docs_source_map.iter().map( + |&DocsSourceMapLine { string_offset, ast_offset }| DocsSourceMapLine { + ast_offset, + string_offset: string_offset + other_offset, + }, + )); + } + + fn extend_with_doc_comment(&mut self, comment: ast::Comment, indent: &mut usize) { + let Some((doc, offset)) = comment.doc_comment() else { return }; + self.extend_with_doc_str(doc, comment.syntax().text_range().start() + offset, indent); + } + + fn extend_with_doc_attr(&mut self, value: SyntaxToken, indent: &mut usize) { + let Some(value) = ast::String::cast(value) else { return }; + let Some(value_offset) = value.text_range_between_quotes() else { return }; + let value_offset = value_offset.start(); + let Ok(value) = value.value() else { return }; + // FIXME: Handle source maps for escaped text. + self.extend_with_doc_str(&value, value_offset, indent); + } + + fn extend_with_doc_str(&mut self, doc: &str, mut offset_in_ast: TextSize, indent: &mut usize) { + for line in doc.split('\n') { + self.docs_source_map.push(DocsSourceMapLine { + string_offset: TextSize::of(&self.docs), + ast_offset: offset_in_ast, + }); + offset_in_ast += TextSize::of(line) + TextSize::of("\n"); + + let line = line.trim_end(); + if let Some(line_indent) = line.chars().position(|ch| !ch.is_whitespace()) { + // Empty lines are handled because `position()` returns `None` for them. + *indent = std::cmp::min(*indent, line_indent); + } + self.docs.push_str(line); + self.docs.push('\n'); + } + } + + fn remove_indent(&mut self, indent: usize, start_source_map_index: usize) { + /// In case of panics, we want to avoid corrupted UTF-8 in `self.docs`, so we clear it. + struct Guard<'a>(&'a mut Docs); + impl Drop for Guard<'_> { + fn drop(&mut self) { + let Docs { + docs, + docs_source_map, + outline_mod, + inline_file: _, + prefix_len: _, + inline_inner_docs_start: _, + outline_inner_docs_start: _, + } = self.0; + // Don't use `String::clear()` here because it's not guaranteed to not do UTF-8-dependent things, + // and we may have temporarily broken the string's encoding. + unsafe { docs.as_mut_vec() }.clear(); + // This is just to avoid panics down the road. + docs_source_map.clear(); + *outline_mod = None; + } + } + + if self.docs.is_empty() { + return; + } + + let guard = Guard(self); + let source_map = &mut guard.0.docs_source_map[start_source_map_index..]; + let Some(&DocsSourceMapLine { string_offset: mut copy_into, .. }) = source_map.first() + else { + return; + }; + // We basically want to remove multiple ranges from a string. Doing this efficiently (without O(N^2) + // or allocations) requires unsafe. Basically, for each line, we copy the line minus the indent into + // consecutive to the previous line (which may have moved). Then at the end we truncate. + let mut accumulated_offset = TextSize::new(0); + for idx in 0..source_map.len() { + let string_end_offset = source_map + .get(idx + 1) + .map_or_else(|| TextSize::of(&guard.0.docs), |next_attr| next_attr.string_offset); + let line_source = &mut source_map[idx]; + let line_docs = + &guard.0.docs[TextRange::new(line_source.string_offset, string_end_offset)]; + let line_docs_len = TextSize::of(line_docs); + let indent_size = line_docs.char_indices().nth(indent).map_or_else( + || TextSize::of(line_docs) - TextSize::of("\n"), + |(offset, _)| TextSize::new(offset as u32), + ); + unsafe { guard.0.docs.as_bytes_mut() }.copy_within( + Range::::from(TextRange::new( + line_source.string_offset + indent_size, + string_end_offset, + )), + copy_into.into(), + ); + copy_into += line_docs_len - indent_size; + + if let Some(inner_attrs_start) = &mut guard.0.inline_inner_docs_start + && *inner_attrs_start == line_source.string_offset + { + *inner_attrs_start -= accumulated_offset; + } + // The removals in the string accumulate, but in the AST not, because it already points + // to the beginning of each attribute. + // Also, we need to shift the AST offset of every line, but the string offset of the first + // line should not get shifted (in general, the shift for the string offset is by the + // number of lines until the current one, excluding the current one). + line_source.string_offset -= accumulated_offset; + line_source.ast_offset += indent_size; + + accumulated_offset += indent_size; + } + // Don't use `String::truncate()` here because it's not guaranteed to not do UTF-8-dependent things, + // and we may have temporarily broken the string's encoding. + unsafe { guard.0.docs.as_mut_vec() }.truncate(copy_into.into()); + + std::mem::forget(guard); + } + + fn remove_last_newline(&mut self) { + self.docs.truncate(self.docs.len().saturating_sub(1)); + } + + fn shrink_to_fit(&mut self) { + let Docs { + docs, + docs_source_map, + outline_mod: _, + inline_file: _, + prefix_len: _, + inline_inner_docs_start: _, + outline_inner_docs_start: _, + } = self; + docs.shrink_to_fit(); + docs_source_map.shrink_to_fit(); + } +} + +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct DeriveInfo { + pub trait_name: Symbol, + pub helpers: Box<[Symbol]>, +} + +fn extract_doc_aliases(result: &mut Vec, attr: Meta) -> ControlFlow { + if let Meta::TokenTree { path, tt } = attr + && path.is1("doc") + { + for atom in DocAtom::parse(tt) { + match atom { + DocAtom::Alias(aliases) => { + result.extend(aliases.into_iter().map(|alias| Symbol::intern(&alias))) + } + DocAtom::KeyValue { key, value } if key == "alias" => { + result.push(Symbol::intern(&value)) + } + _ => {} + } + } + } + ControlFlow::Continue(()) +} + +fn extract_cfgs(result: &mut Vec, attr: Meta) -> ControlFlow { + if let Meta::TokenTree { path, tt } = attr + && path.is1("cfg") + { + result.push(CfgExpr::parse_from_ast(&mut TokenTreeChildren::new(&tt).peekable())); + } + ControlFlow::Continue(()) +} + +fn extract_docs<'a>( + get_cfg_options: &dyn Fn() -> &'a CfgOptions, + source: InFile, + outer_mod_decl: Option>, + inner_attrs_node: Option, +) -> Option> { + let mut result = Docs { + docs: String::new(), + docs_source_map: Vec::new(), + outline_mod: None, + inline_file: source.file_id, + prefix_len: TextSize::new(0), + inline_inner_docs_start: None, + outline_inner_docs_start: None, + }; + + let mut cfg_options = None; + let mut extend_with_attrs = + |result: &mut Docs, node: &SyntaxNode, expect_inner_attrs, indent: &mut usize| { + expand_cfg_attr_with_doc_comments::<_, Infallible>( + AttrDocCommentIter::from_syntax_node(node).filter(|attr| match attr { + Either::Left(attr) => attr.kind().is_inner() == expect_inner_attrs, + Either::Right(comment) => comment.kind().doc.is_some_and(|kind| { + (kind == ast::CommentPlacement::Inner) == expect_inner_attrs + }), + }), + || cfg_options.get_or_insert_with(get_cfg_options), + |attr| { + match attr { + Either::Right(doc_comment) => { + result.extend_with_doc_comment(doc_comment, indent) + } + Either::Left((attr, _, _, _)) => match attr { + // FIXME: Handle macros: `#[doc = concat!("foo", "bar")]`. + Meta::NamedKeyValue { + name: Some(name), value: Some(value), .. + } if name.text() == "doc" => { + result.extend_with_doc_attr(value, indent); + } + _ => {} + }, + } + ControlFlow::Continue(()) + }, + ); + }; + + if let Some(outer_mod_decl) = outer_mod_decl { + let mut indent = usize::MAX; + extend_with_attrs(&mut result, outer_mod_decl.value.syntax(), false, &mut indent); + result.remove_indent(indent, 0); + result.outline_mod = Some((outer_mod_decl.file_id, result.docs_source_map.len())); + } + + let inline_source_map_start = result.docs_source_map.len(); + let mut indent = usize::MAX; + extend_with_attrs(&mut result, source.value.syntax(), false, &mut indent); + if let Some(inner_attrs_node) = &inner_attrs_node { + result.inline_inner_docs_start = Some(TextSize::of(&result.docs)); + extend_with_attrs(&mut result, inner_attrs_node, true, &mut indent); + } + result.remove_indent(indent, inline_source_map_start); + + result.remove_last_newline(); + + result.shrink_to_fit(); + + if result.docs.is_empty() { None } else { Some(Box::new(result)) } +} + +#[salsa::tracked] +impl AttrFlags { + #[salsa::tracked] + pub fn query(db: &dyn DefDatabase, owner: AttrDefId) -> AttrFlags { + let mut attr_flags = AttrFlags::empty(); + collect_attrs(db, owner, |attr| match_attr_flags(&mut attr_flags, attr)); + attr_flags + } + + #[inline] + pub fn query_field(db: &dyn DefDatabase, field: FieldId) -> AttrFlags { + return field_attr_flags(db, field.parent) + .get(field.local_id) + .copied() + .unwrap_or_else(AttrFlags::empty); + + #[salsa::tracked(returns(ref))] + fn field_attr_flags( + db: &dyn DefDatabase, + variant: VariantId, + ) -> ArenaMap { + collect_field_attrs(db, variant, |cfg_options, field| { + let mut attr_flags = AttrFlags::empty(); + expand_cfg_attr( + field.value.attrs(), + || cfg_options, + |attr, _, _, _| match_attr_flags(&mut attr_flags, attr), + ); + attr_flags + }) + } + } + + #[inline] + pub fn query_generic_params( + db: &dyn DefDatabase, + def: GenericDefId, + ) -> &(ArenaMap, ArenaMap) + { + let generic_params = GenericParams::new(db, def); + let params_count_excluding_self = + generic_params.len() - usize::from(generic_params.trait_self_param().is_some()); + if params_count_excluding_self == 0 { + return const { &(ArenaMap::new(), ArenaMap::new()) }; + } + return generic_params_attr_flags(db, def); + + #[salsa::tracked(returns(ref))] + fn generic_params_attr_flags( + db: &dyn DefDatabase, + def: GenericDefId, + ) -> (ArenaMap, ArenaMap) + { + let mut lifetimes = ArenaMap::new(); + let mut type_and_consts = ArenaMap::new(); + + let mut cfg_options = None; + let mut cfg_options = + || *cfg_options.get_or_insert_with(|| def.krate(db).cfg_options(db)); + + let lifetimes_source = HasChildSource::::child_source(&def, db); + for (lifetime_id, lifetime) in lifetimes_source.value.iter() { + let mut attr_flags = AttrFlags::empty(); + expand_cfg_attr(lifetime.attrs(), &mut cfg_options, |attr, _, _, _| { + match_attr_flags(&mut attr_flags, attr) + }); + if !attr_flags.is_empty() { + lifetimes.insert(lifetime_id, attr_flags); + } + } + + let type_and_consts_source = + HasChildSource::::child_source(&def, db); + for (type_or_const_id, type_or_const) in type_and_consts_source.value.iter() { + let mut attr_flags = AttrFlags::empty(); + expand_cfg_attr(type_or_const.attrs(), &mut cfg_options, |attr, _, _, _| { + match_attr_flags(&mut attr_flags, attr) + }); + if !attr_flags.is_empty() { + type_and_consts.insert(type_or_const_id, attr_flags); + } + } + + lifetimes.shrink_to_fit(); + type_and_consts.shrink_to_fit(); + (lifetimes, type_and_consts) + } + } + + #[inline] + pub fn query_lifetime_param(db: &dyn DefDatabase, owner: LifetimeParamId) -> AttrFlags { + AttrFlags::query_generic_params(db, owner.parent) + .0 + .get(owner.local_id) + .copied() + .unwrap_or_else(AttrFlags::empty) + } + #[inline] + pub fn query_type_or_const_param(db: &dyn DefDatabase, owner: TypeOrConstParamId) -> AttrFlags { + AttrFlags::query_generic_params(db, owner.parent) + .1 + .get(owner.local_id) + .copied() + .unwrap_or_else(AttrFlags::empty) + } + + pub(crate) fn is_cfg_enabled_for( + owner: &dyn HasAttrs, + cfg_options: &CfgOptions, + ) -> Result<(), CfgExpr> { + let attrs = ast::attrs_including_inner(owner); + let result = expand_cfg_attr( + attrs, + || cfg_options, + |attr, _, _, _| { + if let Meta::TokenTree { path, tt } = attr + && path.is1("cfg") + && let cfg = + CfgExpr::parse_from_ast(&mut TokenTreeChildren::new(&tt).peekable()) + && cfg_options.check(&cfg) == Some(false) + { + ControlFlow::Break(cfg) + } else { + ControlFlow::Continue(()) + } + }, + ); + match result { + Some(cfg) => Err(cfg), + None => Ok(()), + } + } + + #[inline] + pub fn lang_item(db: &dyn DefDatabase, owner: AttrDefId) -> Option { + AttrFlags::query(db, owner).lang_item_with_attrs(db, owner) + } + + #[inline] + pub fn lang_item_with_attrs(self, db: &dyn DefDatabase, owner: AttrDefId) -> Option { + if !self.contains(AttrFlags::LANG_ITEM) { + // Don't create the query in case this is not a lang item, this wastes memory. + return None; + } + + return lang_item(db, owner); + + #[salsa::tracked] + fn lang_item(db: &dyn DefDatabase, owner: AttrDefId) -> Option { + collect_attrs(db, owner, |attr| { + if let Meta::NamedKeyValue { name: Some(name), value: Some(value), .. } = attr + && name.text() == "lang" + && let Some(value) = ast::String::cast(value) + && let Ok(value) = value.value() + && let symbol = Symbol::intern(&value) + && let Some(lang_item) = LangItem::from_symbol(&symbol) + { + ControlFlow::Break(lang_item) + } else { + ControlFlow::Continue(()) + } + }) + } + } + + #[inline] + pub fn repr(db: &dyn DefDatabase, owner: AdtId) -> Option { + if !AttrFlags::query(db, owner.into()).contains(AttrFlags::HAS_REPR) { + // Don't create the query in case this has no repr, this wastes memory. + return None; + } + + return repr(db, owner); + + #[salsa::tracked] + fn repr(db: &dyn DefDatabase, owner: AdtId) -> Option { + let mut result = None; + collect_attrs::(db, owner.into(), |attr| { + if let Meta::TokenTree { path, tt } = attr + && path.is1("repr") + && let Some(repr) = parse_repr_tt(&tt) + { + match &mut result { + Some(existing) => merge_repr(existing, repr), + None => result = Some(repr), + } + } + ControlFlow::Continue(()) + }); + result + } + } + + /// Call this only if there are legacy const generics, to save memory. + #[salsa::tracked(returns(ref))] + pub(crate) fn legacy_const_generic_indices( + db: &dyn DefDatabase, + owner: FunctionId, + ) -> Option> { + let result = collect_attrs(db, owner.into(), |attr| { + if let Meta::TokenTree { path, tt } = attr + && path.is1("rustc_legacy_const_generics") + { + let result = parse_rustc_legacy_const_generics(tt); + ControlFlow::Break(result) + } else { + ControlFlow::Continue(()) + } + }); + result.filter(|it| !it.is_empty()) + } + + // There aren't typically many crates, so it's okay to always make this a query without a flag. + #[salsa::tracked(returns(ref))] + pub fn doc_html_root_url(db: &dyn DefDatabase, krate: Crate) -> Option { + let root_file_id = krate.root_file_id(db); + let syntax = db.parse(root_file_id).tree(); + + let mut cfg_options = None; + expand_cfg_attr( + syntax.attrs(), + || cfg_options.get_or_insert(krate.cfg_options(db)), + |attr, _, _, _| { + if let Meta::TokenTree { path, tt } = attr + && path.is1("doc") + && let Some(result) = DocAtom::parse(tt).into_iter().find_map(|atom| { + if let DocAtom::KeyValue { key, value } = atom + && key == "html_root_url" + { + Some(value) + } else { + None + } + }) + { + ControlFlow::Break(result) + } else { + ControlFlow::Continue(()) + } + }, + ) + } + + #[inline] + pub fn target_features(db: &dyn DefDatabase, owner: FunctionId) -> &FxHashSet { + if !AttrFlags::query(db, owner.into()).contains(AttrFlags::HAS_TARGET_FEATURE) { + return const { &FxHashSet::with_hasher(rustc_hash::FxBuildHasher) }; + } + + return target_features(db, owner); + + #[salsa::tracked(returns(ref))] + fn target_features(db: &dyn DefDatabase, owner: FunctionId) -> FxHashSet { + let mut result = FxHashSet::default(); + collect_attrs::(db, owner.into(), |attr| { + if let Meta::TokenTree { path, tt } = attr + && path.is1("target_feature") + && let mut tt = TokenTreeChildren::new(&tt) + && let Some(NodeOrToken::Token(enable_ident)) = tt.next() + && enable_ident.text() == "enable" + && let Some(NodeOrToken::Token(eq_token)) = tt.next() + && eq_token.kind() == T![=] + && let Some(NodeOrToken::Token(features)) = tt.next() + && let Some(features) = ast::String::cast(features) + && let Ok(features) = features.value() + && tt.next().is_none() + { + result.extend(features.split(',').map(Symbol::intern)); + } + ControlFlow::Continue(()) + }); + result.shrink_to_fit(); + result + } + } + + #[inline] + pub fn rustc_layout_scalar_valid_range( + db: &dyn DefDatabase, + owner: AdtId, + ) -> RustcLayoutScalarValidRange { + if !AttrFlags::query(db, owner.into()).contains(AttrFlags::RUSTC_LAYOUT_SCALAR_VALID_RANGE) + { + return RustcLayoutScalarValidRange::default(); + } + + return rustc_layout_scalar_valid_range(db, owner); + + #[salsa::tracked] + fn rustc_layout_scalar_valid_range( + db: &dyn DefDatabase, + owner: AdtId, + ) -> RustcLayoutScalarValidRange { + let mut result = RustcLayoutScalarValidRange::default(); + collect_attrs::(db, owner.into(), |attr| { + if let Meta::TokenTree { path, tt } = attr + && (path.is1("rustc_layout_scalar_valid_range_start") + || path.is1("rustc_layout_scalar_valid_range_end")) + && let tt = TokenTreeChildren::new(&tt) + && let Ok(NodeOrToken::Token(value)) = tt.exactly_one() + && let Some(value) = ast::IntNumber::cast(value) + && let Ok(value) = value.value() + { + if path.is1("rustc_layout_scalar_valid_range_start") { + result.start = Some(value) + } else { + result.end = Some(value); + } + } + ControlFlow::Continue(()) + }); + result + } + } + + #[inline] + pub fn doc_aliases(self, db: &dyn DefDatabase, owner: Either) -> &[Symbol] { + if !self.contains(AttrFlags::HAS_DOC_ALIASES) { + return &[]; + } + return match owner { + Either::Left(it) => doc_aliases(db, it), + Either::Right(field) => fields_doc_aliases(db, field.parent) + .get(field.local_id) + .map(|it| &**it) + .unwrap_or_default(), + }; + + #[salsa::tracked(returns(ref))] + fn doc_aliases(db: &dyn DefDatabase, owner: AttrDefId) -> Box<[Symbol]> { + let mut result = Vec::new(); + collect_attrs::(db, owner, |attr| extract_doc_aliases(&mut result, attr)); + result.into_boxed_slice() + } + + #[salsa::tracked(returns(ref))] + fn fields_doc_aliases( + db: &dyn DefDatabase, + variant: VariantId, + ) -> ArenaMap> { + collect_field_attrs(db, variant, |cfg_options, field| { + let mut result = Vec::new(); + expand_cfg_attr( + field.value.attrs(), + || cfg_options, + |attr, _, _, _| extract_doc_aliases(&mut result, attr), + ); + result.into_boxed_slice() + }) + } + } + + #[inline] + pub fn cfgs(self, db: &dyn DefDatabase, owner: Either) -> Option<&CfgExpr> { + if !self.contains(AttrFlags::HAS_CFG) { + return None; + } + return match owner { + Either::Left(it) => cfgs(db, it).as_ref(), + Either::Right(field) => { + fields_cfgs(db, field.parent).get(field.local_id).and_then(|it| it.as_ref()) + } + }; + + // We LRU this query because it is only used by IDE. + #[salsa::tracked(returns(ref), lru = 250)] + fn cfgs(db: &dyn DefDatabase, owner: AttrDefId) -> Option { + let mut result = Vec::new(); + collect_attrs::(db, owner, |attr| extract_cfgs(&mut result, attr)); + match result.len() { + 0 => None, + 1 => result.into_iter().next(), + _ => Some(CfgExpr::All(result.into_boxed_slice())), + } + } + + // We LRU this query because it is only used by IDE. + #[salsa::tracked(returns(ref), lru = 50)] + fn fields_cfgs( + db: &dyn DefDatabase, + variant: VariantId, + ) -> ArenaMap> { + collect_field_attrs(db, variant, |cfg_options, field| { + let mut result = Vec::new(); + expand_cfg_attr( + field.value.attrs(), + || cfg_options, + |attr, _, _, _| extract_cfgs(&mut result, attr), + ); + match result.len() { + 0 => None, + 1 => result.into_iter().next(), + _ => Some(CfgExpr::All(result.into_boxed_slice())), + } + }) + } + } + + #[inline] + pub fn doc_keyword(db: &dyn DefDatabase, owner: InternedModuleId) -> Option { + if !AttrFlags::query(db, AttrDefId::ModuleId(owner)).contains(AttrFlags::HAS_DOC_KEYWORD) { + return None; + } + return doc_keyword(db, owner); + + #[salsa::tracked] + fn doc_keyword(db: &dyn DefDatabase, owner: InternedModuleId) -> Option { + collect_attrs(db, AttrDefId::ModuleId(owner), |attr| { + if let Meta::TokenTree { path, tt } = attr + && path.is1("doc") + { + for atom in DocAtom::parse(tt) { + if let DocAtom::KeyValue { key, value } = atom + && key == "keyword" + { + return ControlFlow::Break(Symbol::intern(&value)); + } + } + } + ControlFlow::Continue(()) + }) + } + } + + // We LRU this query because it is only used by IDE. + #[salsa::tracked(returns(ref), lru = 250)] + pub fn docs(db: &dyn DefDatabase, owner: AttrDefId) -> Option> { + let (source, outer_mod_decl, krate) = attrs_source(db, owner); + let inner_attrs_node = source.value.inner_attributes_node(); + extract_docs(&|| krate.cfg_options(db), source, outer_mod_decl, inner_attrs_node) + } + + #[inline] + pub fn field_docs(db: &dyn DefDatabase, field: FieldId) -> Option<&Docs> { + return fields_docs(db, field.parent).get(field.local_id).and_then(|it| it.as_deref()); + + // We LRU this query because it is only used by IDE. + #[salsa::tracked(returns(ref), lru = 50)] + pub fn fields_docs( + db: &dyn DefDatabase, + variant: VariantId, + ) -> ArenaMap>> { + collect_field_attrs(db, variant, |cfg_options, field| { + extract_docs(&|| cfg_options, field, None, None) + }) + } + } + + #[inline] + pub fn derive_info(db: &dyn DefDatabase, owner: MacroId) -> Option<&DeriveInfo> { + if !AttrFlags::query(db, owner.into()).contains(AttrFlags::IS_DERIVE_OR_BUILTIN_MACRO) { + return None; + } + + return derive_info(db, owner).as_ref(); + + #[salsa::tracked(returns(ref))] + fn derive_info(db: &dyn DefDatabase, owner: MacroId) -> Option { + collect_attrs(db, owner.into(), |attr| { + if let Meta::TokenTree { path, tt } = attr + && path.segments.len() == 1 + && matches!( + path.segments[0].text(), + "proc_macro_derive" | "rustc_builtin_macro" + ) + && let mut tt = TokenTreeChildren::new(&tt) + && let Some(NodeOrToken::Token(trait_name)) = tt.next() + && trait_name.kind().is_any_identifier() + { + let trait_name = Symbol::intern(trait_name.text()); + + let helpers = if let Some(NodeOrToken::Token(comma)) = tt.next() + && comma.kind() == T![,] + && let Some(NodeOrToken::Token(attributes)) = tt.next() + && attributes.text() == "attributes" + && let Some(NodeOrToken::Node(attributes)) = tt.next() + { + attributes + .syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .filter(|it| it.kind().is_any_identifier()) + .map(|it| Symbol::intern(it.text())) + .collect::>() + } else { + Box::new([]) + }; + + ControlFlow::Break(DeriveInfo { trait_name, helpers }) + } else { + ControlFlow::Continue(()) + } + }) + } + } +} + +fn merge_repr(this: &mut ReprOptions, other: ReprOptions) { + let ReprOptions { int, align, pack, flags, field_shuffle_seed: _ } = this; + flags.insert(other.flags); + *align = (*align).max(other.align); + *pack = match (*pack, other.pack) { + (Some(pack), None) | (None, Some(pack)) => Some(pack), + _ => (*pack).min(other.pack), + }; + if other.int.is_some() { + *int = other.int; + } +} + +fn parse_repr_tt(tt: &ast::TokenTree) -> Option { + use crate::builtin_type::{BuiltinInt, BuiltinUint}; + use rustc_abi::{Align, Integer, IntegerType, ReprFlags, ReprOptions}; + + let mut tts = TokenTreeChildren::new(tt).peekable(); + + let mut acc = ReprOptions::default(); + while let Some(tt) = tts.next() { + let NodeOrToken::Token(ident) = tt else { + continue; + }; + if !ident.kind().is_any_identifier() { + continue; + } + let repr = match ident.text() { + "packed" => { + let pack = if let Some(NodeOrToken::Node(tt)) = tts.peek() { + let tt = tt.clone(); + tts.next(); + let mut tt_iter = TokenTreeChildren::new(&tt); + if let Some(NodeOrToken::Token(lit)) = tt_iter.next() + && let Some(lit) = ast::IntNumber::cast(lit) + && let Ok(lit) = lit.value() + && let Ok(lit) = lit.try_into() + { + lit + } else { + 0 + } + } else { + 0 + }; + let pack = Some(Align::from_bytes(pack).unwrap_or(Align::ONE)); + ReprOptions { pack, ..Default::default() } + } + "align" => { + let mut align = None; + if let Some(NodeOrToken::Node(tt)) = tts.peek() { + let tt = tt.clone(); + tts.next(); + let mut tt_iter = TokenTreeChildren::new(&tt); + if let Some(NodeOrToken::Token(lit)) = tt_iter.next() + && let Some(lit) = ast::IntNumber::cast(lit) + && let Ok(lit) = lit.value() + && let Ok(lit) = lit.try_into() + { + align = Align::from_bytes(lit).ok(); + } + } + ReprOptions { align, ..Default::default() } + } + "C" => ReprOptions { flags: ReprFlags::IS_C, ..Default::default() }, + "transparent" => ReprOptions { flags: ReprFlags::IS_TRANSPARENT, ..Default::default() }, + "simd" => ReprOptions { flags: ReprFlags::IS_SIMD, ..Default::default() }, + repr => { + let mut int = None; + if let Some(builtin) = BuiltinInt::from_suffix(repr) + .map(Either::Left) + .or_else(|| BuiltinUint::from_suffix(repr).map(Either::Right)) + { + int = Some(match builtin { + Either::Left(bi) => match bi { + BuiltinInt::Isize => IntegerType::Pointer(true), + BuiltinInt::I8 => IntegerType::Fixed(Integer::I8, true), + BuiltinInt::I16 => IntegerType::Fixed(Integer::I16, true), + BuiltinInt::I32 => IntegerType::Fixed(Integer::I32, true), + BuiltinInt::I64 => IntegerType::Fixed(Integer::I64, true), + BuiltinInt::I128 => IntegerType::Fixed(Integer::I128, true), + }, + Either::Right(bu) => match bu { + BuiltinUint::Usize => IntegerType::Pointer(false), + BuiltinUint::U8 => IntegerType::Fixed(Integer::I8, false), + BuiltinUint::U16 => IntegerType::Fixed(Integer::I16, false), + BuiltinUint::U32 => IntegerType::Fixed(Integer::I32, false), + BuiltinUint::U64 => IntegerType::Fixed(Integer::I64, false), + BuiltinUint::U128 => IntegerType::Fixed(Integer::I128, false), + }, + }); + } + ReprOptions { int, ..Default::default() } + } + }; + merge_repr(&mut acc, repr); + } + + Some(acc) +} + +fn parse_rustc_legacy_const_generics(tt: ast::TokenTree) -> Box<[u32]> { + TokenTreeChildren::new(&tt) + .filter_map(|param| { + ast::IntNumber::cast(param.into_token()?)?.value().ok()?.try_into().ok() + }) + .collect() +} + +#[derive(Debug)] +enum DocAtom { + /// eg. `#[doc(hidden)]` + Flag(SmolStr), + /// eg. `#[doc(alias = "it")]` + /// + /// Note that a key can have multiple values that are all considered "active" at the same time. + /// For example, `#[doc(alias = "x")]` and `#[doc(alias = "y")]`. + KeyValue { key: SmolStr, value: SmolStr }, + /// eg. `#[doc(alias("x", "y"))]` + Alias(Vec), +} + +impl DocAtom { + fn parse(tt: ast::TokenTree) -> SmallVec<[DocAtom; 1]> { + let mut iter = TokenTreeChildren::new(&tt).peekable(); + let mut result = SmallVec::new(); + while iter.peek().is_some() { + if let Some(expr) = next_doc_expr(&mut iter) { + result.push(expr); + } + } + result + } +} + +fn next_doc_expr(it: &mut Peekable) -> Option { + let name = match it.next() { + Some(NodeOrToken::Token(token)) if token.kind().is_any_identifier() => { + SmolStr::new(token.text()) + } + _ => return None, + }; + + let ret = match it.peek() { + Some(NodeOrToken::Token(eq)) if eq.kind() == T![=] => { + it.next(); + if let Some(NodeOrToken::Token(value)) = it.next() + && let Some(value) = ast::String::cast(value) + && let Ok(value) = value.value() + { + DocAtom::KeyValue { key: name, value: SmolStr::new(&*value) } + } else { + return None; + } + } + Some(NodeOrToken::Node(subtree)) => { + if name != "alias" { + return None; + } + let aliases = TokenTreeChildren::new(subtree) + .filter_map(|alias| { + Some(SmolStr::new(&*ast::String::cast(alias.into_token()?)?.value().ok()?)) + }) + .collect(); + it.next(); + DocAtom::Alias(aliases) + } + _ => DocAtom::Flag(name), + }; + Some(ret) +} + +#[cfg(test)] +mod tests { + use expect_test::expect; + use hir_expand::InFile; + use test_fixture::WithFixture; + use tt::{TextRange, TextSize}; + + use crate::attrs::IsInnerDoc; + use crate::{attrs::Docs, test_db::TestDB}; + + #[test] + fn docs() { + let (_db, file_id) = TestDB::with_single_file(""); + let mut docs = Docs { + docs: String::new(), + docs_source_map: Vec::new(), + outline_mod: None, + inline_file: file_id.into(), + prefix_len: TextSize::new(0), + inline_inner_docs_start: None, + outline_inner_docs_start: None, + }; + let mut indent = usize::MAX; + + let outer = " foo\n\tbar baz"; + let mut ast_offset = TextSize::new(123); + for line in outer.split('\n') { + docs.extend_with_doc_str(line, ast_offset, &mut indent); + ast_offset += TextSize::of(line) + TextSize::of("\n"); + } + + docs.inline_inner_docs_start = Some(TextSize::of(&docs.docs)); + ast_offset += TextSize::new(123); + let inner = " bar \n baz"; + for line in inner.split('\n') { + docs.extend_with_doc_str(line, ast_offset, &mut indent); + ast_offset += TextSize::of(line) + TextSize::of("\n"); + } + + assert_eq!(indent, 1); + expect![[r#" + [ + DocsSourceMapLine { + string_offset: 0, + ast_offset: 123, + }, + DocsSourceMapLine { + string_offset: 5, + ast_offset: 128, + }, + DocsSourceMapLine { + string_offset: 15, + ast_offset: 261, + }, + DocsSourceMapLine { + string_offset: 20, + ast_offset: 267, + }, + ] + "#]] + .assert_debug_eq(&docs.docs_source_map); + + docs.remove_indent(indent, 0); + + assert_eq!(docs.inline_inner_docs_start, Some(TextSize::new(13))); + + assert_eq!(docs.docs, "foo\nbar baz\nbar\nbaz\n"); + expect![[r#" + [ + DocsSourceMapLine { + string_offset: 0, + ast_offset: 124, + }, + DocsSourceMapLine { + string_offset: 4, + ast_offset: 129, + }, + DocsSourceMapLine { + string_offset: 13, + ast_offset: 262, + }, + DocsSourceMapLine { + string_offset: 17, + ast_offset: 268, + }, + ] + "#]] + .assert_debug_eq(&docs.docs_source_map); + + docs.append(&docs.clone()); + docs.prepend_str("prefix---"); + assert_eq!(docs.docs, "prefix---foo\nbar baz\nbar\nbaz\nfoo\nbar baz\nbar\nbaz\n"); + expect![[r#" + [ + DocsSourceMapLine { + string_offset: 0, + ast_offset: 124, + }, + DocsSourceMapLine { + string_offset: 4, + ast_offset: 129, + }, + DocsSourceMapLine { + string_offset: 13, + ast_offset: 262, + }, + DocsSourceMapLine { + string_offset: 17, + ast_offset: 268, + }, + DocsSourceMapLine { + string_offset: 21, + ast_offset: 124, + }, + DocsSourceMapLine { + string_offset: 25, + ast_offset: 129, + }, + DocsSourceMapLine { + string_offset: 34, + ast_offset: 262, + }, + DocsSourceMapLine { + string_offset: 38, + ast_offset: 268, + }, + ] + "#]] + .assert_debug_eq(&docs.docs_source_map); + + let range = |start, end| TextRange::new(TextSize::new(start), TextSize::new(end)); + let in_file = |range| InFile::new(file_id.into(), range); + assert_eq!(docs.find_ast_range(range(0, 2)), None); + assert_eq!(docs.find_ast_range(range(8, 10)), None); + assert_eq!( + docs.find_ast_range(range(9, 10)), + Some((in_file(range(124, 125)), IsInnerDoc::No)) + ); + assert_eq!(docs.find_ast_range(range(20, 23)), None); + assert_eq!( + docs.find_ast_range(range(23, 25)), + Some((in_file(range(263, 265)), IsInnerDoc::Yes)) + ); + } +} diff --git a/crates/hir-def/src/db.rs b/crates/hir-def/src/db.rs index 4e1d598623ab..ad2990087672 100644 --- a/crates/hir-def/src/db.rs +++ b/crates/hir-def/src/db.rs @@ -1,23 +1,21 @@ //! Defines database & queries for name resolution. use base_db::{Crate, RootQueryDb, SourceDatabase}; -use either::Either; use hir_expand::{ EditionedFileId, HirFileId, InFile, Lookup, MacroCallId, MacroDefId, MacroDefKind, db::ExpandDatabase, }; -use intern::sym; use la_arena::ArenaMap; -use syntax::{AstPtr, ast}; use triomphe::Arc; use crate::{ - AssocItemId, AttrDefId, BlockId, BlockLoc, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, - EnumVariantId, EnumVariantLoc, ExternBlockId, ExternBlockLoc, ExternCrateId, ExternCrateLoc, - FunctionId, FunctionLoc, GenericDefId, ImplId, ImplLoc, LocalFieldId, Macro2Id, Macro2Loc, - MacroExpander, MacroId, MacroRulesId, MacroRulesLoc, MacroRulesLocFlags, ProcMacroId, - ProcMacroLoc, StaticId, StaticLoc, StructId, StructLoc, TraitId, TraitLoc, TypeAliasId, - TypeAliasLoc, UnionId, UnionLoc, UseId, UseLoc, VariantId, - attr::{Attrs, AttrsWithOwner}, + AssocItemId, AttrDefId, BlockId, BlockLoc, ConstId, ConstLoc, CrateRootModuleId, DefWithBodyId, + EnumId, EnumLoc, EnumVariantId, EnumVariantLoc, ExternBlockId, ExternBlockLoc, ExternCrateId, + ExternCrateLoc, FunctionId, FunctionLoc, GenericDefId, HasModule, ImplId, ImplLoc, + InternedModuleId, LocalFieldId, Macro2Id, Macro2Loc, MacroExpander, MacroId, MacroRulesId, + MacroRulesLoc, MacroRulesLocFlags, ProcMacroId, ProcMacroLoc, StaticId, StaticLoc, StructId, + StructLoc, TraitId, TraitLoc, TypeAliasId, TypeAliasLoc, UnionId, UnionLoc, UseId, UseLoc, + VariantId, + attrs::AttrFlags, expr_store::{ Body, BodySourceMap, ExpressionStore, ExpressionStoreSourceMap, scope::ExprScopes, }, @@ -30,7 +28,6 @@ use crate::{ ConstSignature, EnumSignature, FunctionSignature, ImplSignature, StaticSignature, StructSignature, TraitSignature, TypeAliasSignature, UnionSignature, }, - tt, visibility::{self, Visibility}, }; @@ -238,28 +235,11 @@ pub trait DefDatabase: InternDatabase + ExpandDatabase + SourceDatabase { def: GenericDefId, ) -> (Arc, Arc, Arc); - // region:attrs - - #[salsa::invoke(Attrs::fields_attrs_query)] - fn fields_attrs(&self, def: VariantId) -> Arc>; - - // should this really be a query? - #[salsa::invoke(crate::attr::fields_attrs_source_map)] - fn fields_attrs_source_map( - &self, - def: VariantId, - ) -> Arc>>>; - - // FIXME: Make this a non-interned query. - #[salsa::invoke_interned(AttrsWithOwner::attrs_query)] - fn attrs(&self, def: AttrDefId) -> Attrs; - + // FIXME: Get rid of this, call `AttrFlags::lang_item()` directly. #[salsa::transparent] #[salsa::invoke(lang_item::lang_attr)] fn lang_attr(&self, def: AttrDefId) -> Option; - // endregion:attrs - #[salsa::invoke(ImportMap::import_map_query)] fn import_map(&self, krate: Crate) -> Arc; @@ -303,36 +283,9 @@ fn include_macro_invoc( } fn crate_supports_no_std(db: &dyn DefDatabase, crate_id: Crate) -> bool { - let file = crate_id.data(db).root_file_id(db); - let item_tree = db.file_item_tree(file.into()); - let attrs = item_tree.top_level_raw_attrs(); - for attr in &**attrs { - match attr.path().as_ident() { - Some(ident) if *ident == sym::no_std => return true, - Some(ident) if *ident == sym::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| matches!(tt, tt::TtElement::Leaf(tt::Leaf::Punct(p)) if p.char == ',')); - for output in segments.skip(1) { - match output.flat_tokens() { - [tt::TokenTree::Leaf(tt::Leaf::Ident(ident))] if ident.sym == sym::no_std => { - return true; - } - _ => {} - } - } - } - - false + let root_module = CrateRootModuleId::from(crate_id).module(db); + let attrs = AttrFlags::query(db, AttrDefId::ModuleId(InternedModuleId::new(db, root_module))); + attrs.contains(AttrFlags::IS_NO_STD) } fn macro_def(db: &dyn DefDatabase, id: MacroId) -> MacroDefId { diff --git a/crates/hir-def/src/expr_store/expander.rs b/crates/hir-def/src/expr_store/expander.rs index 23b9712d1e6c..6a2f06b0a6f6 100644 --- a/crates/hir-def/src/expr_store/expander.rs +++ b/crates/hir-def/src/expr_store/expander.rs @@ -17,11 +17,10 @@ use syntax::{AstNode, Parse, ast}; use triomphe::Arc; use tt::TextRange; -use crate::attr::Attrs; -use crate::expr_store::HygieneId; -use crate::macro_call_as_call_id; -use crate::nameres::DefMap; -use crate::{MacroId, UnresolvedMacro, db::DefDatabase}; +use crate::{ + MacroId, UnresolvedMacro, attrs::AttrFlags, db::DefDatabase, expr_store::HygieneId, + macro_call_as_call_id, nameres::DefMap, +}; #[derive(Debug)] pub(super) struct Expander { @@ -70,11 +69,10 @@ impl Expander { pub(super) fn is_cfg_enabled( &self, - db: &dyn DefDatabase, - has_attrs: &dyn HasAttrs, + owner: &dyn HasAttrs, cfg_options: &CfgOptions, ) -> Result<(), cfg::CfgExpr> { - Attrs::is_cfg_enabled_for(db, has_attrs, self.span_map.as_ref(), cfg_options) + AttrFlags::is_cfg_enabled_for(owner, cfg_options) } pub(super) fn call_syntax_ctx(&self) -> SyntaxContext { diff --git a/crates/hir-def/src/expr_store/lower.rs b/crates/hir-def/src/expr_store/lower.rs index 3794cb18e936..fbe0b1ab9596 100644 --- a/crates/hir-def/src/expr_store/lower.rs +++ b/crates/hir-def/src/expr_store/lower.rs @@ -12,7 +12,6 @@ use cfg::CfgOptions; use either::Either; use hir_expand::{ HirFileId, InFile, MacroDefId, - mod_path::tool_path, name::{AsName, Name}, span_map::SpanMapRef, }; @@ -34,6 +33,7 @@ use tt::TextRange; use crate::{ AdtId, BlockId, BlockLoc, DefWithBodyId, FunctionId, GenericDefId, ImplId, MacroId, ModuleDefId, ModuleId, TraitId, TypeAliasId, UnresolvedMacro, + attrs::AttrFlags, builtin_type::BuiltinUint, db::DefDatabase, expr_store::{ @@ -87,14 +87,16 @@ pub(super) fn lower_body( let mut params = vec![]; let mut collector = ExprCollector::new(db, module, current_file_id); - let skip_body = match owner { - DefWithBodyId::FunctionId(it) => db.attrs(it.into()), - DefWithBodyId::StaticId(it) => db.attrs(it.into()), - DefWithBodyId::ConstId(it) => db.attrs(it.into()), - DefWithBodyId::VariantId(it) => db.attrs(it.into()), - } - .rust_analyzer_tool() - .any(|attr| *attr.path() == tool_path![skip]); + let skip_body = AttrFlags::query( + db, + match owner { + DefWithBodyId::FunctionId(it) => it.into(), + DefWithBodyId::StaticId(it) => it.into(), + DefWithBodyId::ConstId(it) => it.into(), + DefWithBodyId::VariantId(it) => it.into(), + }, + ) + .contains(AttrFlags::RUST_ANALYZER_SKIP); // If #[rust_analyzer::skip] annotated, only construct enough information for the signature // and skip the body. if skip_body { @@ -2485,7 +2487,7 @@ impl ExprCollector<'_> { /// Returns `None` (and emits diagnostics) when `owner` if `#[cfg]`d out, and `Some(())` when /// not. fn check_cfg(&mut self, owner: &dyn ast::HasAttrs) -> bool { - let enabled = self.expander.is_cfg_enabled(self.db, owner, self.cfg_options); + let enabled = self.expander.is_cfg_enabled(owner, self.cfg_options); match enabled { Ok(()) => true, Err(cfg) => { diff --git a/crates/hir-def/src/expr_store/pretty.rs b/crates/hir-def/src/expr_store/pretty.rs index 5b9da3c5e668..e386e8d0c596 100644 --- a/crates/hir-def/src/expr_store/pretty.rs +++ b/crates/hir-def/src/expr_store/pretty.rs @@ -12,7 +12,8 @@ use span::Edition; use syntax::ast::HasName; use crate::{ - AdtId, DefWithBodyId, GenericDefId, TypeParamId, VariantId, + AdtId, DefWithBodyId, FunctionId, GenericDefId, StructId, TypeParamId, VariantId, + attrs::AttrFlags, expr_store::path::{GenericArg, GenericArgs}, hir::{ Array, BindingAnnotation, CaptureBy, ClosureKind, Literal, Movability, Statement, @@ -167,7 +168,7 @@ pub fn print_signature(db: &dyn DefDatabase, owner: GenericDefId, edition: Editi GenericDefId::AdtId(id) => match id { AdtId::StructId(id) => { let signature = db.struct_signature(id); - print_struct(db, &signature, edition) + print_struct(db, id, &signature, edition) } AdtId::UnionId(id) => { format!("unimplemented {id:?}") @@ -179,7 +180,7 @@ pub fn print_signature(db: &dyn DefDatabase, owner: GenericDefId, edition: Editi GenericDefId::ConstId(id) => format!("unimplemented {id:?}"), GenericDefId::FunctionId(id) => { let signature = db.function_signature(id); - print_function(db, &signature, edition) + print_function(db, id, &signature, edition) } GenericDefId::ImplId(id) => format!("unimplemented {id:?}"), GenericDefId::StaticId(id) => format!("unimplemented {id:?}"), @@ -208,7 +209,8 @@ pub fn print_path( pub fn print_struct( db: &dyn DefDatabase, - StructSignature { name, generic_params, store, flags, shape, repr }: &StructSignature, + id: StructId, + StructSignature { name, generic_params, store, flags, shape }: &StructSignature, edition: Edition, ) -> String { let mut p = Printer { @@ -219,7 +221,7 @@ pub fn print_struct( line_format: LineFormat::Newline, edition, }; - if let Some(repr) = repr { + if let Some(repr) = AttrFlags::repr(db, id.into()) { if repr.c() { wln!(p, "#[repr(C)]"); } @@ -255,7 +257,8 @@ pub fn print_struct( pub fn print_function( db: &dyn DefDatabase, - FunctionSignature { + id: FunctionId, + signature @ FunctionSignature { name, generic_params, store, @@ -263,10 +266,10 @@ pub fn print_function( ret_type, abi, flags, - legacy_const_generics_indices, }: &FunctionSignature, edition: Edition, ) -> String { + let legacy_const_generics_indices = signature.legacy_const_generics_indices(db, id); let mut p = Printer { db, store, @@ -298,7 +301,7 @@ pub fn print_function( if i != 0 { w!(p, ", "); } - if legacy_const_generics_indices.as_ref().is_some_and(|idx| idx.contains(&(i as u32))) { + if legacy_const_generics_indices.is_some_and(|idx| idx.contains(&(i as u32))) { w!(p, "const: "); } p.print_type_ref(*param); diff --git a/crates/hir-def/src/expr_store/tests/body/block.rs b/crates/hir-def/src/expr_store/tests/body/block.rs index c7707378a5b3..0cb9325b502e 100644 --- a/crates/hir-def/src/expr_store/tests/body/block.rs +++ b/crates/hir-def/src/expr_store/tests/body/block.rs @@ -189,8 +189,8 @@ fn f() { } "#, expect![[r#" - BlockId(3c01) in BlockRelativeModuleId { block: Some(BlockId(3c00)), local_id: Idx::(1) } - BlockId(3c00) in BlockRelativeModuleId { block: None, local_id: Idx::(0) } + BlockId(3801) in BlockRelativeModuleId { block: Some(BlockId(3800)), local_id: Idx::(1) } + BlockId(3800) in BlockRelativeModuleId { block: None, local_id: Idx::(0) } crate scope "#]], ); diff --git a/crates/hir-def/src/expr_store/tests/signatures.rs b/crates/hir-def/src/expr_store/tests/signatures.rs index b68674c7a74f..2dac4e7fc84b 100644 --- a/crates/hir-def/src/expr_store/tests/signatures.rs +++ b/crates/hir-def/src/expr_store/tests/signatures.rs @@ -38,14 +38,24 @@ fn lower_and_print(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expe match def { GenericDefId::AdtId(adt_id) => match adt_id { crate::AdtId::StructId(struct_id) => { - out += &print_struct(&db, &db.struct_signature(struct_id), Edition::CURRENT); + out += &print_struct( + &db, + struct_id, + &db.struct_signature(struct_id), + Edition::CURRENT, + ); } crate::AdtId::UnionId(_id) => (), crate::AdtId::EnumId(_id) => (), }, GenericDefId::ConstId(_id) => (), GenericDefId::FunctionId(function_id) => { - out += &print_function(&db, &db.function_signature(function_id), Edition::CURRENT) + out += &print_function( + &db, + function_id, + &db.function_signature(function_id), + Edition::CURRENT, + ) } GenericDefId::ImplId(_id) => (), diff --git a/crates/hir-def/src/import_map.rs b/crates/hir-def/src/import_map.rs index f31f355cfa5d..67cf466276c5 100644 --- a/crates/hir-def/src/import_map.rs +++ b/crates/hir-def/src/import_map.rs @@ -13,7 +13,8 @@ use stdx::format_to; use triomphe::Arc; use crate::{ - AssocItemId, AttrDefId, Complete, FxIndexMap, ModuleDefId, ModuleId, TraitId, + AssocItemId, AttrDefId, Complete, FxIndexMap, InternedModuleId, ModuleDefId, ModuleId, TraitId, + attrs::AttrFlags, db::DefDatabase, item_scope::{ImportOrExternCrate, ItemInNs}, nameres::{DefMap, assoc::TraitItems, crate_def_map}, @@ -165,17 +166,34 @@ impl ImportMap { } } else { match item { - ItemInNs::Types(id) | ItemInNs::Values(id) => id.try_into().ok(), + ItemInNs::Types(id) | ItemInNs::Values(id) => match id { + ModuleDefId::ModuleId(it) => { + Some(AttrDefId::ModuleId(InternedModuleId::new(db, it))) + } + ModuleDefId::FunctionId(it) => Some(it.into()), + ModuleDefId::AdtId(it) => Some(it.into()), + ModuleDefId::EnumVariantId(it) => Some(it.into()), + ModuleDefId::ConstId(it) => Some(it.into()), + ModuleDefId::StaticId(it) => Some(it.into()), + ModuleDefId::TraitId(it) => Some(it.into()), + ModuleDefId::TypeAliasId(it) => Some(it.into()), + ModuleDefId::MacroId(it) => Some(it.into()), + ModuleDefId::BuiltinType(_) => None, + }, ItemInNs::Macros(id) => Some(id.into()), } }; let (is_doc_hidden, is_unstable, do_not_complete) = match attr_id { None => (false, false, Complete::Yes), Some(attr_id) => { - let attrs = db.attrs(attr_id); + let attrs = AttrFlags::query(db, attr_id); let do_not_complete = - Complete::extract(matches!(attr_id, AttrDefId::TraitId(_)), &attrs); - (attrs.has_doc_hidden(), attrs.is_unstable(), do_not_complete) + Complete::extract(matches!(attr_id, AttrDefId::TraitId(_)), attrs); + ( + attrs.contains(AttrFlags::IS_DOC_HIDDEN), + attrs.contains(AttrFlags::IS_UNSTABLE), + do_not_complete, + ) } }; @@ -239,15 +257,15 @@ impl ImportMap { }; let attr_id = item.into(); - let attrs = &db.attrs(attr_id); + let attrs = AttrFlags::query(db, attr_id); let item_do_not_complete = Complete::extract(false, attrs); let do_not_complete = Complete::for_trait_item(trait_import_info.complete, item_do_not_complete); let assoc_item_info = ImportInfo { container: trait_import_info.container, name: assoc_item_name.clone(), - is_doc_hidden: attrs.has_doc_hidden(), - is_unstable: attrs.is_unstable(), + is_doc_hidden: attrs.contains(AttrFlags::IS_DOC_HIDDEN), + is_unstable: attrs.contains(AttrFlags::IS_UNSTABLE), complete: do_not_complete, }; diff --git a/crates/hir-def/src/item_tree.rs b/crates/hir-def/src/item_tree.rs index f35df8d3a7e1..2a104fff2b92 100644 --- a/crates/hir-def/src/item_tree.rs +++ b/crates/hir-def/src/item_tree.rs @@ -30,6 +30,7 @@ //! In general, any item in the `ItemTree` stores its `AstId`, which allows mapping it back to its //! surface syntax. +mod attrs; mod lower; mod pretty; #[cfg(test)] @@ -43,10 +44,8 @@ use std::{ }; use ast::{AstNode, StructKind}; -use base_db::Crate; use hir_expand::{ ExpandTo, HirFileId, - attrs::RawAttrs, mod_path::{ModPath, PathKind}, name::Name, }; @@ -59,9 +58,12 @@ use syntax::{SyntaxKind, ast, match_ast}; use thin_vec::ThinVec; use triomphe::Arc; -use crate::{BlockId, Lookup, attr::Attrs, db::DefDatabase}; +use crate::{BlockId, Lookup, db::DefDatabase}; -pub(crate) use crate::item_tree::lower::{lower_use_tree, visibility_from_ast}; +pub(crate) use crate::item_tree::{ + attrs::*, + lower::{lower_use_tree, visibility_from_ast}, +}; #[derive(Copy, Clone, Eq, PartialEq)] pub(crate) struct RawVisibilityId(u32); @@ -96,7 +98,7 @@ pub(crate) fn file_item_tree_query(db: &dyn DefDatabase, file_id: HirFileId) -> let mut item_tree = match_ast! { match syntax { ast::SourceFile(file) => { - let top_attrs = RawAttrs::new(db, &file, ctx.span_map()); + let top_attrs = ctx.lower_attrs(&file); let mut item_tree = ctx.lower_module_items(&file); item_tree.top_attrs = top_attrs; item_tree @@ -132,7 +134,7 @@ pub(crate) fn file_item_tree_query(db: &dyn DefDatabase, file_id: HirFileId) -> attrs: FxHashMap::default(), small_data: FxHashMap::default(), big_data: FxHashMap::default(), - top_attrs: RawAttrs::EMPTY, + top_attrs: AttrsOrCfg::empty(), vis: ItemVisibilities { arena: ThinVec::new() }, }) }) @@ -168,7 +170,7 @@ pub(crate) fn block_item_tree_query(db: &dyn DefDatabase, block: BlockId) -> Arc attrs: FxHashMap::default(), small_data: FxHashMap::default(), big_data: FxHashMap::default(), - top_attrs: RawAttrs::EMPTY, + top_attrs: AttrsOrCfg::empty(), vis: ItemVisibilities { arena: ThinVec::new() }, }) }) @@ -182,8 +184,8 @@ pub(crate) fn block_item_tree_query(db: &dyn DefDatabase, block: BlockId) -> Arc #[derive(Debug, Default, Eq, PartialEq)] pub struct ItemTree { top_level: Box<[ModItemId]>, - top_attrs: RawAttrs, - attrs: FxHashMap, RawAttrs>, + top_attrs: AttrsOrCfg, + attrs: FxHashMap, AttrsOrCfg>, vis: ItemVisibilities, big_data: FxHashMap, BigModItem>, small_data: FxHashMap, SmallModItem>, @@ -197,26 +199,12 @@ impl ItemTree { } /// Returns the inner attributes of the source file. - pub(crate) fn top_level_raw_attrs(&self) -> &RawAttrs { + pub(crate) fn top_level_attrs(&self) -> &AttrsOrCfg { &self.top_attrs } - /// Returns the inner attributes of the source file. - pub(crate) fn top_level_attrs(&self, db: &dyn DefDatabase, krate: Crate) -> Attrs { - Attrs::expand_cfg_attr(db, krate, self.top_attrs.clone()) - } - - pub(crate) fn raw_attrs(&self, of: FileAstId) -> &RawAttrs { - self.attrs.get(&of).unwrap_or(&RawAttrs::EMPTY) - } - - pub(crate) fn attrs( - &self, - db: &dyn DefDatabase, - krate: Crate, - of: FileAstId, - ) -> Attrs { - Attrs::expand_cfg_attr(db, krate, self.raw_attrs(of).clone()) + pub(crate) fn attrs(&self, of: FileAstId) -> Option<&AttrsOrCfg> { + self.attrs.get(&of) } /// Returns a count of a few, expensive items. diff --git a/crates/hir-def/src/item_tree/attrs.rs b/crates/hir-def/src/item_tree/attrs.rs new file mode 100644 index 000000000000..5c635a4b3831 --- /dev/null +++ b/crates/hir-def/src/item_tree/attrs.rs @@ -0,0 +1,220 @@ +//! Defines attribute helpers for name resolution. +//! +//! Notice we don't preserve all attributes for name resolution, to save space: +//! for example, we skip doc comments (desugared to `#[doc = "..."]` attributes) +//! and `#[inline]`. The filtered attributes are listed in [`hir_expand::attrs`]. + +use std::{ + borrow::Cow, + convert::Infallible, + ops::{self, ControlFlow}, +}; + +use cfg::{CfgExpr, CfgOptions}; +use either::Either; +use hir_expand::{ + attrs::{Attr, AttrId, AttrInput, Meta, collect_item_tree_attrs}, + mod_path::ModPath, + name::Name, + span_map::SpanMapRef, +}; +use intern::{Interned, Symbol, sym}; +use syntax::{AstNode, T, ast}; +use syntax_bridge::DocCommentDesugarMode; +use tt::token_to_literal; + +use crate::{db::DefDatabase, item_tree::lower::Ctx}; + +#[derive(Debug, PartialEq, Eq)] +pub(crate) enum AttrsOrCfg { + Enabled { + attrs: AttrsOwned, + }, + /// This only collects the attributes up to the disabled `cfg` (this is what needed for crate-level attributes.) + CfgDisabled(Box<(CfgExpr, AttrsOwned)>), +} + +impl Default for AttrsOrCfg { + #[inline] + fn default() -> Self { + AttrsOrCfg::Enabled { attrs: AttrsOwned(Box::new([])) } + } +} + +impl AttrsOrCfg { + pub(crate) fn lower<'a>( + db: &dyn DefDatabase, + owner: &dyn ast::HasAttrs, + cfg_options: &dyn Fn() -> &'a CfgOptions, + span_map: SpanMapRef<'_>, + ) -> AttrsOrCfg { + let mut attrs = Vec::new(); + let result = + collect_item_tree_attrs::(owner, cfg_options, |meta, container, _, _| { + // NOTE: We cannot early return from this function, *every* attribute must be pushed, otherwise we'll mess the `AttrId` + // tracking. + let (span, path_range, input) = match meta { + Meta::NamedKeyValue { path_range, name: _, value } => { + let span = span_map.span_for_range(path_range); + let input = value.map(|value| { + Box::new(AttrInput::Literal(token_to_literal( + value.text(), + span_map.span_for_range(value.text_range()), + ))) + }); + (span, path_range, input) + } + Meta::TokenTree { path, tt } => { + let span = span_map.span_for_range(path.range); + let tt = syntax_bridge::syntax_node_to_token_tree( + tt.syntax(), + span_map, + span, + DocCommentDesugarMode::ProcMacro, + ); + let input = Some(Box::new(AttrInput::TokenTree(tt))); + (span, path.range, input) + } + Meta::Path { path } => { + let span = span_map.span_for_range(path.range); + (span, path.range, None) + } + }; + + let path = container.token_at_offset(path_range.start()).right_biased().and_then( + |first_path_token| { + let is_abs = matches!(first_path_token.kind(), T![:] | T![::]); + let segments = + std::iter::successors(Some(first_path_token), |it| it.next_token()) + .take_while(|it| it.text_range().end() <= path_range.end()) + .filter(|it| it.kind().is_any_identifier()); + ModPath::from_tokens( + db, + &mut |range| span_map.span_for_range(range).ctx, + is_abs, + segments, + ) + }, + ); + let path = path.unwrap_or_else(|| Name::missing().into()); + + attrs.push(Attr { path: Interned::new(path), input, ctxt: span.ctx }); + ControlFlow::Continue(()) + }); + let attrs = AttrsOwned(attrs.into_boxed_slice()); + match result { + Some(Either::Right(cfg)) => AttrsOrCfg::CfgDisabled(Box::new((cfg, attrs))), + None => AttrsOrCfg::Enabled { attrs }, + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub(crate) struct AttrsOwned(Box<[Attr]>); + +#[derive(Debug, Clone, Copy)] +pub(crate) struct Attrs<'a>(&'a [Attr]); + +impl ops::Deref for Attrs<'_> { + type Target = [Attr]; + + #[inline] + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl Ctx<'_> { + #[inline] + pub(super) fn lower_attrs(&self, owner: &dyn ast::HasAttrs) -> AttrsOrCfg { + AttrsOrCfg::lower(self.db, owner, &|| self.cfg_options(), self.span_map()) + } +} + +impl AttrsOwned { + #[inline] + pub(crate) fn as_ref(&self) -> Attrs<'_> { + Attrs(&self.0) + } +} + +impl<'a> Attrs<'a> { + pub(crate) const EMPTY: Self = Attrs(&[]); + + #[inline] + pub(crate) fn by_key(self, key: Symbol) -> AttrQuery<'a> { + AttrQuery { attrs: self, key } + } + + #[inline] + pub(crate) fn iter(self) -> impl Iterator { + self.0.iter().enumerate().map(|(id, attr)| (AttrId::from_item_tree_index(id as u32), attr)) + } + + #[inline] + pub(crate) fn iter_after( + self, + after: Option, + ) -> impl Iterator { + let skip = after.map_or(0, |after| after.item_tree_index() + 1); + self.0[skip as usize..] + .iter() + .enumerate() + .map(move |(id, attr)| (AttrId::from_item_tree_index(id as u32 + skip), attr)) + } + + #[inline] + pub(crate) fn is_proc_macro(&self) -> bool { + self.by_key(sym::proc_macro).exists() + } + + #[inline] + pub(crate) fn is_proc_macro_attribute(&self) -> bool { + self.by_key(sym::proc_macro_attribute).exists() + } +} +#[derive(Debug, Clone)] +pub(crate) struct AttrQuery<'attr> { + attrs: Attrs<'attr>, + key: Symbol, +} + +impl<'attr> AttrQuery<'attr> { + #[inline] + pub(crate) fn tt_values(self) -> impl Iterator { + self.attrs().filter_map(|attr| attr.token_tree_value()) + } + + #[inline] + pub(crate) fn string_value_with_span(self) -> Option<(&'attr Symbol, span::Span)> { + self.attrs().find_map(|attr| attr.string_value_with_span()) + } + + #[inline] + pub(crate) fn string_value_unescape(self) -> Option> { + self.attrs().find_map(|attr| attr.string_value_unescape()) + } + + #[inline] + pub(crate) fn exists(self) -> bool { + self.attrs().next().is_some() + } + + #[inline] + pub(crate) fn attrs(self) -> impl Iterator + Clone { + let key = self.key; + self.attrs.0.iter().filter(move |attr| attr.path.as_ident().is_some_and(|s| *s == key)) + } +} + +impl AttrsOrCfg { + #[inline] + pub(super) fn empty() -> Self { + AttrsOrCfg::Enabled { attrs: AttrsOwned(Box::new([])) } + } + + #[inline] + pub(super) fn is_empty(&self) -> bool { + matches!(self, AttrsOrCfg::Enabled { attrs } if attrs.as_ref().is_empty()) + } +} diff --git a/crates/hir-def/src/item_tree/lower.rs b/crates/hir-def/src/item_tree/lower.rs index 454e06399583..b50a75169158 100644 --- a/crates/hir-def/src/item_tree/lower.rs +++ b/crates/hir-def/src/item_tree/lower.rs @@ -1,8 +1,9 @@ //! AST -> `ItemTree` lowering code. -use std::{cell::OnceCell, collections::hash_map::Entry}; +use std::cell::OnceCell; use base_db::FxIndexSet; +use cfg::CfgOptions; use hir_expand::{ HirFileId, mod_path::PathKind, @@ -22,18 +23,19 @@ use crate::{ item_tree::{ BigModItem, Const, Enum, ExternBlock, ExternCrate, FieldsShape, Function, Impl, ImportAlias, Interned, ItemTree, ItemTreeAstId, Macro2, MacroCall, MacroRules, Mod, - ModItemId, ModKind, ModPath, RawAttrs, RawVisibility, RawVisibilityId, SmallModItem, - Static, Struct, StructKind, Trait, TypeAlias, Union, Use, UseTree, UseTreeKind, - VisibilityExplicitness, + ModItemId, ModKind, ModPath, RawVisibility, RawVisibilityId, SmallModItem, Static, Struct, + StructKind, Trait, TypeAlias, Union, Use, UseTree, UseTreeKind, VisibilityExplicitness, + attrs::AttrsOrCfg, }, }; pub(super) struct Ctx<'a> { - db: &'a dyn DefDatabase, + pub(super) db: &'a dyn DefDatabase, tree: ItemTree, source_ast_id_map: Arc, span_map: OnceCell, file: HirFileId, + cfg_options: OnceCell<&'a CfgOptions>, top_level: Vec, visibilities: FxIndexSet, } @@ -45,12 +47,18 @@ impl<'a> Ctx<'a> { tree: ItemTree::default(), source_ast_id_map: db.ast_id_map(file), file, + cfg_options: OnceCell::new(), span_map: OnceCell::new(), visibilities: FxIndexSet::default(), top_level: Vec::new(), } } + #[inline] + pub(super) fn cfg_options(&self) -> &'a CfgOptions { + self.cfg_options.get_or_init(|| self.file.krate(self.db).cfg_options(self.db)) + } + pub(super) fn span_map(&self) -> SpanMapRef<'_> { self.span_map.get_or_init(|| self.db.span_map(self.file)).as_ref() } @@ -98,7 +106,7 @@ impl<'a> Ctx<'a> { } pub(super) fn lower_block(mut self, block: &ast::BlockExpr) -> ItemTree { - self.tree.top_attrs = RawAttrs::new(self.db, block, self.span_map()); + self.tree.top_attrs = self.lower_attrs(block); self.top_level = block .statements() .filter_map(|stmt| match stmt { @@ -144,22 +152,15 @@ impl<'a> Ctx<'a> { // FIXME: Handle `global_asm!()`. ast::Item::AsmExpr(_) => return None, }; - let attrs = RawAttrs::new(self.db, item, self.span_map()); + let attrs = self.lower_attrs(item); self.add_attrs(mod_item.ast_id(), attrs); Some(mod_item) } - fn add_attrs(&mut self, item: FileAstId, attrs: RawAttrs) { + fn add_attrs(&mut self, item: FileAstId, attrs: AttrsOrCfg) { if !attrs.is_empty() { - match self.tree.attrs.entry(item) { - Entry::Occupied(mut entry) => { - *entry.get_mut() = entry.get().merge(attrs); - } - Entry::Vacant(entry) => { - entry.insert(attrs); - } - } + self.tree.attrs.insert(item, attrs); } } @@ -352,7 +353,7 @@ impl<'a> Ctx<'a> { ast::ExternItem::TypeAlias(ty) => self.lower_type_alias(ty)?.into(), ast::ExternItem::MacroCall(call) => self.lower_macro_call(call)?.into(), }; - let attrs = RawAttrs::new(self.db, &item, self.span_map()); + let attrs = self.lower_attrs(&item); self.add_attrs(mod_item.ast_id(), attrs); Some(mod_item) }) diff --git a/crates/hir-def/src/item_tree/pretty.rs b/crates/hir-def/src/item_tree/pretty.rs index 94a6cce3ce33..66a2d14a734f 100644 --- a/crates/hir-def/src/item_tree/pretty.rs +++ b/crates/hir-def/src/item_tree/pretty.rs @@ -7,8 +7,8 @@ use span::{Edition, ErasedFileAstId}; use crate::{ item_tree::{ Const, DefDatabase, Enum, ExternBlock, ExternCrate, FieldsShape, Function, Impl, ItemTree, - Macro2, MacroCall, MacroRules, Mod, ModItemId, ModKind, RawAttrs, RawVisibilityId, Static, - Struct, Trait, TypeAlias, Union, Use, UseTree, UseTreeKind, + Macro2, MacroCall, MacroRules, Mod, ModItemId, ModKind, RawVisibilityId, Static, Struct, + Trait, TypeAlias, Union, Use, UseTree, UseTreeKind, attrs::AttrsOrCfg, }, visibility::RawVisibility, }; @@ -85,9 +85,13 @@ impl Printer<'_> { } } - fn print_attrs(&mut self, attrs: &RawAttrs, inner: bool, separated_by: &str) { + fn print_attrs(&mut self, attrs: &AttrsOrCfg, inner: bool, separated_by: &str) { + let AttrsOrCfg::Enabled { attrs } = attrs else { + w!(self, "#[cfg(false)]{separated_by}"); + return; + }; let inner = if inner { "!" } else { "" }; - for attr in &**attrs { + for attr in &*attrs.as_ref() { w!( self, "#{}[{}{}]{}", diff --git a/crates/hir-def/src/item_tree/tests.rs b/crates/hir-def/src/item_tree/tests.rs index 91b42bef8f79..a57432f33c3d 100644 --- a/crates/hir-def/src/item_tree/tests.rs +++ b/crates/hir-def/src/item_tree/tests.rs @@ -30,10 +30,8 @@ use crate::{A, B}; use a::{c, d::{e}}; "#, - expect![[r##" - #![doc = " file comment"] + expect![[r#" #![no_std] - #![doc = " another file comment"] // AstId: ExternCrate[070B, 0] pub(self) extern crate self as renamed; @@ -47,13 +45,12 @@ use a::{c, d::{e}}; // AstId: Use[0000, 1] pub(self) use globs::*; - #[doc = " docs on import"] // AstId: Use[0000, 2] pub(self) use crate::{A, B}; // AstId: Use[0000, 3] pub(self) use a::{c, d::{e}}; - "##]], + "#]], ); } @@ -195,8 +192,6 @@ mod inline { mod outline; "#, expect![[r##" - #[doc = " outer"] - #[doc = " inner"] // AstId: Module[03AE, 0] pub(self) mod inline { // AstId: Use[0000, 0] diff --git a/crates/hir-def/src/lang_item.rs b/crates/hir-def/src/lang_item.rs index a0be69cb2f96..0c81acbe80bf 100644 --- a/crates/hir-def/src/lang_item.rs +++ b/crates/hir-def/src/lang_item.rs @@ -10,6 +10,7 @@ use triomphe::Arc; use crate::{ AdtId, AssocItemId, AttrDefId, Crate, EnumId, EnumVariantId, FunctionId, ImplId, ModuleDefId, StaticId, StructId, TraitId, TypeAliasId, UnionId, + attrs::AttrFlags, db::DefDatabase, expr_store::path::Path, nameres::{assoc::TraitItems, crate_def_map}, @@ -192,14 +193,14 @@ impl LangItems { T: Into + Copy, { let _p = tracing::info_span!("collect_lang_item").entered(); - if let Some(lang_item) = lang_attr(db, item.into()) { + if let Some(lang_item) = AttrFlags::lang_item(db, item.into()) { self.items.entry(lang_item).or_insert_with(|| constructor(item)); } } } pub(crate) fn lang_attr(db: &dyn DefDatabase, item: AttrDefId) -> Option { - db.attrs(item).lang_item() + AttrFlags::lang_item(db, item) } pub(crate) fn notable_traits_in_deps(db: &dyn DefDatabase, krate: Crate) -> Arc<[Arc<[TraitId]>]> { @@ -219,7 +220,7 @@ pub(crate) fn crate_notable_traits(db: &dyn DefDatabase, krate: Crate) -> Option for (_, module_data) in crate_def_map.modules() { for def in module_data.scope.declarations() { if let ModuleDefId::TraitId(trait_) = def - && db.attrs(trait_.into()).has_doc_notable_trait() + && AttrFlags::query(db, trait_.into()).contains(AttrFlags::IS_DOC_NOTABLE_TRAIT) { traits.push(trait_); } diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs index 1f5b1e023702..9fcb81d54d03 100644 --- a/crates/hir-def/src/lib.rs +++ b/crates/hir-def/src/lib.rs @@ -23,7 +23,7 @@ extern crate ra_ap_rustc_abi as rustc_abi; pub mod db; -pub mod attr; +pub mod attrs; pub mod builtin_type; pub mod item_scope; pub mod per_ns; @@ -49,7 +49,7 @@ pub mod find_path; pub mod import_map; pub mod visibility; -use intern::{Interned, Symbol, sym}; +use intern::{Interned, Symbol}; pub use rustc_abi as layout; use thin_vec::ThinVec; use triomphe::Arc; @@ -84,7 +84,7 @@ use syntax::{AstNode, ast}; pub use hir_expand::{Intern, Lookup, tt}; use crate::{ - attr::Attrs, + attrs::AttrFlags, builtin_type::BuiltinType, db::DefDatabase, expr_store::ExpressionStoreSourceMap, @@ -960,10 +960,16 @@ impl CallableDefId { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +// FIXME: We probably should use this in more places. +/// This is used to avoid interning the whole `AttrDefId`, so we intern just modules and not everything. +#[salsa_macros::interned(debug, no_lifetime)] +pub struct InternedModuleId { + pub loc: ModuleId, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, salsa_macros::Supertype)] pub enum AttrDefId { - ModuleId(ModuleId), - FieldId(FieldId), + ModuleId(InternedModuleId), AdtId(AdtId), FunctionId(FunctionId), EnumVariantId(EnumVariantId), @@ -973,15 +979,12 @@ pub enum AttrDefId { TypeAliasId(TypeAliasId), MacroId(MacroId), ImplId(ImplId), - GenericParamId(GenericParamId), ExternBlockId(ExternBlockId), ExternCrateId(ExternCrateId), UseId(UseId), } impl_from!( - ModuleId, - FieldId, AdtId(StructId, EnumId, UnionId), EnumVariantId, StaticId, @@ -991,41 +994,11 @@ impl_from!( TypeAliasId, MacroId(Macro2Id, MacroRulesId, ProcMacroId), ImplId, - GenericParamId, ExternCrateId, UseId for AttrDefId ); -impl TryFrom for AttrDefId { - type Error = (); - - fn try_from(value: ModuleDefId) -> Result { - match value { - ModuleDefId::ModuleId(it) => Ok(it.into()), - ModuleDefId::FunctionId(it) => Ok(it.into()), - ModuleDefId::AdtId(it) => Ok(it.into()), - ModuleDefId::EnumVariantId(it) => Ok(it.into()), - ModuleDefId::ConstId(it) => Ok(it.into()), - ModuleDefId::StaticId(it) => Ok(it.into()), - ModuleDefId::TraitId(it) => Ok(it.into()), - ModuleDefId::TypeAliasId(it) => Ok(it.into()), - ModuleDefId::MacroId(id) => Ok(id.into()), - ModuleDefId::BuiltinType(_) => Err(()), - } - } -} - -impl From 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), - } - } -} impl From for AttrDefId { fn from(assoc: AssocItemId) -> Self { match assoc { @@ -1266,8 +1239,7 @@ impl HasModule for GenericDefId { impl HasModule for AttrDefId { fn module(&self, db: &dyn DefDatabase) -> ModuleId { match self { - AttrDefId::ModuleId(it) => *it, - AttrDefId::FieldId(it) => it.parent.module(db), + AttrDefId::ModuleId(it) => it.loc(db), AttrDefId::AdtId(it) => it.module(db), AttrDefId::FunctionId(it) => it.module(db), AttrDefId::EnumVariantId(it) => it.module(db), @@ -1277,12 +1249,6 @@ impl HasModule for AttrDefId { AttrDefId::TypeAliasId(it) => it.module(db), AttrDefId::ImplId(it) => it.module(db), AttrDefId::ExternBlockId(it) => it.module(db), - AttrDefId::GenericParamId(it) => match it { - GenericParamId::TypeParamId(it) => it.parent(), - GenericParamId::ConstParamId(it) => it.parent(), - GenericParamId::LifetimeParamId(it) => it.parent, - } - .module(db), AttrDefId::MacroId(it) => it.module(db), AttrDefId::ExternCrateId(it) => it.module(db), AttrDefId::UseId(it) => it.module(db), @@ -1406,32 +1372,18 @@ pub enum Complete { } impl Complete { - pub fn extract(is_trait: bool, attrs: &Attrs) -> Complete { - let mut do_not_complete = Complete::Yes; - for ra_attr in attrs.rust_analyzer_tool() { - let segments = ra_attr.path.segments(); - if segments.len() != 2 { - continue; - } - let action = segments[1].symbol(); - if *action == sym::completions { - match ra_attr.token_tree_value().map(|tt| tt.token_trees().flat_tokens()) { - Some([tt::TokenTree::Leaf(tt::Leaf::Ident(ident))]) => { - if ident.sym == sym::ignore_flyimport { - do_not_complete = Complete::IgnoreFlyimport; - } else if is_trait { - if ident.sym == sym::ignore_methods { - do_not_complete = Complete::IgnoreMethods; - } else if ident.sym == sym::ignore_flyimport_methods { - do_not_complete = Complete::IgnoreFlyimportMethods; - } - } - } - _ => {} - } + #[inline] + pub fn extract(is_trait: bool, attrs: AttrFlags) -> Complete { + if attrs.contains(AttrFlags::COMPLETE_IGNORE_FLYIMPORT) { + return Complete::IgnoreFlyimport; + } else if is_trait { + if attrs.contains(AttrFlags::COMPLETE_IGNORE_METHODS) { + return Complete::IgnoreMethods; + } else if attrs.contains(AttrFlags::COMPLETE_IGNORE_FLYIMPORT_METHODS) { + return Complete::IgnoreFlyimportMethods; } } - do_not_complete + Complete::Yes } #[inline] diff --git a/crates/hir-def/src/macro_expansion_tests/mbe.rs b/crates/hir-def/src/macro_expansion_tests/mbe.rs index c489c1f7c1da..115b487b7ac8 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe.rs @@ -300,21 +300,21 @@ fn match_by_first_token_literally() { check( r#" macro_rules! m { - ($i:ident) => ( mod $i {} ); + ($i:ident) => ( enum $i {} ); (= $i:ident) => ( fn $i() {} ); (+ $i:ident) => ( struct $i; ) } -m! { foo } +m! { Foo } m! { = bar } m! { + Baz } "#, expect![[r#" macro_rules! m { - ($i:ident) => ( mod $i {} ); + ($i:ident) => ( enum $i {} ); (= $i:ident) => ( fn $i() {} ); (+ $i:ident) => ( struct $i; ) } -mod foo {} +enum Foo {} fn bar() {} struct Baz; "#]], @@ -326,21 +326,21 @@ fn match_by_last_token_literally() { check( r#" macro_rules! m { - ($i:ident) => ( mod $i {} ); + ($i:ident) => ( enum $i {} ); ($i:ident =) => ( fn $i() {} ); ($i:ident +) => ( struct $i; ) } -m! { foo } +m! { Foo } m! { bar = } m! { Baz + } "#, expect![[r#" macro_rules! m { - ($i:ident) => ( mod $i {} ); + ($i:ident) => ( enum $i {} ); ($i:ident =) => ( fn $i() {} ); ($i:ident +) => ( struct $i; ) } -mod foo {} +enum Foo {} fn bar() {} struct Baz; "#]], @@ -352,21 +352,21 @@ fn match_by_ident() { check( r#" macro_rules! m { - ($i:ident) => ( mod $i {} ); + ($i:ident) => ( enum $i {} ); (spam $i:ident) => ( fn $i() {} ); (eggs $i:ident) => ( struct $i; ) } -m! { foo } +m! { Foo } m! { spam bar } m! { eggs Baz } "#, expect![[r#" macro_rules! m { - ($i:ident) => ( mod $i {} ); + ($i:ident) => ( enum $i {} ); (spam $i:ident) => ( fn $i() {} ); (eggs $i:ident) => ( struct $i; ) } -mod foo {} +enum Foo {} fn bar() {} struct Baz; "#]], @@ -378,12 +378,12 @@ fn match_by_separator_token() { check( r#" macro_rules! m { - ($($i:ident),*) => ($(mod $i {} )*); + ($($i:ident),*) => ($(enum $i {} )*); ($($i:ident)#*) => ($(fn $i() {} )*); ($i:ident ,# $ j:ident) => ( struct $i; struct $ j; ) } -m! { foo, bar } +m! { Baz, Qux } m! { foo# bar } @@ -391,13 +391,13 @@ m! { Foo,# Bar } "#, expect![[r#" macro_rules! m { - ($($i:ident),*) => ($(mod $i {} )*); + ($($i:ident),*) => ($(enum $i {} )*); ($($i:ident)#*) => ($(fn $i() {} )*); ($i:ident ,# $ j:ident) => ( struct $i; struct $ j; ) } -mod foo {} -mod bar {} +enum Baz {} +enum Qux {} fn foo() {} fn bar() {} @@ -1114,11 +1114,11 @@ fn test_single_item() { check( r#" macro_rules! m { ($i:item) => ( $i ) } -m! { mod c {} } +m! { struct C {} } "#, expect![[r#" macro_rules! m { ($i:item) => ( $i ) } -mod c {} +struct C {} "#]], ) } @@ -1144,6 +1144,7 @@ m! { type T = u8; } "#, + // The modules are counted twice, once because of the module and once because of the macro call. expect![[r#" macro_rules! m { ($($i:item)*) => ($($i )*) } extern crate a; @@ -1161,7 +1162,9 @@ trait J {} fn h() {} extern {} type T = u8; -"#]], + +mod b; +mod c {}"#]], ); } diff --git a/crates/hir-def/src/macro_expansion_tests/mod.rs b/crates/hir-def/src/macro_expansion_tests/mod.rs index e8ae499d27b2..74393411054e 100644 --- a/crates/hir-def/src/macro_expansion_tests/mod.rs +++ b/crates/hir-def/src/macro_expansion_tests/mod.rs @@ -245,6 +245,21 @@ pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream } } + for (_, module) in def_map.modules() { + let Some(src) = module.declaration_source(&db) else { + continue; + }; + if let Some(macro_file) = src.file_id.macro_file() { + let pp = pretty_print_macro_expansion( + src.value.syntax().clone(), + db.span_map(macro_file.into()).as_ref(), + false, + false, + ); + 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 let Some(macro_file) = src.file_id.macro_file() diff --git a/crates/hir-def/src/macro_expansion_tests/proc_macros.rs b/crates/hir-def/src/macro_expansion_tests/proc_macros.rs index 6952a9da1013..3f0afe61e0b8 100644 --- a/crates/hir-def/src/macro_expansion_tests/proc_macros.rs +++ b/crates/hir-def/src/macro_expansion_tests/proc_macros.rs @@ -9,37 +9,93 @@ use crate::macro_expansion_tests::{check, check_errors}; #[test] fn attribute_macro_attr_censoring() { - cov_mark::check!(attribute_macro_attr_censoring); check( r#" //- proc_macros: identity -#[attr1] #[proc_macros::identity] #[attr2] +//- minicore: derive +#[attr1] #[derive()] #[proc_macros::identity] #[attr2] struct S; + +/// Foo +#[cfg_attr(false, doc = "abc...", attr1)] +mod foo { + #![cfg_attr(true, cfg_attr(true, foo, cfg_attr(false, bar), proc_macros::identity))] + #![cfg_attr(true, doc = "123...", attr2)] + #![attr3] + + #[cfg_attr(true, cfg(false))] + fn foo() {} + + #[cfg(true)] + fn bar() {} +} "#, - expect![[r#" -#[attr1] #[proc_macros::identity] #[attr2] + expect![[r##" +#[attr1] #[derive()] #[proc_macros::identity] #[attr2] struct S; +/// Foo +#[cfg_attr(false, doc = "abc...", attr1)] +mod foo { + #![cfg_attr(true, cfg_attr(true, foo, cfg_attr(false, bar), proc_macros::identity))] + #![cfg_attr(true, doc = "123...", attr2)] + #![attr3] + + #[cfg_attr(true, cfg(false))] + fn foo() {} + + #[cfg(true)] + fn bar() {} +} + #[attr1] -#[attr2] struct S;"#]], +#[attr2] struct S; +#[doc = " Foo"] mod foo { + # ![foo] + # ![doc = "123..."] + # ![attr2] + # ![attr3] + #[cfg_attr(true , cfg(false ))] fn foo() {} + #[cfg(true )] fn bar() {} +}"##]], ); } #[test] fn derive_censoring() { - cov_mark::check!(derive_censoring); check( r#" //- proc_macros: derive_identity //- minicore:derive +use derive as my_cool_derive; #[attr1] #[derive(Foo)] #[derive(proc_macros::DeriveIdentity)] #[derive(Bar)] #[attr2] struct S; + +#[my_cool_derive()] +#[cfg_attr(true, derive(), attr1, derive(proc_macros::DeriveIdentity))] +#[my_cool_derive()] +struct Foo { + #[cfg_attr(false, cfg(false), attr2)] + v1: i32, + #[cfg_attr(true, cfg(false), attr2)] + v1: i32, + #[cfg_attr(true, attr3)] + v2: fn(#[cfg(false)] param: i32, #[cfg_attr(true, attr4)] param2: u32), + v3: Foo<{ + #[cfg(false)] + let foo = 123; + 456 + }>, + #[cfg(false)] + v4: bool // No comma here +} "#, expect![[r#" +use derive as my_cool_derive; #[attr1] #[derive(Foo)] #[derive(proc_macros::DeriveIdentity)] @@ -47,6 +103,32 @@ struct S; #[attr2] struct S; +#[my_cool_derive()] +#[cfg_attr(true, derive(), attr1, derive(proc_macros::DeriveIdentity))] +#[my_cool_derive()] +struct Foo { + #[cfg_attr(false, cfg(false), attr2)] + v1: i32, + #[cfg_attr(true, cfg(false), attr2)] + v1: i32, + #[cfg_attr(true, attr3)] + v2: fn(#[cfg(false)] param: i32, #[cfg_attr(true, attr4)] param2: u32), + v3: Foo<{ + #[cfg(false)] + let foo = 123; + 456 + }>, + #[cfg(false)] + v4: bool // No comma here +} + +#[attr1] +#[my_cool_derive()] struct Foo { + v1: i32, #[attr3]v2: fn(#[attr4]param2: u32), v3: Foo< { + 456 + } + >, +} #[attr1] #[derive(Bar)] #[attr2] struct S;"#]], @@ -87,7 +169,7 @@ fn foo() { bar.; blub } fn foo() { bar.; blub } fn foo() { - bar. ; + bar.; blub }"#]], ); diff --git a/crates/hir-def/src/nameres.rs b/crates/hir-def/src/nameres.rs index 5030585147de..c33944619579 100644 --- a/crates/hir-def/src/nameres.rs +++ b/crates/hir-def/src/nameres.rs @@ -396,19 +396,14 @@ pub(crate) fn crate_local_def_map(db: &dyn DefDatabase, crate_id: Crate) -> DefM ) .entered(); - let module_data = ModuleData::new( - ModuleOrigin::CrateRoot { definition: krate.root_file_id(db) }, - Visibility::Public, - ); + let root_file_id = crate_id.root_file_id(db); + let module_data = + ModuleData::new(ModuleOrigin::CrateRoot { definition: root_file_id }, Visibility::Public); let def_map = DefMap::empty(crate_id, Arc::new(DefMapCrateData::new(krate.edition)), module_data, None); - let (def_map, local_def_map) = collector::collect_defs( - db, - def_map, - TreeId::new(krate.root_file_id(db).into(), None), - None, - ); + let (def_map, local_def_map) = + collector::collect_defs(db, def_map, TreeId::new(root_file_id.into(), None), None); DefMapPair::new(db, def_map, local_def_map) } diff --git a/crates/hir-def/src/nameres/assoc.rs b/crates/hir-def/src/nameres/assoc.rs index 8d2a386de8ec..b67853347bde 100644 --- a/crates/hir-def/src/nameres/assoc.rs +++ b/crates/hir-def/src/nameres/assoc.rs @@ -4,7 +4,8 @@ use std::mem; use cfg::CfgOptions; use hir_expand::{ - AstId, ExpandTo, HirFileId, InFile, Intern, Lookup, MacroCallKind, MacroDefKind, + AstId, AttrMacroAttrIds, ExpandTo, HirFileId, InFile, Intern, Lookup, MacroCallKind, + MacroDefKind, mod_path::ModPath, name::{AsName, Name}, span_map::SpanMap, @@ -21,8 +22,8 @@ use triomphe::Arc; use crate::{ AssocItemId, AstIdWithPath, ConstLoc, FunctionId, FunctionLoc, ImplId, ItemContainerId, ItemLoc, MacroCallId, ModuleId, TraitId, TypeAliasId, TypeAliasLoc, - attr::Attrs, db::DefDatabase, + item_tree::AttrsOrCfg, macro_call_as_call_id, nameres::{ DefMap, LocalDefMap, MacroSubNs, @@ -191,19 +192,22 @@ impl<'a> AssocItemCollector<'a> { fn collect_item(&mut self, item: ast::AssocItem) { let ast_id = self.ast_id_map.ast_id(&item); - let attrs = Attrs::new(self.db, &item, self.span_map.as_ref(), self.cfg_options); - if let Err(cfg) = attrs.is_cfg_enabled(self.cfg_options) { - self.diagnostics.push(DefDiagnostic::unconfigured_code( - self.module_id.local_id, - InFile::new(self.file_id, ast_id.erase()), - cfg, - self.cfg_options.clone(), - )); - return; - } + let attrs = + match AttrsOrCfg::lower(self.db, &item, &|| self.cfg_options, self.span_map.as_ref()) { + AttrsOrCfg::Enabled { attrs } => attrs, + AttrsOrCfg::CfgDisabled(cfg) => { + self.diagnostics.push(DefDiagnostic::unconfigured_code( + self.module_id.local_id, + InFile::new(self.file_id, ast_id.erase()), + cfg.0, + self.cfg_options.clone(), + )); + return; + } + }; let ast_id = InFile::new(self.file_id, ast_id.upcast()); - 'attrs: for attr in &*attrs { + 'attrs: for (attr_id, attr) in attrs.as_ref().iter() { let ast_id_with_path = AstIdWithPath { path: attr.path.clone(), ast_id }; match self.def_map.resolve_attr_macro( @@ -212,6 +216,7 @@ impl<'a> AssocItemCollector<'a> { self.module_id.local_id, ast_id_with_path, attr, + attr_id, ) { Ok(ResolvedAttr::Macro(call_id)) => { let loc = self.db.lookup_intern_macro_call(call_id); @@ -240,8 +245,12 @@ impl<'a> AssocItemCollector<'a> { Err(_) => { self.diagnostics.push(DefDiagnostic::unresolved_macro_call( self.module_id.local_id, - MacroCallKind::Attr { ast_id, attr_args: None, invoc_attr_index: attr.id }, - attr.path().clone(), + MacroCallKind::Attr { + ast_id, + attr_args: None, + censored_attr_ids: AttrMacroAttrIds::from_one(attr_id), + }, + (*attr.path).clone(), )); } } diff --git a/crates/hir-def/src/nameres/attr_resolution.rs b/crates/hir-def/src/nameres/attr_resolution.rs index e7e96804ae73..4b67119cd393 100644 --- a/crates/hir-def/src/nameres/attr_resolution.rs +++ b/crates/hir-def/src/nameres/attr_resolution.rs @@ -2,7 +2,7 @@ use base_db::Crate; use hir_expand::{ - MacroCallId, MacroCallKind, MacroDefId, + AttrMacroAttrIds, MacroCallId, MacroCallKind, MacroDefId, attrs::{Attr, AttrId, AttrInput}, inert_attr_macro::find_builtin_attr_idx, mod_path::{ModPath, PathKind}, @@ -28,6 +28,7 @@ pub enum ResolvedAttr { } impl DefMap { + /// This cannot be used to resolve items that allow derives. pub(crate) fn resolve_attr_macro( &self, local_def_map: &LocalDefMap, @@ -35,6 +36,7 @@ impl DefMap { original_module: LocalModuleId, ast_id: AstIdWithPath, attr: &Attr, + attr_id: AttrId, ) -> Result { // NB: does not currently work for derive helpers as they aren't recorded in the `DefMap` @@ -68,6 +70,9 @@ impl DefMap { db, &ast_id, attr, + // There aren't any active attributes before this one, because attribute macros + // replace their input, and derive macros are not allowed in this function. + AttrMacroAttrIds::from_one(attr_id), self.krate, db.macro_def(def), ))) @@ -107,6 +112,7 @@ pub(super) fn attr_macro_as_call_id( db: &dyn DefDatabase, item_attr: &AstIdWithPath, macro_attr: &Attr, + censored_attr_ids: AttrMacroAttrIds, krate: Crate, def: MacroDefId, ) -> MacroCallId { @@ -126,7 +132,7 @@ pub(super) fn attr_macro_as_call_id( MacroCallKind::Attr { ast_id: item_attr.ast_id, attr_args: arg.map(Arc::new), - invoc_attr_index: macro_attr.id, + censored_attr_ids, }, macro_attr.ctxt, ) diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs index 07e61718222b..ada69e918a82 100644 --- a/crates/hir-def/src/nameres/collector.rs +++ b/crates/hir-def/src/nameres/collector.rs @@ -3,14 +3,14 @@ //! `DefCollector::collect` contains the fixed-point iteration loop which //! resolves imports and expands macros. -use std::{cmp::Ordering, iter, mem, ops::Not}; +use std::{cmp::Ordering, iter, mem}; use base_db::{BuiltDependency, Crate, CrateOrigin, LangCrateOrigin}; use cfg::{CfgAtom, CfgExpr, CfgOptions}; use either::Either; use hir_expand::{ - EditionedFileId, ErasedAstId, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, - MacroDefId, MacroDefKind, + AttrMacroAttrIds, EditionedFileId, ErasedAstId, ExpandTo, HirFileId, InFile, MacroCallId, + MacroCallKind, MacroDefId, MacroDefKind, attrs::{Attr, AttrId}, builtin::{find_builtin_attr, find_builtin_derive, find_builtin_macro}, mod_path::{ModPath, PathKind}, @@ -18,9 +18,10 @@ use hir_expand::{ proc_macro::CustomProcMacroExpander, }; use intern::{Interned, sym}; -use itertools::{Itertools, izip}; +use itertools::izip; use la_arena::Idx; use rustc_hash::{FxHashMap, FxHashSet}; +use smallvec::SmallVec; use span::{Edition, FileAstId, SyntaxContext}; use syntax::ast; use triomphe::Arc; @@ -31,12 +32,11 @@ use crate::{ LocalModuleId, Lookup, Macro2Id, Macro2Loc, MacroExpander, MacroId, MacroRulesId, MacroRulesLoc, MacroRulesLocFlags, ModuleDefId, ModuleId, ProcMacroId, ProcMacroLoc, StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc, UnresolvedMacro, UseId, UseLoc, - attr::Attrs, db::DefDatabase, item_scope::{GlobId, ImportId, ImportOrExternCrate, PerNsGlobImports}, item_tree::{ - self, FieldsShape, ImportAlias, ImportKind, ItemTree, ItemTreeAstId, Macro2, MacroCall, - MacroRules, Mod, ModItemId, ModKind, TreeId, + self, Attrs, AttrsOrCfg, FieldsShape, ImportAlias, ImportKind, ItemTree, ItemTreeAstId, + Macro2, MacroCall, MacroRules, Mod, ModItemId, ModKind, TreeId, }, macro_call_as_call_id, nameres::{ @@ -101,6 +101,7 @@ pub(super) fn collect_defs( proc_macros, from_glob_import: Default::default(), skip_attrs: Default::default(), + prev_active_attrs: Default::default(), unresolved_extern_crates: Default::default(), is_proc_macro: krate.is_proc_macro, }; @@ -205,6 +206,7 @@ enum MacroDirectiveKind<'db> { }, Attr { ast_id: AstIdWithPath, + attr_id: AttrId, attr: Attr, mod_item: ModItemId, /* is this needed? */ tree: TreeId, @@ -245,28 +247,27 @@ struct DefCollector<'db> { /// This also stores the attributes to skip when we resolve derive helpers and non-macro /// non-builtin attributes in general. // FIXME: There has to be a better way to do this - skip_attrs: FxHashMap>, AttrId>, + skip_attrs: FxHashMap, AttrId>, + /// When we expand attributes, we need to censor all previous active attributes + /// on the same item. Therefore, this holds all active attributes that we already + /// expanded. + prev_active_attrs: FxHashMap, SmallVec<[AttrId; 1]>>, } impl<'db> DefCollector<'db> { fn seed_with_top_level(&mut self) { let _p = tracing::info_span!("seed_with_top_level").entered(); - let file_id = self.def_map.krate.data(self.db).root_file_id(self.db); + let file_id = self.def_map.krate.root_file_id(self.db); let item_tree = self.db.file_item_tree(file_id.into()); - let attrs = item_tree.top_level_attrs(self.db, self.def_map.krate); + let attrs = match item_tree.top_level_attrs() { + AttrsOrCfg::Enabled { attrs } => attrs.as_ref(), + AttrsOrCfg::CfgDisabled(it) => it.1.as_ref(), + }; let crate_data = Arc::get_mut(&mut self.def_map.data).unwrap(); - let mut process = true; - // Process other crate-level attributes. for attr in &*attrs { - if let Some(cfg) = attr.cfg() - && self.cfg_options.check(&cfg) == Some(false) - { - process = false; - break; - } let Some(attr_name) = attr.path.as_ident() else { continue }; match () { @@ -290,7 +291,7 @@ impl<'db> DefCollector<'db> { () if *attr_name == sym::feature => { let features = attr.parse_path_comma_token_tree(self.db).into_iter().flatten().filter_map( - |(feat, _)| match feat.segments() { + |(feat, _, _)| match feat.segments() { [name] => Some(name.symbol().clone()), _ => None, }, @@ -347,7 +348,7 @@ impl<'db> DefCollector<'db> { self.inject_prelude(); - if !process { + if matches!(item_tree.top_level_attrs(), AttrsOrCfg::CfgDisabled(_)) { return; } @@ -365,10 +366,7 @@ impl<'db> DefCollector<'db> { fn seed_with_inner(&mut self, tree_id: TreeId) { let item_tree = tree_id.item_tree(self.db); - let is_cfg_enabled = item_tree - .top_level_attrs(self.db, self.def_map.krate) - .cfg() - .is_none_or(|cfg| self.cfg_options.check(&cfg) != Some(false)); + let is_cfg_enabled = matches!(item_tree.top_level_attrs(), AttrsOrCfg::Enabled { .. }); if is_cfg_enabled { self.inject_prelude(); @@ -459,18 +457,18 @@ impl<'db> DefCollector<'db> { self.unresolved_macros.iter().enumerate().find_map(|(idx, directive)| match &directive .kind { - MacroDirectiveKind::Attr { ast_id, mod_item, attr, tree, item_tree } => { + MacroDirectiveKind::Attr { ast_id, mod_item, attr_id, attr, tree, item_tree } => { self.def_map.diagnostics.push(DefDiagnostic::unresolved_macro_call( directive.module_id, MacroCallKind::Attr { ast_id: ast_id.ast_id, attr_args: None, - invoc_attr_index: attr.id, + censored_attr_ids: AttrMacroAttrIds::from_one(*attr_id), }, - attr.path().clone(), + (*attr.path).clone(), )); - self.skip_attrs.insert(ast_id.ast_id.with_value(mod_item.ast_id()), attr.id); + self.skip_attrs.insert(ast_id.ast_id.with_value(mod_item.ast_id()), *attr_id); Some((idx, directive, *mod_item, *tree, *item_tree)) } @@ -1353,6 +1351,7 @@ impl<'db> DefCollector<'db> { MacroDirectiveKind::Attr { ast_id: file_ast_id, mod_item, + attr_id, attr, tree, item_tree, @@ -1365,7 +1364,7 @@ impl<'db> DefCollector<'db> { let mod_dir = collector.mod_dirs[&directive.module_id].clone(); collector .skip_attrs - .insert(InFile::new(file_id, mod_item.ast_id()), attr.id); + .insert(InFile::new(file_id, mod_item.ast_id()), *attr_id); ModCollector { def_collector: collector, @@ -1401,7 +1400,6 @@ impl<'db> DefCollector<'db> { // being cfg'ed out). // Ideally we will just expand them to nothing here. But we are only collecting macro calls, // not expanding them, so we have no way to do that. - // If you add an ignored attribute here, also add it to `Semantics::might_be_inside_macro_call()`. if matches!( def.kind, MacroDefKind::BuiltInAttr(_, expander) @@ -1413,8 +1411,18 @@ impl<'db> DefCollector<'db> { } } - let call_id = || { - attr_macro_as_call_id(self.db, file_ast_id, attr, self.def_map.krate, def) + let mut call_id = || { + let active_attrs = self.prev_active_attrs.entry(ast_id).or_default(); + active_attrs.push(*attr_id); + + attr_macro_as_call_id( + self.db, + file_ast_id, + attr, + AttrMacroAttrIds::from_many(active_attrs), + self.def_map.krate, + def, + ) }; if matches!(def, MacroDefId { kind: MacroDefKind::BuiltInAttr(_, exp), .. } @@ -1432,7 +1440,7 @@ impl<'db> DefCollector<'db> { let diag = DefDiagnostic::invalid_derive_target( directive.module_id, ast_id, - attr.id, + *attr_id, ); self.def_map.diagnostics.push(diag); return recollect_without(self); @@ -1445,7 +1453,7 @@ impl<'db> DefCollector<'db> { Some(derive_macros) => { let call_id = call_id(); let mut len = 0; - for (idx, (path, call_site)) in derive_macros.enumerate() { + for (idx, (path, call_site, _)) in derive_macros.enumerate() { let ast_id = AstIdWithPath::new( file_id, ast_id.value, @@ -1456,7 +1464,7 @@ impl<'db> DefCollector<'db> { depth: directive.depth + 1, kind: MacroDirectiveKind::Derive { ast_id, - derive_attr: attr.id, + derive_attr: *attr_id, derive_pos: idx, ctxt: call_site.ctx, derive_macro_id: call_id, @@ -1472,13 +1480,13 @@ impl<'db> DefCollector<'db> { // Check the comment in [`builtin_attr_macro`]. self.def_map.modules[directive.module_id] .scope - .init_derive_attribute(ast_id, attr.id, call_id, len + 1); + .init_derive_attribute(ast_id, *attr_id, call_id, len + 1); } None => { let diag = DefDiagnostic::malformed_derive( directive.module_id, ast_id, - attr.id, + *attr_id, ); self.def_map.diagnostics.push(diag); } @@ -1715,16 +1723,17 @@ impl ModCollector<'_, '_> { }; let mut process_mod_item = |item: ModItemId| { - let attrs = self.item_tree.attrs(db, krate, item.ast_id()); - if let Some(cfg) = attrs.cfg() - && !self.is_cfg_enabled(&cfg) - { - let ast_id = item.ast_id().erase(); - self.emit_unconfigured_diagnostic(InFile::new(self.file_id(), ast_id), &cfg); - return; - } + let attrs = match self.item_tree.attrs(item.ast_id()) { + Some(AttrsOrCfg::Enabled { attrs }) => attrs.as_ref(), + None => Attrs::EMPTY, + Some(AttrsOrCfg::CfgDisabled(cfg)) => { + let ast_id = item.ast_id().erase(); + self.emit_unconfigured_diagnostic(InFile::new(self.file_id(), ast_id), &cfg.0); + return; + } + }; - if let Err(()) = self.resolve_attributes(&attrs, item, container) { + 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. return; @@ -1736,7 +1745,7 @@ impl ModCollector<'_, '_> { self.def_collector.crate_local_def_map.unwrap_or(&self.def_collector.local_def_map); match item { - ModItemId::Mod(m) => self.collect_module(m, &attrs), + ModItemId::Mod(m) => self.collect_module(m, attrs), ModItemId::Use(item_tree_id) => { let id = UseLoc { container: module, id: InFile::new(self.file_id(), item_tree_id) } @@ -2009,7 +2018,7 @@ impl ModCollector<'_, '_> { ); return; }; - for (path, _) in paths { + for (path, _, _) in paths { if let Some(name) = path.as_ident() { single_imports.push(name.clone()); } @@ -2023,7 +2032,7 @@ impl ModCollector<'_, '_> { ); } - fn collect_module(&mut self, module_ast_id: ItemTreeAstId, attrs: &Attrs) { + fn collect_module(&mut self, module_ast_id: ItemTreeAstId, attrs: Attrs<'_>) { let path_attr = attrs.by_key(sym::path).string_value_unescape(); let is_macro_use = attrs.by_key(sym::macro_use).exists(); let module = &self.item_tree[module_ast_id]; @@ -2064,23 +2073,18 @@ impl ModCollector<'_, '_> { self.file_id(), &module.name, path_attr.as_deref(), + self.def_collector.def_map.krate, ) { 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() - .and_then(|cfg| self.is_cfg_enabled(&cfg).not().then_some(cfg)) - .map_or(Ok(()), Err); - match is_enabled { - Err(cfg) => { + match item_tree.top_level_attrs() { + AttrsOrCfg::CfgDisabled(cfg) => { self.emit_unconfigured_diagnostic( InFile::new(self.file_id(), module_ast_id.erase()), - &cfg, + &cfg.0, ); } - Ok(()) => { + AttrsOrCfg::Enabled { attrs } => { let module_id = self.push_child_module( module.name.clone(), ast_id.value, @@ -2096,11 +2100,8 @@ impl ModCollector<'_, '_> { 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(sym::macro_use) - .exists(); + let is_macro_use = + is_macro_use || attrs.as_ref().by_key(sym::macro_use).exists(); if is_macro_use { self.import_all_legacy_macros(module_id); } @@ -2188,36 +2189,16 @@ impl ModCollector<'_, '_> { /// assumed to be resolved already. fn resolve_attributes( &mut self, - attrs: &Attrs, + attrs: Attrs<'_>, mod_item: ModItemId, container: ItemContainerId, ) -> Result<(), ()> { - let mut ignore_up_to = self + let ignore_up_to = self .def_collector .skip_attrs .get(&InFile::new(self.file_id(), mod_item.ast_id())) .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 { + for (attr_id, attr) in attrs.iter_after(ignore_up_to) { if self.def_collector.def_map.is_builtin_or_registered_attr(&attr.path) { continue; } @@ -2232,6 +2213,7 @@ impl ModCollector<'_, '_> { depth: self.macro_depth + 1, kind: MacroDirectiveKind::Attr { ast_id, + attr_id, attr: attr.clone(), mod_item, tree: self.tree_id, @@ -2247,9 +2229,14 @@ impl ModCollector<'_, '_> { } fn collect_macro_rules(&mut self, ast_id: ItemTreeAstId, module: ModuleId) { - let krate = self.def_collector.def_map.krate; let mac = &self.item_tree[ast_id]; - let attrs = self.item_tree.attrs(self.def_collector.db, krate, ast_id.upcast()); + let attrs = match self.item_tree.attrs(ast_id.upcast()) { + Some(AttrsOrCfg::Enabled { attrs }) => attrs.as_ref(), + None => Attrs::EMPTY, + Some(AttrsOrCfg::CfgDisabled(_)) => { + unreachable!("we only get here if the macro is not cfg'ed out") + } + }; let f_ast_id = InFile::new(self.file_id(), ast_id.upcast()); let export_attr = || attrs.by_key(sym::macro_export); @@ -2329,9 +2316,14 @@ impl ModCollector<'_, '_> { } fn collect_macro_def(&mut self, ast_id: ItemTreeAstId, module: ModuleId) { - let krate = self.def_collector.def_map.krate; let mac = &self.item_tree[ast_id]; - let attrs = self.item_tree.attrs(self.def_collector.db, krate, ast_id.upcast()); + let attrs = match self.item_tree.attrs(ast_id.upcast()) { + Some(AttrsOrCfg::Enabled { attrs }) => attrs.as_ref(), + None => Attrs::EMPTY, + Some(AttrsOrCfg::CfgDisabled(_)) => { + unreachable!("we only get here if the macro is not cfg'ed out") + } + }; let f_ast_id = InFile::new(self.file_id(), ast_id.upcast()); // Case 1: builtin macros @@ -2517,10 +2509,6 @@ impl ModCollector<'_, '_> { Some((a, b)) } - fn is_cfg_enabled(&self, cfg: &CfgExpr) -> bool { - self.def_collector.cfg_options.check(cfg) != Some(false) - } - fn emit_unconfigured_diagnostic(&mut self, ast_id: ErasedAstId, cfg: &CfgExpr) { self.def_collector.def_map.diagnostics.push(DefDiagnostic::unconfigured_code( self.module_id, @@ -2560,6 +2548,7 @@ mod tests { proc_macros: Default::default(), from_glob_import: Default::default(), skip_attrs: Default::default(), + prev_active_attrs: Default::default(), is_proc_macro: false, unresolved_extern_crates: Default::default(), }; diff --git a/crates/hir-def/src/nameres/diagnostics.rs b/crates/hir-def/src/nameres/diagnostics.rs index c495a0744919..6a07c56aeebe 100644 --- a/crates/hir-def/src/nameres/diagnostics.rs +++ b/crates/hir-def/src/nameres/diagnostics.rs @@ -17,8 +17,8 @@ pub enum DefDiagnosticKind { UnconfiguredCode { ast_id: ErasedAstId, cfg: CfgExpr, opts: CfgOptions }, UnresolvedMacroCall { ast: MacroCallKind, path: ModPath }, UnimplementedBuiltinMacro { ast: AstId }, - InvalidDeriveTarget { ast: AstId, id: usize }, - MalformedDerive { ast: AstId, id: usize }, + InvalidDeriveTarget { ast: AstId, id: AttrId }, + MalformedDerive { ast: AstId, id: AttrId }, MacroDefError { ast: AstId, message: String }, MacroError { ast: AstId, path: ModPath, err: ExpandErrorKind }, } @@ -119,10 +119,7 @@ impl DefDiagnostic { ast: AstId, id: AttrId, ) -> Self { - Self { - in_module: container, - kind: DefDiagnosticKind::InvalidDeriveTarget { ast, id: id.ast_index() }, - } + Self { in_module: container, kind: DefDiagnosticKind::InvalidDeriveTarget { ast, id } } } pub(super) fn malformed_derive( @@ -130,9 +127,6 @@ impl DefDiagnostic { ast: AstId, id: AttrId, ) -> Self { - Self { - in_module: container, - kind: DefDiagnosticKind::MalformedDerive { ast, id: id.ast_index() }, - } + Self { in_module: container, kind: DefDiagnosticKind::MalformedDerive { ast, id } } } } diff --git a/crates/hir-def/src/nameres/mod_resolution.rs b/crates/hir-def/src/nameres/mod_resolution.rs index 0c50f13edfb6..140b77ac002f 100644 --- a/crates/hir-def/src/nameres/mod_resolution.rs +++ b/crates/hir-def/src/nameres/mod_resolution.rs @@ -1,6 +1,6 @@ //! This module resolves `mod foo;` declaration to file. use arrayvec::ArrayVec; -use base_db::AnchoredPath; +use base_db::{AnchoredPath, Crate}; use hir_expand::{EditionedFileId, name::Name}; use crate::{HirFileId, db::DefDatabase}; @@ -62,6 +62,7 @@ impl ModDir { file_id: HirFileId, name: &Name, attr_path: Option<&str>, + krate: Crate, ) -> Result<(EditionedFileId, bool, ModDir), Box<[String]>> { let name = name.as_str(); @@ -91,7 +92,7 @@ impl ModDir { if let Some(mod_dir) = self.child(dir_path, !root_dir_owner) { return Ok(( // FIXME: Edition, is this rightr? - EditionedFileId::new(db, file_id, orig_file_id.edition(db)), + EditionedFileId::new(db, file_id, orig_file_id.edition(db), krate), is_mod_rs, mod_dir, )); diff --git a/crates/hir-def/src/nameres/proc_macro.rs b/crates/hir-def/src/nameres/proc_macro.rs index cd8882183bb4..cd45afe57d7c 100644 --- a/crates/hir-def/src/nameres/proc_macro.rs +++ b/crates/hir-def/src/nameres/proc_macro.rs @@ -3,8 +3,10 @@ use hir_expand::name::{AsName, Name}; use intern::sym; -use crate::attr::Attrs; -use crate::tt::{Leaf, TokenTree, TopSubtree, TtElement}; +use crate::{ + item_tree::Attrs, + tt::{Leaf, TokenTree, TopSubtree, TtElement}, +}; #[derive(Debug, PartialEq, Eq)] pub struct ProcMacroDef { @@ -29,8 +31,8 @@ impl ProcMacroKind { } } -impl Attrs { - pub fn parse_proc_macro_decl(&self, func_name: &Name) -> Option { +impl Attrs<'_> { + pub(crate) fn parse_proc_macro_decl(&self, func_name: &Name) -> Option { if self.is_proc_macro() { Some(ProcMacroDef { name: func_name.clone(), kind: ProcMacroKind::Bang }) } else if self.is_proc_macro_attribute() { @@ -51,15 +53,10 @@ impl Attrs { } } - pub fn parse_proc_macro_derive(&self) -> Option<(Name, Box<[Name]>)> { + pub(crate) fn parse_proc_macro_derive(&self) -> Option<(Name, Box<[Name]>)> { let derive = self.by_key(sym::proc_macro_derive).tt_values().next()?; parse_macro_name_and_helper_attrs(derive) } - - pub fn parse_rustc_builtin_macro(&self) -> Option<(Name, Box<[Name]>)> { - let derive = self.by_key(sym::rustc_builtin_macro).tt_values().next()?; - parse_macro_name_and_helper_attrs(derive) - } } // This fn is intended for `#[proc_macro_derive(..)]` and `#[rustc_builtin_macro(..)]`, which have @@ -84,14 +81,11 @@ pub(crate) fn parse_macro_name_and_helper_attrs(tt: &TopSubtree) -> Option<(Name let helpers = tt::TokenTreesView::new(&tt.token_trees().flat_tokens()[3..]).try_into_subtree()?; let helpers = helpers .iter() - .filter( - |tt| !matches!(tt, TtElement::Leaf(Leaf::Punct(comma)) if comma.char == ','), - ) - .map(|tt| match tt { + .filter_map(|tt| match tt { TtElement::Leaf(Leaf::Ident(helper)) => Some(helper.as_name()), _ => None, }) - .collect::>>()?; + .collect::>(); Some((trait_name.as_name(), helpers)) } diff --git a/crates/hir-def/src/signatures.rs b/crates/hir-def/src/signatures.rs index bf72fafeae72..f48507154f02 100644 --- a/crates/hir-def/src/signatures.rs +++ b/crates/hir-def/src/signatures.rs @@ -21,7 +21,7 @@ use triomphe::Arc; use crate::{ ConstId, EnumId, EnumVariantId, EnumVariantLoc, ExternBlockId, FunctionId, HasModule, ImplId, ItemContainerId, ModuleId, StaticId, StructId, TraitId, TypeAliasId, UnionId, VariantId, - attr::Attrs, + attrs::AttrFlags, db::DefDatabase, expr_store::{ ExpressionStore, ExpressionStoreSourceMap, @@ -48,12 +48,13 @@ pub struct StructSignature { pub store: Arc, pub flags: StructFlags, pub shape: FieldsShape, - pub repr: Option, } bitflags! { #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct StructFlags: u8 { + /// Indicates whether this struct has `#[repr]`. + const HAS_REPR = 1 << 0; /// Indicates whether the struct has a `#[rustc_has_incoherent_inherent_impls]` attribute. const RUSTC_HAS_INCOHERENT_INHERENT_IMPLS = 1 << 1; /// Indicates whether the struct has a `#[fundamental]` attribute. @@ -75,16 +76,19 @@ impl StructSignature { pub fn query(db: &dyn DefDatabase, id: StructId) -> (Arc, Arc) { let loc = id.lookup(db); let InFile { file_id, value: source } = loc.source(db); - let attrs = db.attrs(id.into()); + let attrs = AttrFlags::query(db, id.into()); let mut flags = StructFlags::empty(); - if attrs.by_key(sym::rustc_has_incoherent_inherent_impls).exists() { + if attrs.contains(AttrFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS) { flags |= StructFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS; } - if attrs.by_key(sym::fundamental).exists() { + if attrs.contains(AttrFlags::FUNDAMENTAL) { flags |= StructFlags::FUNDAMENTAL; } - if let Some(lang) = attrs.lang_item() { + if attrs.contains(AttrFlags::HAS_REPR) { + flags |= StructFlags::HAS_REPR; + } + if let Some(lang) = attrs.lang_item_with_attrs(db, id.into()) { match lang { LangItem::PhantomData => flags |= StructFlags::IS_PHANTOM_DATA, LangItem::OwnedBox => flags |= StructFlags::IS_BOX, @@ -94,7 +98,6 @@ impl StructSignature { _ => (), } } - let repr = attrs.repr(); let shape = adt_shape(source.kind()); let (store, generic_params, source_map) = lower_generic_params( @@ -112,11 +115,19 @@ impl StructSignature { flags, shape, name: as_name_opt(source.name()), - repr, }), Arc::new(source_map), ) } + + #[inline] + pub fn repr(&self, db: &dyn DefDatabase, id: StructId) -> Option { + if self.flags.contains(StructFlags::HAS_REPR) { + AttrFlags::repr(db, id.into()) + } else { + None + } + } } #[inline] @@ -134,22 +145,22 @@ pub struct UnionSignature { pub generic_params: Arc, pub store: Arc, pub flags: StructFlags, - pub repr: Option, } impl UnionSignature { pub fn query(db: &dyn DefDatabase, id: UnionId) -> (Arc, Arc) { let loc = id.lookup(db); - let attrs = db.attrs(id.into()); + let attrs = AttrFlags::query(db, id.into()); let mut flags = StructFlags::empty(); - if attrs.by_key(sym::rustc_has_incoherent_inherent_impls).exists() { + if attrs.contains(AttrFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS) { flags |= StructFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS; } - if attrs.by_key(sym::fundamental).exists() { + if attrs.contains(AttrFlags::FUNDAMENTAL) { flags |= StructFlags::FUNDAMENTAL; } - - let repr = attrs.repr(); + if attrs.contains(AttrFlags::HAS_REPR) { + flags |= StructFlags::HAS_REPR; + } let InFile { file_id, value: source } = loc.source(db); let (store, generic_params, source_map) = lower_generic_params( @@ -165,7 +176,6 @@ impl UnionSignature { generic_params, store, flags, - repr, name: as_name_opt(source.name()), }), Arc::new(source_map), @@ -186,20 +196,17 @@ pub struct EnumSignature { pub generic_params: Arc, pub store: Arc, pub flags: EnumFlags, - pub repr: Option, } impl EnumSignature { pub fn query(db: &dyn DefDatabase, id: EnumId) -> (Arc, Arc) { let loc = id.lookup(db); - let attrs = db.attrs(id.into()); + let attrs = AttrFlags::query(db, id.into()); let mut flags = EnumFlags::empty(); - if attrs.by_key(sym::rustc_has_incoherent_inherent_impls).exists() { + if attrs.contains(AttrFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS) { flags |= EnumFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS; } - let repr = attrs.repr(); - let InFile { file_id, value: source } = loc.source(db); let (store, generic_params, source_map) = lower_generic_params( db, @@ -215,15 +222,14 @@ impl EnumSignature { generic_params, store, flags, - repr, name: as_name_opt(source.name()), }), Arc::new(source_map), ) } - pub fn variant_body_type(&self) -> IntegerType { - match self.repr { + pub fn variant_body_type(db: &dyn DefDatabase, id: EnumId) -> IntegerType { + match AttrFlags::repr(db, id.into()) { Some(ReprOptions { int: Some(builtin), .. }) => builtin, _ => IntegerType::Pointer(true), } @@ -251,9 +257,9 @@ impl ConstSignature { let loc = id.lookup(db); let module = loc.container.module(db); - let attrs = db.attrs(id.into()); + let attrs = AttrFlags::query(db, id.into()); let mut flags = ConstFlags::empty(); - if attrs.by_key(sym::rustc_allow_incoherent_impl).exists() { + if attrs.contains(AttrFlags::RUSTC_ALLOW_INCOHERENT_IMPL) { flags |= ConstFlags::RUSTC_ALLOW_INCOHERENT_IMPL; } let source = loc.source(db); @@ -306,9 +312,9 @@ impl StaticSignature { let loc = id.lookup(db); let module = loc.container.module(db); - let attrs = db.attrs(id.into()); + let attrs = AttrFlags::query(db, id.into()); let mut flags = StaticFlags::empty(); - if attrs.by_key(sym::rustc_allow_incoherent_impl).exists() { + if attrs.contains(AttrFlags::RUSTC_ALLOW_INCOHERENT_IMPL) { flags |= StaticFlags::RUSTC_ALLOW_INCOHERENT_IMPL; } @@ -419,7 +425,7 @@ impl TraitSignature { let loc = id.lookup(db); let mut flags = TraitFlags::empty(); - let attrs = db.attrs(id.into()); + let attrs = AttrFlags::query(db, id.into()); let source = loc.source(db); if source.value.auto_token().is_some() { flags.insert(TraitFlags::AUTO); @@ -430,34 +436,23 @@ impl TraitSignature { if source.value.eq_token().is_some() { flags.insert(TraitFlags::ALIAS); } - if attrs.by_key(sym::fundamental).exists() { + if attrs.contains(AttrFlags::FUNDAMENTAL) { flags |= TraitFlags::FUNDAMENTAL; } - if attrs.by_key(sym::rustc_has_incoherent_inherent_impls).exists() { + if attrs.contains(AttrFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS) { flags |= TraitFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS; } - if attrs.by_key(sym::rustc_paren_sugar).exists() { + if attrs.contains(AttrFlags::RUSTC_PAREN_SUGAR) { flags |= TraitFlags::RUSTC_PAREN_SUGAR; } - if attrs.by_key(sym::rustc_coinductive).exists() { + if attrs.contains(AttrFlags::RUSTC_COINDUCTIVE) { flags |= TraitFlags::COINDUCTIVE; } - let mut skip_array_during_method_dispatch = - attrs.by_key(sym::rustc_skip_array_during_method_dispatch).exists(); - let mut skip_boxed_slice_during_method_dispatch = false; - for tt in attrs.by_key(sym::rustc_skip_during_method_dispatch).tt_values() { - for tt in tt.iter() { - if let tt::iter::TtElement::Leaf(tt::Leaf::Ident(ident)) = tt { - skip_array_during_method_dispatch |= ident.sym == sym::array; - skip_boxed_slice_during_method_dispatch |= ident.sym == sym::boxed_slice; - } - } - } - if skip_array_during_method_dispatch { + if attrs.contains(AttrFlags::RUSTC_SKIP_ARRAY_DURING_METHOD_DISPATCH) { flags |= TraitFlags::SKIP_ARRAY_DURING_METHOD_DISPATCH; } - if skip_boxed_slice_during_method_dispatch { + if attrs.contains(AttrFlags::RUSTC_SKIP_BOXED_SLICE_DURING_METHOD_DISPATCH) { flags |= TraitFlags::SKIP_BOXED_SLICE_DURING_METHOD_DISPATCH; } @@ -489,6 +484,7 @@ bitflags! { const HAS_TARGET_FEATURE = 1 << 9; const DEPRECATED_SAFE_2024 = 1 << 10; const EXPLICIT_SAFE = 1 << 11; + const HAS_LEGACY_CONST_GENERICS = 1 << 12; } } @@ -501,8 +497,6 @@ pub struct FunctionSignature { pub ret_type: Option, pub abi: Option, pub flags: FnFlags, - // FIXME: we should put this behind a fn flags + query to avoid bloating the struct - pub legacy_const_generics_indices: Option>>, } impl FunctionSignature { @@ -514,20 +508,23 @@ impl FunctionSignature { let module = loc.container.module(db); let mut flags = FnFlags::empty(); - let attrs = db.attrs(id.into()); - if attrs.by_key(sym::rustc_allow_incoherent_impl).exists() { + let attrs = AttrFlags::query(db, id.into()); + if attrs.contains(AttrFlags::RUSTC_ALLOW_INCOHERENT_IMPL) { flags.insert(FnFlags::RUSTC_ALLOW_INCOHERENT_IMPL); } - if attrs.by_key(sym::target_feature).exists() { + if attrs.contains(AttrFlags::HAS_TARGET_FEATURE) { flags.insert(FnFlags::HAS_TARGET_FEATURE); } - let legacy_const_generics_indices = attrs.rustc_legacy_const_generics(); + + if attrs.contains(AttrFlags::HAS_LEGACY_CONST_GENERICS) { + flags.insert(FnFlags::HAS_LEGACY_CONST_GENERICS); + } let source = loc.source(db); if source.value.unsafe_token().is_some() { - if attrs.by_key(sym::rustc_deprecated_safe_2024).exists() { + if attrs.contains(AttrFlags::RUSTC_DEPRECATED_SAFE_2024) { flags.insert(FnFlags::DEPRECATED_SAFE_2024); } else { flags.insert(FnFlags::UNSAFE); @@ -569,7 +566,6 @@ impl FunctionSignature { ret_type, abi, flags, - legacy_const_generics_indices, name, }), Arc::new(source_map), @@ -617,6 +613,19 @@ impl FunctionSignature { pub fn has_target_feature(&self) -> bool { self.flags.contains(FnFlags::HAS_TARGET_FEATURE) } + + #[inline] + pub fn legacy_const_generics_indices<'db>( + &self, + db: &'db dyn DefDatabase, + id: FunctionId, + ) -> Option<&'db [u32]> { + if !self.flags.contains(FnFlags::HAS_LEGACY_CONST_GENERICS) { + return None; + } + + AttrFlags::legacy_const_generic_indices(db, id).as_deref() + } } bitflags! { @@ -646,11 +655,11 @@ impl TypeAliasSignature { let loc = id.lookup(db); let mut flags = TypeAliasFlags::empty(); - let attrs = db.attrs(id.into()); - if attrs.by_key(sym::rustc_has_incoherent_inherent_impls).exists() { + let attrs = AttrFlags::query(db, id.into()); + if attrs.contains(AttrFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS) { flags.insert(TypeAliasFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPL); } - if attrs.by_key(sym::rustc_allow_incoherent_impl).exists() { + if attrs.contains(AttrFlags::RUSTC_ALLOW_INCOHERENT_IMPL) { flags.insert(TypeAliasFlags::RUSTC_ALLOW_INCOHERENT_IMPL); } if matches!(loc.container, ItemContainerId::ExternBlockId(_)) { @@ -833,7 +842,7 @@ fn lower_fields( let mut has_fields = false; for (ty, field) in fields.value { has_fields = true; - match Attrs::is_cfg_enabled_for(db, &field, col.span_map(), cfg_options) { + match AttrFlags::is_cfg_enabled_for(&field, cfg_options) { Ok(()) => { let type_ref = col.lower_type_ref_opt(ty, &mut ExprCollector::impl_trait_error_allocator); @@ -895,7 +904,6 @@ impl EnumVariants { let loc = e.lookup(db); let source = loc.source(db); let ast_id_map = db.ast_id_map(source.file_id); - let span_map = db.span_map(source.file_id); let mut diagnostics = ThinVec::new(); let cfg_options = loc.container.krate.cfg_options(db); @@ -907,7 +915,7 @@ impl EnumVariants { .variants() .filter_map(|variant| { let ast_id = ast_id_map.ast_id(&variant); - match Attrs::is_cfg_enabled_for(db, &variant, span_map.as_ref(), cfg_options) { + match AttrFlags::is_cfg_enabled_for(&variant, cfg_options) { Ok(()) => { let enum_variant = EnumVariantLoc { id: source.with_value(ast_id), parent: e, index } diff --git a/crates/hir-def/src/src.rs b/crates/hir-def/src/src.rs index 367b543cf908..153fd195f0ad 100644 --- a/crates/hir-def/src/src.rs +++ b/crates/hir-def/src/src.rs @@ -7,7 +7,7 @@ use syntax::{AstNode, AstPtr, ast}; use crate::{ AstIdLoc, GenericDefId, LocalFieldId, LocalLifetimeParamId, LocalTypeOrConstParamId, Lookup, - UseId, VariantId, attr::Attrs, db::DefDatabase, + UseId, VariantId, attrs::AttrFlags, db::DefDatabase, }; pub trait HasSource { @@ -145,15 +145,13 @@ impl HasChildSource for VariantId { (lookup.source(db).map(|it| it.kind()), lookup.container) } }; - let span_map = db.span_map(src.file_id); let mut map = ArenaMap::new(); match &src.value { ast::StructKind::Tuple(fl) => { let cfg_options = container.krate.cfg_options(db); let mut idx = 0; for fd in fl.fields() { - let enabled = - Attrs::is_cfg_enabled_for(db, &fd, span_map.as_ref(), cfg_options).is_ok(); + let enabled = AttrFlags::is_cfg_enabled_for(&fd, cfg_options).is_ok(); if !enabled { continue; } @@ -168,8 +166,7 @@ impl HasChildSource for VariantId { let cfg_options = container.krate.cfg_options(db); let mut idx = 0; for fd in fl.fields() { - let enabled = - Attrs::is_cfg_enabled_for(db, &fd, span_map.as_ref(), cfg_options).is_ok(); + let enabled = AttrFlags::is_cfg_enabled_for(&fd, cfg_options).is_ok(); if !enabled { continue; } diff --git a/crates/hir-def/src/test_db.rs b/crates/hir-def/src/test_db.rs index e30a5b65a1f7..e9e8a510903d 100644 --- a/crates/hir-def/src/test_db.rs +++ b/crates/hir-def/src/test_db.rs @@ -173,7 +173,15 @@ impl TestDB { let mut res = DefMap::ROOT; for (module, data) in def_map.modules() { let src = data.definition_source(self); - if src.file_id != position.file_id { + // We're not comparing the `base_db::EditionedFileId`, but rather the VFS `FileId`, because + // `position.file_id` is created before the def map, causing it to have to wrong crate + // attached often, which means it won't compare equal. This should not be a problem in real + // r-a session, only in tests, because in real r-a we only guess the crate on syntactic-only + // (e.g. on-enter) handlers. The rest pick the `EditionedFileId` from the def map. + let Some(file_id) = src.file_id.file_id() else { + continue; + }; + if file_id.file_id(self) != position.file_id.file_id(self) { continue; } @@ -213,7 +221,15 @@ impl TestDB { 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 { + // We're not comparing the `base_db::EditionedFileId`, but rather the VFS `FileId`, because + // `position.file_id` is created before the def map, causing it to have to wrong crate + // attached often, which means it won't compare equal. This should not be a problem in real + // r-a session, only in tests, because in real r-a we only guess the crate on syntactic-only + // (e.g. on-enter) handlers. The rest pick the `EditionedFileId` from the def map. + let Some(file_id) = file_id.file_id() else { + continue; + }; + if file_id.file_id(self) != position.file_id.file_id(self) { continue; } for decl in module.scope.declarations() { @@ -236,26 +252,25 @@ impl TestDB { }; if size != Some(new_size) { size = Some(new_size); - fn_def = Some(it); + fn_def = Some((it, file_id)); } } } } // Find the innermost block expression that has a `DefMap`. - let def_with_body = fn_def?.into(); + let (def_with_body, file_id) = fn_def?; + let def_with_body = def_with_body.into(); let source_map = self.body_with_source_map(def_with_body).1; let scopes = self.expr_scopes(def_with_body); - let root_syntax_node = self.parse(position.file_id).syntax_node(); + let root_syntax_node = self.parse(file_id).syntax_node(); 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))? - .as_expr() - .unwrap(); + let expr_id = + source_map.node_expr(InFile::new(file_id.into(), &expr))?.as_expr().unwrap(); let scope = scopes.scope_for(expr_id).unwrap(); Some(scope) }); diff --git a/crates/hir-expand/Cargo.toml b/crates/hir-expand/Cargo.toml index 80a3c0848653..4fa476afb64a 100644 --- a/crates/hir-expand/Cargo.toml +++ b/crates/hir-expand/Cargo.toml @@ -23,6 +23,8 @@ triomphe.workspace = true query-group.workspace = true salsa.workspace = true salsa-macros.workspace = true +arrayvec.workspace = true +thin-vec.workspace = true # local deps stdx.workspace = true diff --git a/crates/hir-expand/src/attrs.rs b/crates/hir-expand/src/attrs.rs index 986f8764f5c9..e1807cd2e1e9 100644 --- a/crates/hir-expand/src/attrs.rs +++ b/crates/hir-expand/src/attrs.rs @@ -1,200 +1,397 @@ -//! A higher level attributes based on TokenTree, with also some shortcuts. -use std::iter; -use std::{borrow::Cow, fmt, ops}; +//! Defines the basics of attributes lowering. +//! +//! The heart and soul of this module is [`expand_cfg_attr()`], alongside its sibling +//! [`expand_cfg_attr_with_doc_comments()`]. It is used to implement all attribute lowering +//! in r-a. Its basic job is to list attributes; however, attributes do not necessarily map +//! into [`ast::Attr`], because `cfg_attr` can map to zero, one, or more attributes +//! (`#[cfg_attr(predicate, attr1, attr2, ...)]`). To bridge this gap, this module defines +//! [`Meta`], which represents a desugared attribute. Various bits of r-a need different +//! things from [`Meta`], therefore it contains many parts. The basic idea is: +//! +//! - There are three kinds of attributes, `path = value`, `path`, and `path(token_tree)`. +//! - Most bits of rust-analyzer only need to deal with some paths. Therefore, we keep +//! the path only if it has up to 2 segments, or one segment for `path = value`. +//! We also only keep the value in `path = value` if it is a literal. However, we always +//! save the all relevant ranges of attributes (the path range, and the full attribute range) +//! for parts of r-a (e.g. name resolution) that need a faithful representation of the +//! attribute. +//! +//! [`expand_cfg_attr()`] expands `cfg_attr`s as it goes (as its name implies), to list +//! all attributes. +//! +//! Another thing to note is that we need to be able to map an attribute back to a range +//! (for diagnostic purposes etc.). This is only ever needed for attributes that participate +//! in name resolution. An attribute is mapped back by its [`AttrId`], which is just an +//! index into the item tree attributes list. To minimize the risk of bugs, we have one +//! place (here) and one function ([`is_item_tree_filtered_attr()`]) that decides whether +//! an attribute participate in name resolution. + +use std::{ + borrow::Cow, cell::OnceCell, convert::Infallible, fmt, iter::Peekable, ops::ControlFlow, +}; +use ::tt::{TextRange, TextSize}; +use arrayvec::ArrayVec; use base_db::Crate; use cfg::{CfgExpr, CfgOptions}; use either::Either; -use intern::{Interned, Symbol, sym}; - +use intern::{Interned, Symbol}; use mbe::{DelimiterKind, Punct}; -use smallvec::{SmallVec, smallvec}; -use span::{Span, SyntaxContext}; -use syntax::unescape; -use syntax::{AstNode, AstToken, SyntaxNode, ast, match_ast}; -use syntax_bridge::{DocCommentDesugarMode, desugar_doc_comment_text, syntax_node_to_token_tree}; -use triomphe::ThinArc; +use parser::T; +use smallvec::SmallVec; +use span::{RealSpanMap, Span, SyntaxContext}; +use syntax::{ + AstNode, NodeOrToken, SyntaxNode, SyntaxToken, + ast::{self, TokenTreeChildren}, + unescape, +}; +use syntax_bridge::DocCommentDesugarMode; use crate::{ + AstId, db::ExpandDatabase, mod_path::ModPath, - name::Name, span_map::SpanMapRef, - tt::{self, TopSubtree, token_to_literal}, + tt::{self, TopSubtree}, }; -/// Syntactical attributes, without filtering of `cfg_attr`s. -#[derive(Default, Debug, Clone, PartialEq, Eq)] -pub struct RawAttrs { - // FIXME: This can become `Box<[Attr]>` if https://internals.rust-lang.org/t/layout-of-dst-box/21728?u=chrefr is accepted. - entries: Option>, -} - -impl ops::Deref for RawAttrs { - type Target = [Attr]; - - fn deref(&self) -> &[Attr] { - match &self.entries { - Some(it) => &it.slice, - None => &[], - } - } +#[derive(Debug)] +pub struct AttrPath { + /// This can be empty if the path is not of 1 or 2 segments exactly. + pub segments: ArrayVec, + pub range: TextRange, + // FIXME: This shouldn't be textual, `#[test]` needs name resolution. + // And if textual, it shouldn't be here, it should be in hir-def/src/attrs.rs. But some macros + // fully qualify `test` as `core::prelude::vX::test`, and this is more than 2 segments, so hir-def + // attrs can't find it. But this will mean we have to push every up-to-4-segments path, which + // may impact perf. So it was easier to just hack it here. + pub is_test: bool, } -impl RawAttrs { - pub const EMPTY: Self = Self { entries: None }; - - pub fn new( - db: &dyn ExpandDatabase, - owner: &dyn ast::HasAttrs, - span_map: SpanMapRef<'_>, - ) -> Self { - let entries: Vec<_> = Self::attrs_iter::(db, owner, span_map).collect(); - - let entries = if entries.is_empty() { - None - } else { - Some(ThinArc::from_header_and_iter((), entries.into_iter())) - }; - - RawAttrs { entries } - } - - /// A [`RawAttrs`] that has its `#[cfg_attr(...)]` attributes expanded. - pub fn new_expanded( - db: &dyn ExpandDatabase, - owner: &dyn ast::HasAttrs, - span_map: SpanMapRef<'_>, - cfg_options: &CfgOptions, - ) -> Self { - let entries: Vec<_> = - Self::attrs_iter_expanded::(db, owner, span_map, cfg_options).collect(); - - let entries = if entries.is_empty() { - None - } else { - Some(ThinArc::from_header_and_iter((), entries.into_iter())) - }; - - RawAttrs { entries } - } - - pub fn attrs_iter( - db: &dyn ExpandDatabase, - owner: &dyn ast::HasAttrs, - span_map: SpanMapRef<'_>, - ) -> impl Iterator { - collect_attrs(owner).filter_map(move |(id, attr)| match attr { - Either::Left(attr) => { - attr.meta().and_then(|meta| Attr::from_src(db, meta, span_map, id)) +impl AttrPath { + #[inline] + fn extract(path: &ast::Path) -> Self { + let mut is_test = false; + let segments = (|| { + let mut segments = ArrayVec::new(); + let segment2 = path.segment()?.name_ref()?.syntax().first_token()?; + if segment2.text() == "test" { + // `#[test]` or `#[core::prelude::vX::test]`. + is_test = true; } - Either::Right(comment) if DESUGAR_COMMENTS => comment.doc_comment().map(|doc| { - let span = span_map.span_for_range(comment.syntax().text_range()); - let (text, kind) = desugar_doc_comment_text(doc, DocCommentDesugarMode::ProcMacro); - Attr { - id, - input: Some(Box::new(AttrInput::Literal(tt::Literal { - symbol: text, - span, - kind, - suffix: None, - }))), - path: Interned::new(ModPath::from(Name::new_symbol(sym::doc, span.ctx))), - ctxt: span.ctx, + let segment1 = path.qualifier(); + if let Some(segment1) = segment1 { + if segment1.qualifier().is_some() { + None + } else { + let segment1 = segment1.segment()?.name_ref()?.syntax().first_token()?; + segments.push(segment1); + segments.push(segment2); + Some(segments) } - }), - Either::Right(_) => None, - }) + } else { + segments.push(segment2); + Some(segments) + } + })(); + AttrPath { + segments: segments.unwrap_or(ArrayVec::new()), + range: path.syntax().text_range(), + is_test, + } } - pub fn attrs_iter_expanded( - db: &dyn ExpandDatabase, - owner: &dyn ast::HasAttrs, - span_map: SpanMapRef<'_>, - cfg_options: &CfgOptions, - ) -> impl Iterator { - Self::attrs_iter::(db, owner, span_map) - .flat_map(|attr| attr.expand_cfg_attr(db, cfg_options)) + #[inline] + pub fn is1(&self, segment: &str) -> bool { + self.segments.len() == 1 && self.segments[0].text() == segment } +} - pub fn merge(&self, other: Self) -> Self { - 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.slice.last().map_or(0, |it| it.id.ast_index() + 1); - let items = a - .slice - .iter() - .cloned() - .chain(b.slice.iter().map(|it| { - let mut it = it.clone(); - let id = it.id.ast_index() + last_ast_index; - it.id = AttrId::new(id, it.id.is_inner_attr()); - it - })) - .collect::>(); - Self { entries: Some(ThinArc::from_header_and_iter((), items.into_iter())) } - } +#[derive(Debug)] +pub enum Meta { + /// `name` is `None` if not a single token. `value` is a literal or `None`. + NamedKeyValue { + path_range: TextRange, + name: Option, + value: Option, + }, + TokenTree { + path: AttrPath, + tt: ast::TokenTree, + }, + Path { + path: AttrPath, + }, +} + +impl Meta { + #[inline] + pub fn path_range(&self) -> TextRange { + match self { + Meta::NamedKeyValue { path_range, .. } => *path_range, + Meta::TokenTree { path, .. } | Meta::Path { path } => path.range, } } - /// Processes `cfg_attr`s - pub fn expand_cfg_attr(self, db: &dyn ExpandDatabase, krate: Crate) -> RawAttrs { - let has_cfg_attrs = - self.iter().any(|attr| attr.path.as_ident().is_some_and(|name| *name == sym::cfg_attr)); - if !has_cfg_attrs { - return self; + fn extract(iter: &mut Peekable) -> Option<(Self, TextSize)> { + let mut start_offset = None; + if let Some(NodeOrToken::Token(colon1)) = iter.peek() + && colon1.kind() == T![:] + { + start_offset = Some(colon1.text_range().start()); + iter.next(); + iter.next_if(|it| it.as_token().is_some_and(|it| it.kind() == T![:])); + } + let first_segment = iter + .next_if(|it| it.as_token().is_some_and(|it| it.kind().is_any_identifier()))? + .into_token()?; + let mut is_test = first_segment.text() == "test"; + let start_offset = start_offset.unwrap_or_else(|| first_segment.text_range().start()); + + let mut segments_len = 1; + let mut second_segment = None; + let mut path_range = first_segment.text_range(); + while iter.peek().and_then(NodeOrToken::as_token).is_some_and(|it| it.kind() == T![:]) + && let _ = iter.next() + && iter.peek().and_then(NodeOrToken::as_token).is_some_and(|it| it.kind() == T![:]) + && let _ = iter.next() + && let Some(NodeOrToken::Token(segment)) = iter.peek() + && segment.kind().is_any_identifier() + { + segments_len += 1; + is_test = segment.text() == "test"; + second_segment = Some(segment.clone()); + path_range = TextRange::new(path_range.start(), segment.text_range().end()); + iter.next(); } - let cfg_options = krate.cfg_options(db); - let new_attrs = self - .iter() - .cloned() - .flat_map(|attr| attr.expand_cfg_attr(db, cfg_options)) - .collect::>(); - let entries = if new_attrs.is_empty() { - None - } else { - Some(ThinArc::from_header_and_iter((), new_attrs.into_iter())) + let segments = |first, second| { + let mut segments = ArrayVec::new(); + if segments_len <= 2 { + segments.push(first); + if let Some(second) = second { + segments.push(second); + } + } + segments + }; + let meta = match iter.peek() { + Some(NodeOrToken::Token(eq)) if eq.kind() == T![=] => { + iter.next(); + let value = match iter.peek() { + Some(NodeOrToken::Token(token)) if token.kind().is_literal() => { + // No need to consume it, it will be consumed by `extract_and_eat_comma()`. + Some(token.clone()) + } + _ => None, + }; + let name = if second_segment.is_none() { Some(first_segment) } else { None }; + Meta::NamedKeyValue { path_range, name, value } + } + Some(NodeOrToken::Node(tt)) => Meta::TokenTree { + path: AttrPath { + segments: segments(first_segment, second_segment), + range: path_range, + is_test, + }, + tt: tt.clone(), + }, + _ => Meta::Path { + path: AttrPath { + segments: segments(first_segment, second_segment), + range: path_range, + is_test, + }, + }, }; - RawAttrs { entries } + Some((meta, start_offset)) } - pub fn is_empty(&self) -> bool { - self.entries.is_none() + fn extract_possibly_unsafe( + iter: &mut Peekable, + container: &ast::TokenTree, + ) -> Option<(Self, TextRange)> { + if iter.peek().is_some_and(|it| it.as_token().is_some_and(|it| it.kind() == T![unsafe])) { + iter.next(); + let tt = iter.next()?.into_node()?; + let result = Self::extract(&mut TokenTreeChildren::new(&tt).peekable()).map( + |(meta, start_offset)| (meta, TextRange::new(start_offset, tt_end_offset(&tt))), + ); + while iter.next().is_some_and(|it| it.as_token().is_none_or(|it| it.kind() != T![,])) {} + result + } else { + Self::extract(iter).map(|(meta, start_offset)| { + let end_offset = 'find_end_offset: { + for it in iter { + if let NodeOrToken::Token(it) = it + && it.kind() == T![,] + { + break 'find_end_offset it.text_range().start(); + } + } + tt_end_offset(container) + }; + (meta, TextRange::new(start_offset, end_offset)) + }) + } } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct AttrId { - id: u32, +fn tt_end_offset(tt: &ast::TokenTree) -> TextSize { + tt.syntax().last_token().unwrap().text_range().start() } -// FIXME: This only handles a single level of cfg_attr nesting -// that is `#[cfg_attr(all(), cfg_attr(all(), cfg(any())))]` breaks again -impl AttrId { - const INNER_ATTR_SET_BIT: u32 = 1 << 31; +/// The callback is passed a desugared form of the attribute ([`Meta`]), a [`SyntaxNode`] fully containing it +/// (note: it may not be the direct parent), the range within the [`SyntaxNode`] bounding the attribute, +/// and the outermost `ast::Attr`. Note that one node may map to multiple [`Meta`]s due to `cfg_attr`. +#[inline] +pub fn expand_cfg_attr<'a, BreakValue>( + attrs: impl Iterator, + cfg_options: impl FnMut() -> &'a CfgOptions, + mut callback: impl FnMut(Meta, &SyntaxNode, TextRange, &ast::Attr) -> ControlFlow, +) -> Option { + expand_cfg_attr_with_doc_comments::( + attrs.map(Either::Left), + cfg_options, + move |Either::Left((meta, container, range, top_attr))| { + callback(meta, container, range, top_attr) + }, + ) +} - pub fn new(id: usize, is_inner: bool) -> Self { - assert!(id <= !Self::INNER_ATTR_SET_BIT as usize); - let id = id as u32; - Self { id: if is_inner { id | Self::INNER_ATTR_SET_BIT } else { id } } - } +#[inline] +pub fn expand_cfg_attr_with_doc_comments<'a, DocComment, BreakValue>( + mut attrs: impl Iterator>, + mut cfg_options: impl FnMut() -> &'a CfgOptions, + mut callback: impl FnMut( + Either<(Meta, &SyntaxNode, TextRange, &ast::Attr), DocComment>, + ) -> ControlFlow, +) -> Option { + let mut stack = SmallVec::<[_; 1]>::new(); + let result = attrs.try_for_each(|top_attr| { + let top_attr = match top_attr { + Either::Left(it) => it, + Either::Right(comment) => return callback(Either::Right(comment)), + }; + if let Some((attr_name, tt)) = top_attr.as_simple_call() + && attr_name == "cfg_attr" + { + let mut tt_iter = TokenTreeChildren::new(&tt).peekable(); + let cfg = cfg::CfgExpr::parse_from_ast(&mut tt_iter); + if cfg_options().check(&cfg) != Some(false) { + stack.push((tt_iter, tt)); + while let Some((tt_iter, tt)) = stack.last_mut() { + let Some((attr, range)) = Meta::extract_possibly_unsafe(tt_iter, tt) else { + stack.pop(); + continue; + }; + if let Meta::TokenTree { path, tt: nested_tt } = &attr + && path.is1("cfg_attr") + { + let mut nested_tt_iter = TokenTreeChildren::new(nested_tt).peekable(); + let cfg = cfg::CfgExpr::parse_from_ast(&mut nested_tt_iter); + if cfg_options().check(&cfg) != Some(false) { + stack.push((nested_tt_iter, nested_tt.clone())); + } + } else { + callback(Either::Left((attr, tt.syntax(), range, &top_attr)))?; + } + } + } + } else if let Some(ast_meta) = top_attr.meta() + && let Some(path) = ast_meta.path() + { + let path = AttrPath::extract(&path); + let meta = if let Some(tt) = ast_meta.token_tree() { + Meta::TokenTree { path, tt } + } else if let Some(value) = ast_meta.expr() { + let value = + if let ast::Expr::Literal(value) = value { Some(value.token()) } else { None }; + let name = + if path.segments.len() == 1 { Some(path.segments[0].clone()) } else { None }; + Meta::NamedKeyValue { name, value, path_range: path.range } + } else { + Meta::Path { path } + }; + callback(Either::Left(( + meta, + ast_meta.syntax(), + ast_meta.syntax().text_range(), + &top_attr, + )))?; + } + ControlFlow::Continue(()) + }); + result.break_value() +} - pub fn ast_index(&self) -> usize { - (self.id & !Self::INNER_ATTR_SET_BIT) as usize - } +#[inline] +pub(crate) fn is_item_tree_filtered_attr(name: &str) -> bool { + matches!( + name, + "doc" + | "stable" + | "unstable" + | "target_feature" + | "allow" + | "expect" + | "warn" + | "deny" + | "forbid" + | "repr" + | "inline" + | "track_caller" + | "must_use" + ) +} - pub fn is_inner_attr(&self) -> bool { - self.id & Self::INNER_ATTR_SET_BIT != 0 - } +/// This collects attributes exactly as the item tree needs them. This is used for the item tree, +/// as well as for resolving [`AttrId`]s. +pub fn collect_item_tree_attrs<'a, BreakValue>( + owner: &dyn ast::HasAttrs, + cfg_options: impl Fn() -> &'a CfgOptions, + mut on_attr: impl FnMut(Meta, &SyntaxNode, &ast::Attr, TextRange) -> ControlFlow, +) -> Option> { + let attrs = ast::attrs_including_inner(owner); + expand_cfg_attr( + attrs, + || cfg_options(), + |attr, container, range, top_attr| { + // We filter builtin attributes that we don't need for nameres, because this saves memory. + // I only put the most common attributes, but if some attribute becomes common feel free to add it. + // Notice, however: for an attribute to be filtered out, it *must* not be shadowable with a macro! + let filter = match &attr { + Meta::NamedKeyValue { name: Some(name), .. } => { + is_item_tree_filtered_attr(name.text()) + } + Meta::TokenTree { path, tt } if path.segments.len() == 1 => { + let name = path.segments[0].text(); + if name == "cfg" { + let cfg = + CfgExpr::parse_from_ast(&mut TokenTreeChildren::new(tt).peekable()); + if cfg_options().check(&cfg) == Some(false) { + return ControlFlow::Break(Either::Right(cfg)); + } + true + } else { + is_item_tree_filtered_attr(name) + } + } + Meta::Path { path } => { + path.segments.len() == 1 && is_item_tree_filtered_attr(path.segments[0].text()) + } + _ => false, + }; + if !filter && let ControlFlow::Break(v) = on_attr(attr, container, top_attr, range) { + return ControlFlow::Break(Either::Left(v)); + } + ControlFlow::Continue(()) + }, + ) } #[derive(Debug, Clone, PartialEq, Eq)] pub struct Attr { - pub id: AttrId, pub path: Interned, pub input: Option>, pub ctxt: SyntaxContext, @@ -217,131 +414,6 @@ impl fmt::Display for AttrInput { } } -impl Attr { - fn from_src( - db: &dyn ExpandDatabase, - ast: ast::Meta, - span_map: SpanMapRef<'_>, - id: AttrId, - ) -> Option { - let path = ast.path()?; - let range = path.syntax().text_range(); - let path = Interned::new(ModPath::from_src(db, path, &mut |range| { - span_map.span_for_range(range).ctx - })?); - let span = span_map.span_for_range(range); - let input = if let Some(ast::Expr::Literal(lit)) = ast.expr() { - let token = lit.token(); - Some(Box::new(AttrInput::Literal(token_to_literal(token.text(), span)))) - } else if let Some(tt) = ast.token_tree() { - let tree = syntax_node_to_token_tree( - tt.syntax(), - span_map, - span, - DocCommentDesugarMode::ProcMacro, - ); - Some(Box::new(AttrInput::TokenTree(tree))) - } else { - None - }; - Some(Attr { id, path, input, ctxt: span.ctx }) - } - - fn from_tt( - db: &dyn ExpandDatabase, - mut tt: tt::TokenTreesView<'_>, - id: AttrId, - ) -> Option { - if matches!(tt.flat_tokens(), - [tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { sym, .. })), ..] - if *sym == sym::unsafe_ - ) { - match tt.iter().nth(1) { - Some(tt::TtElement::Subtree(_, iter)) => tt = iter.remaining(), - _ => return None, - } - } - let first = tt.flat_tokens().first()?; - let ctxt = first.first_span().ctx; - let (path, input) = { - let mut iter = tt.iter(); - let start = iter.savepoint(); - let mut input = tt::TokenTreesView::new(&[]); - let mut path = iter.from_savepoint(start); - let mut path_split_savepoint = iter.savepoint(); - while let Some(tt) = iter.next() { - path = iter.from_savepoint(start); - if !matches!( - tt, - tt::TtElement::Leaf( - tt::Leaf::Punct(tt::Punct { char: ':' | '$', .. }) | tt::Leaf::Ident(_), - ) - ) { - input = path_split_savepoint.remaining(); - break; - } - path_split_savepoint = iter.savepoint(); - } - (path, input) - }; - - let path = Interned::new(ModPath::from_tt(db, path)?); - - let input = match (input.flat_tokens().first(), input.try_into_subtree()) { - (_, Some(tree)) => { - Some(Box::new(AttrInput::TokenTree(tt::TopSubtree::from_subtree(tree)))) - } - (Some(tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: '=', .. }))), _) => { - match input.flat_tokens().get(1) { - Some(tt::TokenTree::Leaf(tt::Leaf::Literal(lit))) => { - Some(Box::new(AttrInput::Literal(lit.clone()))) - } - _ => None, - } - } - _ => None, - }; - Some(Attr { id, path, input, ctxt }) - } - - pub fn path(&self) -> &ModPath { - &self.path - } - - pub fn expand_cfg_attr( - self, - db: &dyn ExpandDatabase, - cfg_options: &CfgOptions, - ) -> impl IntoIterator { - let is_cfg_attr = self.path.as_ident().is_some_and(|name| *name == sym::cfg_attr); - if !is_cfg_attr { - return smallvec![self]; - } - - let subtree = match self.token_tree_value() { - Some(it) => it, - _ => return smallvec![self.clone()], - }; - - let (cfg, parts) = match parse_cfg_attr_input(subtree) { - Some(it) => it, - None => return smallvec![self.clone()], - }; - let index = self.id; - let attrs = parts.filter_map(|attr| Attr::from_tt(db, attr, index)); - - let cfg = TopSubtree::from_token_trees(subtree.top_subtree().delimiter, cfg); - let cfg = CfgExpr::parse(&cfg); - if cfg_options.check(&cfg) == Some(false) { - smallvec![] - } else { - cov_mark::hit!(cfg_attr_active); - - attrs.collect::>() - } - } -} - impl Attr { /// #[path = "string"] pub fn string_value(&self) -> Option<&Symbol> { @@ -403,30 +475,26 @@ impl Attr { pub fn parse_path_comma_token_tree<'a>( &'a self, db: &'a dyn ExpandDatabase, - ) -> Option + 'a> { + ) -> Option)> + 'a> { let args = self.token_tree_value()?; if args.top_subtree().delimiter.kind != DelimiterKind::Parenthesis { return None; } - let paths = args - .token_trees() - .split(|tt| matches!(tt, tt::TtElement::Leaf(tt::Leaf::Punct(Punct { char: ',', .. })))) - .filter_map(move |tts| { - let span = tts.flat_tokens().first()?.first_span(); - Some((ModPath::from_tt(db, tts)?, span)) - }); - - Some(paths) + Some(parse_path_comma_token_tree(db, args)) } +} - pub fn cfg(&self) -> Option { - if *self.path.as_ident()? == sym::cfg { - self.token_tree_value().map(CfgExpr::parse) - } else { - None - } - } +fn parse_path_comma_token_tree<'a>( + db: &'a dyn ExpandDatabase, + args: &'a tt::TopSubtree, +) -> impl Iterator)> { + args.token_trees() + .split(|tt| matches!(tt, tt::TtElement::Leaf(tt::Leaf::Punct(Punct { char: ',', .. })))) + .filter_map(move |tts| { + let span = tts.flat_tokens().first()?.first_span(); + Some((ModPath::from_tt(db, tts)?, span, tts)) + }) } fn unescape(s: &str) -> Option> { @@ -455,58 +523,104 @@ fn unescape(s: &str) -> Option> { } } -pub fn collect_attrs( - owner: &dyn ast::HasAttrs, -) -> impl Iterator)> { - let inner_attrs = - inner_attributes(owner.syntax()).into_iter().flatten().zip(iter::repeat(true)); - 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(), - }) - .zip(iter::repeat(false)); - outer_attrs - .chain(inner_attrs) - .enumerate() - .map(|(id, (attr, is_inner))| (AttrId::new(id, is_inner), attr)) +/// This is an index of an attribute *that always points to the item tree attributes*. +/// +/// Outer attributes are counted first, then inner attributes. This does not support +/// out-of-line modules, which may have attributes spread across 2 files! +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct AttrId { + id: u32, } -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) => { - if !it.may_carry_attributes() { - return None +impl AttrId { + #[inline] + pub fn from_item_tree_index(id: u32) -> Self { + Self { id } + } + + #[inline] + pub fn item_tree_index(self) -> u32 { + self.id + } + + /// Returns the containing `ast::Attr` (note that it may contain other attributes as well due + /// to `cfg_attr`), a `SyntaxNode` guaranteed to contain the attribute, the full range of the + /// attribute, and its desugared [`Meta`]. + pub fn find_attr_range( + self, + db: &dyn ExpandDatabase, + krate: Crate, + owner: AstId, + ) -> (ast::Attr, SyntaxNode, TextRange, Meta) { + self.find_attr_range_with_source(db, krate, &owner.to_node(db)) + } + + /// Returns the containing `ast::Attr` (note that it may contain other attributes as well due + /// to `cfg_attr`), a `SyntaxNode` guaranteed to contain the attribute, the full range of the + /// attribute, and its desugared [`Meta`]. + pub fn find_attr_range_with_source( + self, + db: &dyn ExpandDatabase, + krate: Crate, + owner: &dyn ast::HasAttrs, + ) -> (ast::Attr, SyntaxNode, TextRange, Meta) { + let cfg_options = OnceCell::new(); + let mut index = 0; + let result = collect_item_tree_attrs( + owner, + || cfg_options.get_or_init(|| krate.cfg_options(db)), + |meta, container, top_attr, range| { + if index == self.id { + return ControlFlow::Break((top_attr.clone(), container.clone(), range, meta)); } - syntax.clone() + index += 1; + ControlFlow::Continue(()) }, - _ => return None, + ); + match result { + Some(Either::Left(it)) => it, + _ => { + panic!("used an incorrect `AttrId`; crate={krate:?}, attr_id={self:?}"); + } } - }; - - 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) -} + } -// Input subtree is: `(cfg, $(attr),+)` -// Split it up into a `cfg` subtree and the `attr` subtrees. -fn parse_cfg_attr_input( - subtree: &TopSubtree, -) -> Option<(tt::TokenTreesView<'_>, impl Iterator>)> { - let mut parts = subtree - .token_trees() - .split(|tt| matches!(tt, tt::TtElement::Leaf(tt::Leaf::Punct(Punct { char: ',', .. })))); - let cfg = parts.next()?; - Some((cfg, parts.filter(|it| !it.is_empty()))) + pub fn find_derive_range( + self, + db: &dyn ExpandDatabase, + krate: Crate, + owner: AstId, + derive_index: u32, + ) -> TextRange { + let (_, _, derive_attr_range, derive_attr) = self.find_attr_range(db, krate, owner); + let Meta::TokenTree { tt, .. } = derive_attr else { + return derive_attr_range; + }; + // Fake the span map, as we don't really need spans here, just the offsets of the node in the file. + let span_map = RealSpanMap::absolute(span::EditionedFileId::current_edition( + span::FileId::from_raw(0), + )); + let tt = syntax_bridge::syntax_node_to_token_tree( + tt.syntax(), + SpanMapRef::RealSpanMap(&span_map), + span_map.span_for_range(tt.syntax().text_range()), + DocCommentDesugarMode::ProcMacro, + ); + let Some((_, _, derive_tts)) = + parse_path_comma_token_tree(db, &tt).nth(derive_index as usize) + else { + return derive_attr_range; + }; + let (Some(first_tt), Some(last_tt)) = + (derive_tts.flat_tokens().first(), derive_tts.flat_tokens().last()) + else { + return derive_attr_range; + }; + let start = first_tt.first_span().range.start(); + let end = match last_tt { + tt::TokenTree::Leaf(it) => it.span().range.end(), + tt::TokenTree::Subtree(it) => it.delimiter.close.range.end(), + }; + TextRange::new(start, end) + } } diff --git a/crates/hir-expand/src/builtin/fn_macro.rs b/crates/hir-expand/src/builtin/fn_macro.rs index ec3446137616..31dbfaf0b305 100644 --- a/crates/hir-expand/src/builtin/fn_macro.rs +++ b/crates/hir-expand/src/builtin/fn_macro.rs @@ -772,7 +772,7 @@ fn relative_file( if res == call_site && !allow_recursion { Err(ExpandError::other(err_span, format!("recursive inclusion of `{path_str}`"))) } else { - Ok(EditionedFileId::new(db, res, lookup.krate.data(db).edition)) + Ok(EditionedFileId::new(db, res, lookup.krate.data(db).edition, lookup.krate)) } } diff --git a/crates/hir-expand/src/cfg_process.rs b/crates/hir-expand/src/cfg_process.rs index d5ebd6ee19f5..8b82671ed4a0 100644 --- a/crates/hir-expand/src/cfg_process.rs +++ b/crates/hir-expand/src/cfg_process.rs @@ -1,373 +1,343 @@ //! Processes out #[cfg] and #[cfg_attr] attributes from the input for the derive macro -use std::iter::Peekable; +use std::{cell::OnceCell, ops::ControlFlow}; +use ::tt::TextRange; use base_db::Crate; -use cfg::{CfgAtom, CfgExpr}; -use intern::{Symbol, sym}; -use rustc_hash::FxHashSet; +use cfg::CfgExpr; +use parser::T; +use smallvec::SmallVec; use syntax::{ - AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, T, - ast::{self, Attr, HasAttrs, Meta, TokenTree, VariantList}, + AstNode, PreorderWithTokens, SyntaxElement, SyntaxNode, SyntaxToken, WalkEvent, + ast::{self, HasAttrs, TokenTreeChildren}, }; -use tracing::{debug, warn}; +use syntax_bridge::DocCommentDesugarMode; -use crate::{MacroCallLoc, MacroDefKind, db::ExpandDatabase, proc_macro::ProcMacroKind}; +use crate::{ + attrs::{AttrId, Meta, expand_cfg_attr, is_item_tree_filtered_attr}, + db::ExpandDatabase, + fixup::{self, SyntaxFixupUndoInfo}, + span_map::SpanMapRef, + tt::{self, DelimSpan, Span}, +}; -fn check_cfg(db: &dyn ExpandDatabase, attr: &Attr, krate: Crate) -> Option { - if !attr.simple_name().as_deref().map(|v| v == "cfg")? { - return None; - } - let cfg = parse_from_attr_token_tree(&attr.meta()?.token_tree()?)?; - let enabled = krate.cfg_options(db).check(&cfg) != Some(false); - Some(enabled) +struct ItemIsCfgedOut; + +#[derive(Debug)] +struct ExpandedAttrToProcess { + range: TextRange, } -fn check_cfg_attr(db: &dyn ExpandDatabase, attr: &Attr, krate: Crate) -> Option { - if !attr.simple_name().as_deref().map(|v| v == "cfg_attr")? { - return None; - } - check_cfg_attr_value(db, &attr.token_tree()?, krate) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum NextExpandedAttrState { + NotStarted, + InTheMiddle, } -pub fn check_cfg_attr_value( - db: &dyn ExpandDatabase, - attr: &TokenTree, - krate: Crate, -) -> Option { - let cfg_expr = parse_from_attr_token_tree(attr)?; - let enabled = krate.cfg_options(db).check(&cfg_expr) != Some(false); - Some(enabled) +#[derive(Debug)] +struct AstAttrToProcess { + range: TextRange, + expanded_attrs: SmallVec<[ExpandedAttrToProcess; 1]>, + expanded_attrs_idx: usize, + next_expanded_attr: NextExpandedAttrState, + pound_span: Span, + brackets_span: DelimSpan, + /// If `Some`, this is an inner attribute. + excl_span: Option, } -fn process_has_attrs_with_possible_comma( +fn macro_input_callback( db: &dyn ExpandDatabase, - items: impl Iterator, + is_derive: bool, + censor_item_tree_attr_ids: &[AttrId], krate: Crate, - remove: &mut FxHashSet, -) -> Option<()> { - for item in items { - let field_attrs = item.attrs(); - 'attrs: for attr in field_attrs { - if let Some(enabled) = check_cfg(db, &attr, krate) { - if enabled { - debug!("censoring {:?}", attr.syntax()); - remove.insert(attr.syntax().clone().into()); - } else { - debug!("censoring {:?}", item.syntax()); - remove.insert(item.syntax().clone().into()); - // We need to remove the , as well - remove_possible_comma(&item, remove); - break 'attrs; - } - } + default_span: Span, + span_map: SpanMapRef<'_>, +) -> impl FnMut(&mut PreorderWithTokens, &WalkEvent) -> (bool, Vec) { + let cfg_options = OnceCell::new(); + let cfg_options = move || *cfg_options.get_or_init(|| krate.cfg_options(db)); - if let Some(enabled) = check_cfg_attr(db, &attr, krate) { - if enabled { - debug!("Removing cfg_attr tokens {:?}", attr); - let meta = attr.meta()?; - let removes_from_cfg_attr = remove_tokens_within_cfg_attr(meta)?; - remove.extend(removes_from_cfg_attr); - } else { - debug!("censoring type cfg_attr {:?}", item.syntax()); - remove.insert(attr.syntax().clone().into()); - } + let mut should_strip_attr = { + let mut item_tree_attr_id = 0; + let mut censor_item_tree_attr_ids_index = 0; + move || { + let mut result = false; + if let Some(&next_censor_attr_id) = + censor_item_tree_attr_ids.get(censor_item_tree_attr_ids_index) + && next_censor_attr_id.item_tree_index() == item_tree_attr_id + { + censor_item_tree_attr_ids_index += 1; + result = true; } + item_tree_attr_id += 1; + result } - } - Some(()) -} + }; -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -enum CfgExprStage { - /// Stripping the CFGExpr part of the attribute - StrippigCfgExpr, - /// Found the comma after the CFGExpr. Will keep all tokens until the next comma or the end of the attribute - FoundComma, - /// Everything following the attribute. This could be another attribute or the end of the attribute. - // FIXME: cfg_attr with multiple attributes will not be handled correctly. We will only keep the first attribute - // Related Issue: https://github.com/rust-lang/rust-analyzer/issues/10110 - EverythingElse, -} + let mut attrs = Vec::new(); + let mut attrs_idx = 0; + let mut has_inner_attrs_owner = false; + let mut in_attr = false; + let mut done_with_attrs = false; + let mut did_top_attrs = false; + move |preorder, event| { + match event { + WalkEvent::Enter(SyntaxElement::Node(node)) => { + if done_with_attrs { + return (true, Vec::new()); + } -/// This function creates its own set of tokens to remove. To help prevent malformed syntax as input. -fn remove_tokens_within_cfg_attr(meta: Meta) -> Option> { - let mut remove: FxHashSet = FxHashSet::default(); - debug!("Enabling attribute {}", meta); - let meta_path = meta.path()?; - debug!("Removing {:?}", meta_path.syntax()); - remove.insert(meta_path.syntax().clone().into()); + if ast::Attr::can_cast(node.kind()) { + in_attr = true; + let node_range = node.text_range(); + while attrs + .get(attrs_idx) + .is_some_and(|it: &AstAttrToProcess| it.range != node_range) + { + attrs_idx += 1; + } + } else if let Some(has_attrs) = ast::AnyHasAttrs::cast(node.clone()) { + if has_inner_attrs_owner { + has_inner_attrs_owner = false; + return (true, Vec::new()); + } - let meta_tt = meta.token_tree()?; - debug!("meta_tt {}", meta_tt); - let mut stage = CfgExprStage::StrippigCfgExpr; - for tt in meta_tt.token_trees_and_tokens() { - debug!("Checking {:?}. Stage: {:?}", tt, stage); - match (stage, tt) { - (CfgExprStage::StrippigCfgExpr, syntax::NodeOrToken::Node(node)) => { - remove.insert(node.syntax().clone().into()); - } - (CfgExprStage::StrippigCfgExpr, syntax::NodeOrToken::Token(token)) => { - if token.kind() == T![,] { - stage = CfgExprStage::FoundComma; - } - remove.insert(token.into()); - } - (CfgExprStage::FoundComma, syntax::NodeOrToken::Token(token)) - if (token.kind() == T![,] || token.kind() == T![')']) => - { - // The end of the attribute or separator for the next attribute - stage = CfgExprStage::EverythingElse; - remove.insert(token.into()); - } - (CfgExprStage::EverythingElse, syntax::NodeOrToken::Node(node)) => { - remove.insert(node.syntax().clone().into()); - } - (CfgExprStage::EverythingElse, syntax::NodeOrToken::Token(token)) => { - remove.insert(token.into()); - } - // This is an actual attribute - _ => {} - } - } - if stage != CfgExprStage::EverythingElse { - warn!("Invalid cfg_attr attribute. {:?}", meta_tt); - return None; - } - Some(remove) -} -/// Removes a possible comma after the [AstNode] -fn remove_possible_comma(item: &impl AstNode, res: &mut FxHashSet) { - if let Some(comma) = item.syntax().next_sibling_or_token().filter(|it| it.kind() == T![,]) { - res.insert(comma); - } -} -fn process_enum( - db: &dyn ExpandDatabase, - variants: VariantList, - krate: Crate, - remove: &mut FxHashSet, -) -> Option<()> { - 'variant: for variant in variants.variants() { - for attr in variant.attrs() { - if let Some(enabled) = check_cfg(db, &attr, krate) { - if enabled { - debug!("censoring {:?}", attr.syntax()); - remove.insert(attr.syntax().clone().into()); - } else { - // Rustc does not strip the attribute if it is enabled. So we will leave it - debug!("censoring type {:?}", variant.syntax()); - remove.insert(variant.syntax().clone().into()); - // We need to remove the , as well - remove_possible_comma(&variant, remove); - continue 'variant; - } - } + if did_top_attrs && !is_derive { + // Derives need all attributes handled, but attribute macros need only the top attributes handled. + done_with_attrs = true; + return (true, Vec::new()); + } + did_top_attrs = true; + + if let Some(inner_attrs_node) = has_attrs.inner_attributes_node() + && inner_attrs_node != *node + { + has_inner_attrs_owner = true; + } + + let node_attrs = ast::attrs_including_inner(&has_attrs); + + attrs.clear(); + node_attrs.clone().for_each(|attr| { + let span_for = |token: Option| { + token + .map(|token| span_map.span_for_range(token.text_range())) + .unwrap_or(default_span) + }; + attrs.push(AstAttrToProcess { + range: attr.syntax().text_range(), + pound_span: span_for(attr.pound_token()), + brackets_span: DelimSpan { + open: span_for(attr.l_brack_token()), + close: span_for(attr.r_brack_token()), + }, + excl_span: attr + .excl_token() + .map(|token| span_map.span_for_range(token.text_range())), + expanded_attrs: SmallVec::new(), + expanded_attrs_idx: 0, + next_expanded_attr: NextExpandedAttrState::NotStarted, + }); + }); + + attrs_idx = 0; + let strip_current_item = expand_cfg_attr( + node_attrs, + &cfg_options, + |attr, _container, range, top_attr| { + // Find the attr. + while attrs[attrs_idx].range != top_attr.syntax().text_range() { + attrs_idx += 1; + } + + let mut strip_current_attr = false; + match attr { + Meta::NamedKeyValue { name, .. } => { + if name + .is_none_or(|name| !is_item_tree_filtered_attr(name.text())) + { + strip_current_attr = should_strip_attr(); + } + } + Meta::TokenTree { path, tt } => { + if path.segments.len() != 1 + || !is_item_tree_filtered_attr(path.segments[0].text()) + { + strip_current_attr = should_strip_attr(); + } + + if path.segments.len() == 1 { + let name = path.segments[0].text(); + + if name == "cfg" { + let cfg_expr = CfgExpr::parse_from_ast( + &mut TokenTreeChildren::new(&tt).peekable(), + ); + if cfg_options().check(&cfg_expr) == Some(false) { + return ControlFlow::Break(ItemIsCfgedOut); + } + strip_current_attr = true; + } + } + } + Meta::Path { path } => { + if path.segments.len() != 1 + || !is_item_tree_filtered_attr(path.segments[0].text()) + { + strip_current_attr = should_strip_attr(); + } + } + } + + if !strip_current_attr { + attrs[attrs_idx] + .expanded_attrs + .push(ExpandedAttrToProcess { range }); + } - if let Some(enabled) = check_cfg_attr(db, &attr, krate) { - if enabled { - debug!("Removing cfg_attr tokens {:?}", attr); - let meta = attr.meta()?; - let removes_from_cfg_attr = remove_tokens_within_cfg_attr(meta)?; - remove.extend(removes_from_cfg_attr); - } else { - debug!("censoring type cfg_attr {:?}", variant.syntax()); - remove.insert(attr.syntax().clone().into()); + ControlFlow::Continue(()) + }, + ); + attrs_idx = 0; + + if strip_current_item.is_some() { + preorder.skip_subtree(); + attrs.clear(); + + 'eat_comma: { + // If there is a comma after this node, eat it too. + let mut events_until_comma = 0; + for event in preorder.clone() { + match event { + WalkEvent::Enter(SyntaxElement::Node(_)) + | WalkEvent::Leave(_) => {} + WalkEvent::Enter(SyntaxElement::Token(token)) => { + let kind = token.kind(); + if kind == T![,] { + break; + } else if !kind.is_trivia() { + break 'eat_comma; + } + } + } + events_until_comma += 1; + } + preorder.nth(events_until_comma); + } + + return (false, Vec::new()); + } } } - } - if let Some(fields) = variant.field_list() { - match fields { - ast::FieldList::RecordFieldList(fields) => { - process_has_attrs_with_possible_comma(db, fields.fields(), krate, remove)?; - } - ast::FieldList::TupleFieldList(fields) => { - process_has_attrs_with_possible_comma(db, fields.fields(), krate, remove)?; + WalkEvent::Leave(SyntaxElement::Node(node)) => { + if ast::Attr::can_cast(node.kind()) { + in_attr = false; + attrs_idx += 1; } } - } - } - Some(()) -} + WalkEvent::Enter(SyntaxElement::Token(token)) => { + if !in_attr { + return (true, Vec::new()); + } -pub(crate) fn process_cfg_attrs( - db: &dyn ExpandDatabase, - node: &SyntaxNode, - loc: &MacroCallLoc, -) -> Option> { - // FIXME: #[cfg_eval] is not implemented. But it is not stable yet - let is_derive = match loc.def.kind { - MacroDefKind::BuiltInDerive(..) - | MacroDefKind::ProcMacro(_, _, ProcMacroKind::CustomDerive) => true, - MacroDefKind::BuiltInAttr(_, expander) => expander.is_derive(), - _ => false, - }; - let mut remove = FxHashSet::default(); + let Some(ast_attr) = attrs.get_mut(attrs_idx) else { + return (true, Vec::new()); + }; + let token_range = token.text_range(); + let Some(expanded_attr) = ast_attr.expanded_attrs.get(ast_attr.expanded_attrs_idx) + else { + // No expanded attributes in this `ast::Attr`, or we finished them all already, either way + // the remaining tokens should be discarded. + return (false, Vec::new()); + }; + match ast_attr.next_expanded_attr { + NextExpandedAttrState::NotStarted => { + if token_range.start() >= expanded_attr.range.start() { + // We started the next attribute. + let mut insert_tokens = Vec::with_capacity(3); + insert_tokens.push(tt::Leaf::Punct(tt::Punct { + char: '#', + spacing: tt::Spacing::Alone, + span: ast_attr.pound_span, + })); + if let Some(span) = ast_attr.excl_span { + insert_tokens.push(tt::Leaf::Punct(tt::Punct { + char: '!', + spacing: tt::Spacing::Alone, + span, + })); + } + insert_tokens.push(tt::Leaf::Punct(tt::Punct { + char: '[', + spacing: tt::Spacing::Alone, + span: ast_attr.brackets_span.open, + })); - let item = ast::Item::cast(node.clone())?; - for attr in item.attrs() { - if let Some(enabled) = check_cfg_attr(db, &attr, loc.krate) { - if enabled { - debug!("Removing cfg_attr tokens {:?}", attr); - let meta = attr.meta()?; - let removes_from_cfg_attr = remove_tokens_within_cfg_attr(meta)?; - remove.extend(removes_from_cfg_attr); - } else { - debug!("Removing type cfg_attr {:?}", item.syntax()); - remove.insert(attr.syntax().clone().into()); - } - } - } + ast_attr.next_expanded_attr = NextExpandedAttrState::InTheMiddle; - if is_derive { - // Only derives get their code cfg-clean, normal attribute macros process only the cfg at their level - // (cfg_attr is handled above, cfg is handled in the def map). - match item { - ast::Item::Struct(it) => match it.field_list()? { - ast::FieldList::RecordFieldList(fields) => { - process_has_attrs_with_possible_comma( - db, - fields.fields(), - loc.krate, - &mut remove, - )?; - } - ast::FieldList::TupleFieldList(fields) => { - process_has_attrs_with_possible_comma( - db, - fields.fields(), - loc.krate, - &mut remove, - )?; - } - }, - ast::Item::Enum(it) => { - process_enum(db, it.variant_list()?, loc.krate, &mut remove)?; - } - ast::Item::Union(it) => { - process_has_attrs_with_possible_comma( - db, - it.record_field_list()?.fields(), - loc.krate, - &mut remove, - )?; - } - // FIXME: Implement for other items if necessary. As we do not support #[cfg_eval] yet, we do not need to implement it for now - _ => {} - } - } - Some(remove) -} -/// Parses a `cfg` attribute from the meta -fn parse_from_attr_token_tree(tt: &TokenTree) -> Option { - let mut iter = tt - .token_trees_and_tokens() - .filter(is_not_whitespace) - .skip(1) - .take_while(is_not_closing_paren) - .peekable(); - next_cfg_expr_from_syntax(&mut iter) -} + return (true, insert_tokens); + } else { + // Before any attribute or between the attributes. + return (false, Vec::new()); + } + } + NextExpandedAttrState::InTheMiddle => { + if token_range.start() >= expanded_attr.range.end() { + // Finished the current attribute. + let insert_tokens = vec![tt::Leaf::Punct(tt::Punct { + char: ']', + spacing: tt::Spacing::Alone, + span: ast_attr.brackets_span.close, + })]; -fn is_not_closing_paren(element: &NodeOrToken) -> bool { - !matches!(element, NodeOrToken::Token(token) if (token.kind() == syntax::T![')'])) -} -fn is_not_whitespace(element: &NodeOrToken) -> bool { - !matches!(element, NodeOrToken::Token(token) if (token.kind() == SyntaxKind::WHITESPACE)) -} + ast_attr.next_expanded_attr = NextExpandedAttrState::NotStarted; + ast_attr.expanded_attrs_idx += 1; -fn next_cfg_expr_from_syntax(iter: &mut Peekable) -> Option -where - I: Iterator>, -{ - let name = match iter.next() { - None => return None, - Some(NodeOrToken::Token(element)) => match element.kind() { - syntax::T![ident] => Symbol::intern(element.text()), - _ => return Some(CfgExpr::Invalid), - }, - Some(_) => return Some(CfgExpr::Invalid), - }; - let result = match &name { - s if [&sym::all, &sym::any, &sym::not].contains(&s) => { - let mut preds = Vec::new(); - let Some(NodeOrToken::Node(tree)) = iter.next() else { - return Some(CfgExpr::Invalid); - }; - let mut tree_iter = tree - .token_trees_and_tokens() - .filter(is_not_whitespace) - .skip(1) - .take_while(is_not_closing_paren) - .peekable(); - while tree_iter.peek().is_some() { - let pred = next_cfg_expr_from_syntax(&mut tree_iter); - if let Some(pred) = pred { - preds.push(pred); - } - } - let group = match &name { - s if *s == sym::all => CfgExpr::All(preds.into_boxed_slice()), - s if *s == sym::any => CfgExpr::Any(preds.into_boxed_slice()), - s if *s == sym::not => { - CfgExpr::Not(Box::new(preds.pop().unwrap_or(CfgExpr::Invalid))) - } - _ => unreachable!(), - }; - Some(group) - } - _ => match iter.peek() { - Some(NodeOrToken::Token(element)) if (element.kind() == syntax::T![=]) => { - iter.next(); - match iter.next() { - Some(NodeOrToken::Token(value_token)) - if (value_token.kind() == syntax::SyntaxKind::STRING) => - { - let value = value_token.text(); - Some(CfgExpr::Atom(CfgAtom::KeyValue { - key: name, - value: Symbol::intern(value.trim_matches('"')), - })) + // It's safe to ignore the current token because between attributes + // there is always at least one token we skip - either the closing bracket + // in `#[]` or the comma in case of multiple attrs in `cfg_attr` expansion. + return (false, insert_tokens); + } else { + // Still in the middle. + return (true, Vec::new()); + } } - _ => None, } } - _ => Some(CfgExpr::Atom(CfgAtom::Flag(name))), - }, - }; - if let Some(NodeOrToken::Token(element)) = iter.peek() - && element.kind() == syntax::T![,] - { - iter.next(); + WalkEvent::Leave(SyntaxElement::Token(_)) => {} + } + (true, Vec::new()) } - result } -#[cfg(test)] -mod tests { - use cfg::DnfExpr; - use expect_test::{Expect, expect}; - use syntax::{AstNode, SourceFile, ast::Attr}; - - use crate::cfg_process::parse_from_attr_token_tree; - fn check_dnf_from_syntax(input: &str, expect: Expect) { - let parse = SourceFile::parse(input, span::Edition::CURRENT); - let node = match parse.tree().syntax().descendants().find_map(Attr::cast) { - Some(it) => it, - None => { - let node = std::any::type_name::(); - panic!("Failed to make ast node `{node}` from text {input}") - } - }; - let node = node.clone_subtree(); - assert_eq!(node.syntax().text_range().start(), 0.into()); +pub(crate) fn attr_macro_input_to_token_tree( + db: &dyn ExpandDatabase, + node: &SyntaxNode, + span_map: SpanMapRef<'_>, + span: Span, + is_derive: bool, + censor_item_tree_attr_ids: &[AttrId], + krate: Crate, +) -> (tt::TopSubtree, SyntaxFixupUndoInfo) { + let fixups = fixup::fixup_syntax(span_map, node, span, DocCommentDesugarMode::ProcMacro); + ( + syntax_bridge::syntax_node_to_token_tree_modified( + node, + span_map, + fixups.append, + fixups.remove, + span, + DocCommentDesugarMode::ProcMacro, + macro_input_callback(db, is_derive, censor_item_tree_attr_ids, krate, span, span_map), + ), + fixups.undo_info, + ) +} - let cfg = parse_from_attr_token_tree(&node.meta().unwrap().token_tree().unwrap()).unwrap(); - let actual = format!("#![cfg({})]", DnfExpr::new(&cfg)); - expect.assert_eq(&actual); - } - #[test] - fn cfg_from_attr() { - check_dnf_from_syntax(r#"#[cfg(test)]"#, expect![[r#"#![cfg(test)]"#]]); - check_dnf_from_syntax(r#"#[cfg(not(never))]"#, expect![[r#"#![cfg(not(never))]"#]]); - } +pub fn check_cfg_attr_value( + db: &dyn ExpandDatabase, + attr: &ast::TokenTree, + krate: Crate, +) -> Option { + let cfg_expr = CfgExpr::parse_from_ast(&mut TokenTreeChildren::new(attr).peekable()); + krate.cfg_options(db).check(&cfg_expr) } diff --git a/crates/hir-expand/src/db.rs b/crates/hir-expand/src/db.rs index 888c1405a6bb..6b5aa39fa6bf 100644 --- a/crates/hir-expand/src/db.rs +++ b/crates/hir-expand/src/db.rs @@ -1,11 +1,9 @@ //! Defines database & queries for macro expansion. use base_db::{Crate, RootQueryDb}; -use either::Either; use mbe::MatchedArmIndex; -use rustc_hash::FxHashSet; use span::{AstIdMap, Edition, Span, SyntaxContext}; -use syntax::{AstNode, Parse, SyntaxElement, SyntaxError, SyntaxNode, SyntaxToken, T, ast}; +use syntax::{AstNode, Parse, SyntaxError, SyntaxNode, SyntaxToken, T, ast}; use syntax_bridge::{DocCommentDesugarMode, syntax_node_to_token_tree}; use triomphe::Arc; @@ -13,9 +11,9 @@ use crate::{ AstId, BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerCallInfo, EagerExpander, EditionedFileId, ExpandError, ExpandResult, ExpandTo, HirFileId, MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId, MacroDefKind, - attrs::{AttrId, AttrInput, RawAttrs, collect_attrs}, + attrs::Meta, builtin::pseudo_derive_attr_expansion, - cfg_process, + cfg_process::attr_macro_input_to_token_tree, declarative::DeclarativeMacroExpander, fixup::{self, SyntaxFixupUndoInfo}, hygiene::{span_with_call_site_ctxt, span_with_def_site_ctxt, span_with_mixed_site_ctxt}, @@ -177,7 +175,7 @@ pub fn expand_speculative( let span_map = SpanMapRef::RealSpanMap(&span_map); // Build the subtree and token mapping for the speculative args - let (mut tt, undo_info) = match loc.kind { + let (mut tt, undo_info) = match &loc.kind { MacroCallKind::FnLike { .. } => ( syntax_bridge::syntax_node_to_token_tree( speculative_args, @@ -200,48 +198,35 @@ pub fn expand_speculative( ), SyntaxFixupUndoInfo::NONE, ), - MacroCallKind::Derive { derive_attr_index: index, .. } - | MacroCallKind::Attr { invoc_attr_index: index, .. } => { - let censor = if let MacroCallKind::Derive { .. } = loc.kind { - censor_derive_input(index, &ast::Adt::cast(speculative_args.clone())?) - } else { - attr_source(index, &ast::Item::cast(speculative_args.clone())?) - .into_iter() - .map(|it| it.syntax().clone().into()) - .collect() + MacroCallKind::Derive { derive_macro_id, .. } => { + let MacroCallKind::Attr { censored_attr_ids: attr_ids, .. } = + &derive_macro_id.loc(db).kind + else { + unreachable!("`derive_macro_id` should be `MacroCallKind::Attr`"); }; - - let censor_cfg = - cfg_process::process_cfg_attrs(db, speculative_args, &loc).unwrap_or_default(); - let mut fixups = fixup::fixup_syntax( - span_map, + attr_macro_input_to_token_tree( + db, speculative_args, + span_map, span, - DocCommentDesugarMode::ProcMacro, - ); - fixups.append.retain(|it, _| match it { - syntax::NodeOrToken::Token(_) => true, - it => !censor.contains(it) && !censor_cfg.contains(it), - }); - fixups.remove.extend(censor); - fixups.remove.extend(censor_cfg); - - ( - syntax_bridge::syntax_node_to_token_tree_modified( - speculative_args, - span_map, - fixups.append, - fixups.remove, - span, - DocCommentDesugarMode::ProcMacro, - ), - fixups.undo_info, + true, + attr_ids, + loc.krate, ) } + MacroCallKind::Attr { censored_attr_ids: attr_ids, .. } => attr_macro_input_to_token_tree( + db, + speculative_args, + span_map, + span, + false, + attr_ids, + loc.krate, + ), }; - let attr_arg = match loc.kind { - MacroCallKind::Attr { invoc_attr_index, .. } => { + let attr_arg = match &loc.kind { + MacroCallKind::Attr { censored_attr_ids: attr_ids, .. } => { if loc.def.is_attribute_derive() { // for pseudo-derive expansion we actually pass the attribute itself only ast::Attr::cast(speculative_args.clone()).and_then(|attr| attr.token_tree()).map( @@ -260,18 +245,21 @@ pub fn expand_speculative( // Attributes may have an input token tree, build the subtree and map for this as well // then try finding a token id for our token if it is inside this input subtree. let item = ast::Item::cast(speculative_args.clone())?; - let attrs = RawAttrs::new_expanded(db, &item, span_map, loc.krate.cfg_options(db)); - attrs.iter().find(|attr| attr.id == invoc_attr_index).and_then(|attr| { - match attr.input.as_deref()? { - AttrInput::TokenTree(tt) => { - let mut attr_arg = tt.clone(); - attr_arg.top_subtree_delimiter_mut().kind = - tt::DelimiterKind::Invisible; - Some(attr_arg) - } - AttrInput::Literal(_) => None, + let (_, _, _, meta) = + attr_ids.invoc_attr().find_attr_range_with_source(db, loc.krate, &item); + match meta { + Meta::TokenTree { tt, .. } => { + let mut attr_arg = syntax_bridge::syntax_node_to_token_tree( + tt.syntax(), + span_map, + span, + DocCommentDesugarMode::ProcMacro, + ); + attr_arg.top_subtree_delimiter_mut().kind = tt::DelimiterKind::Invisible; + Some(attr_arg) } - }) + _ => None, + } } } _ => None, @@ -433,7 +421,7 @@ fn macro_arg(db: &dyn ExpandDatabase, id: MacroCallId) -> MacroArgResult { let (parse, map) = parse_with_map(db, loc.kind.file_id()); let root = parse.syntax_node(); - let (censor, item_node, span) = match loc.kind { + let (is_derive, censor_item_tree_attr_ids, item_node, span) = match &loc.kind { MacroCallKind::FnLike { ast_id, .. } => { let node = &ast_id.to_ptr(db).to_node(&root); let path_range = node @@ -501,53 +489,29 @@ fn macro_arg(db: &dyn ExpandDatabase, id: MacroCallId) -> MacroArgResult { MacroCallKind::Derive { .. } => { unreachable!("`ExpandDatabase::macro_arg` called with `MacroCallKind::Derive`") } - MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => { + MacroCallKind::Attr { ast_id, censored_attr_ids: attr_ids, .. } => { let node = ast_id.to_ptr(db).to_node(&root); - let attr_source = attr_source(invoc_attr_index, &node); - - let span = map.span_for_range( - attr_source - .as_ref() - .and_then(|it| it.path()) - .map_or_else(|| node.syntax().text_range(), |it| it.syntax().text_range()), - ); - // If derive attribute we need to censor the derive input - if matches!(loc.def.kind, MacroDefKind::BuiltInAttr(_, expander) if expander.is_derive()) - && ast::Adt::can_cast(node.syntax().kind()) - { - let adt = ast::Adt::cast(node.syntax().clone()).unwrap(); - let censor_derive_input = censor_derive_input(invoc_attr_index, &adt); - (censor_derive_input, node, span) - } else { - (attr_source.into_iter().map(|it| it.syntax().clone().into()).collect(), node, span) - } + let range = attr_ids + .invoc_attr() + .find_attr_range_with_source(db, loc.krate, &node) + .3 + .path_range(); + let span = map.span_for_range(range); + + let is_derive = matches!(loc.def.kind, MacroDefKind::BuiltInAttr(_, expander) if expander.is_derive()); + (is_derive, &**attr_ids, node, span) } }; - let (mut tt, undo_info) = { - let syntax = item_node.syntax(); - let censor_cfg = cfg_process::process_cfg_attrs(db, syntax, &loc).unwrap_or_default(); - let mut fixups = - fixup::fixup_syntax(map.as_ref(), syntax, span, DocCommentDesugarMode::ProcMacro); - fixups.append.retain(|it, _| match it { - syntax::NodeOrToken::Token(_) => true, - it => !censor.contains(it) && !censor_cfg.contains(it), - }); - fixups.remove.extend(censor); - fixups.remove.extend(censor_cfg); - - ( - syntax_bridge::syntax_node_to_token_tree_modified( - syntax, - map, - fixups.append, - fixups.remove, - span, - DocCommentDesugarMode::ProcMacro, - ), - fixups.undo_info, - ) - }; + let (mut tt, undo_info) = attr_macro_input_to_token_tree( + db, + item_node.syntax(), + map.as_ref(), + span, + is_derive, + censor_item_tree_attr_ids, + loc.krate, + ); if loc.def.is_proc_macro() { // proc macros expect their inputs without parentheses, MBEs expect it with them included @@ -557,31 +521,6 @@ fn macro_arg(db: &dyn ExpandDatabase, id: MacroCallId) -> MacroArgResult { (Arc::new(tt), undo_info, span) } -// FIXME: Censoring info should be calculated by the caller! Namely by name resolution -/// Derives expect all `#[derive(..)]` invocations up to (and including) the currently invoked one to be stripped -fn censor_derive_input(derive_attr_index: AttrId, node: &ast::Adt) -> FxHashSet { - // FIXME: handle `cfg_attr` - cov_mark::hit!(derive_censoring); - collect_attrs(node) - .take(derive_attr_index.ast_index() + 1) - .filter_map(|(_, attr)| Either::left(attr)) - // FIXME, this resolution should not be done syntactically - // derive is a proper macro now, no longer builtin - // But we do not have resolution at this stage, this means - // we need to know about all macro calls for the given ast item here - // so we require some kind of mapping... - .filter(|attr| attr.simple_name().as_deref() == Some("derive")) - .map(|it| it.syntax().clone().into()) - .collect() -} - -/// Attributes expect the invoking attribute to be stripped -fn attr_source(invoc_attr_index: AttrId, node: &ast::Item) -> Option { - // FIXME: handle `cfg_attr` - cov_mark::hit!(attribute_macro_attr_censoring); - collect_attrs(node).nth(invoc_attr_index.ast_index()).and_then(|(_, attr)| Either::left(attr)) -} - impl TokenExpander { fn macro_expander(db: &dyn ExpandDatabase, id: MacroDefId) -> TokenExpander { match id.kind { diff --git a/crates/hir-expand/src/declarative.rs b/crates/hir-expand/src/declarative.rs index 0d100c1364ab..3fb9aca9649e 100644 --- a/crates/hir-expand/src/declarative.rs +++ b/crates/hir-expand/src/declarative.rs @@ -1,16 +1,20 @@ //! Compiled declarative macro expanders (`macro_rules!` and `macro`) +use std::{cell::OnceCell, ops::ControlFlow}; + use base_db::Crate; -use intern::sym; use span::{Edition, Span, SyntaxContext}; use stdx::TupleExt; -use syntax::{AstNode, ast}; +use syntax::{ + AstNode, AstToken, + ast::{self, HasAttrs}, +}; use syntax_bridge::DocCommentDesugarMode; use triomphe::Arc; use crate::{ AstId, ExpandError, ExpandErrorKind, ExpandResult, HirFileId, Lookup, MacroCallId, - attrs::RawAttrs, + attrs::{Meta, expand_cfg_attr}, db::ExpandDatabase, hygiene::{Transparency, apply_mark}, tt, @@ -80,29 +84,28 @@ impl DeclarativeMacroExpander { let (root, map) = crate::db::parse_with_map(db, id.file_id); let root = root.syntax_node(); - let transparency = |node| { - // ... would be nice to have the item tree here - let attrs = RawAttrs::new_expanded(db, node, map.as_ref(), def_crate.cfg_options(db)); - match attrs - .iter() - .find(|it| { - it.path - .as_ident() - .map(|it| *it == sym::rustc_macro_transparency) - .unwrap_or(false) - })? - .token_tree_value()? - .token_trees() - .flat_tokens() - { - [tt::TokenTree::Leaf(tt::Leaf::Ident(i)), ..] => match &i.sym { - s if *s == sym::transparent => Some(Transparency::Transparent), - s if *s == sym::semitransparent => Some(Transparency::SemiTransparent), - s if *s == sym::opaque => Some(Transparency::Opaque), - _ => None, + let transparency = |node: ast::AnyHasAttrs| { + let cfg_options = OnceCell::new(); + expand_cfg_attr( + node.attrs(), + || cfg_options.get_or_init(|| def_crate.cfg_options(db)), + |attr, _, _, _| { + if let Meta::NamedKeyValue { name: Some(name), value, .. } = attr + && name.text() == "rustc_macro_transparency" + && let Some(value) = value.and_then(ast::String::cast) + && let Ok(value) = value.value() + { + match &*value { + "transparent" => ControlFlow::Break(Transparency::Transparent), + "semitransparent" => ControlFlow::Break(Transparency::SemiTransparent), + "opaque" => ControlFlow::Break(Transparency::Opaque), + _ => ControlFlow::Continue(()), + } + } else { + ControlFlow::Continue(()) + } }, - _ => None, - } + ) }; let ctx_edition = |ctx: SyntaxContext| { if ctx.is_root() { @@ -133,7 +136,8 @@ impl DeclarativeMacroExpander { "expected a token tree".into(), )), }, - transparency(¯o_rules).unwrap_or(Transparency::SemiTransparent), + transparency(ast::AnyHasAttrs::from(macro_rules)) + .unwrap_or(Transparency::SemiTransparent), ), ast::Macro::MacroDef(macro_def) => ( match macro_def.body() { @@ -161,7 +165,7 @@ impl DeclarativeMacroExpander { "expected a token tree".into(), )), }, - transparency(¯o_def).unwrap_or(Transparency::Opaque), + transparency(macro_def.into()).unwrap_or(Transparency::Opaque), ), }; let edition = ctx_edition(match id.file_id { diff --git a/crates/hir-expand/src/files.rs b/crates/hir-expand/src/files.rs index a7f3e27a4553..fe557d68023d 100644 --- a/crates/hir-expand/src/files.rs +++ b/crates/hir-expand/src/files.rs @@ -55,30 +55,6 @@ impl From for HirFilePosition { } } -impl FilePositionWrapper { - pub fn with_edition(self, db: &dyn ExpandDatabase, edition: span::Edition) -> FilePosition { - FilePositionWrapper { - file_id: EditionedFileId::new(db, self.file_id, edition), - offset: self.offset, - } - } -} - -impl FileRangeWrapper { - pub fn with_edition(self, db: &dyn ExpandDatabase, edition: span::Edition) -> FileRange { - FileRangeWrapper { - file_id: EditionedFileId::new(db, self.file_id, edition), - range: self.range, - } - } -} - -impl InFileWrapper { - pub fn with_edition(self, db: &dyn ExpandDatabase, edition: span::Edition) -> InRealFile { - InRealFile { file_id: EditionedFileId::new(db, self.file_id, edition), value: self.value } - } -} - impl HirFileRange { pub fn file_range(self) -> Option { Some(FileRange { file_id: self.file_id.file_id()?, range: self.range }) @@ -407,7 +383,7 @@ impl InFile { // Fall back to whole macro call. let loc = db.lookup_intern_macro_call(mac_file); - loc.kind.original_call_range(db) + loc.kind.original_call_range(db, loc.krate) } } } @@ -453,7 +429,10 @@ impl InFile { Some(it) => it, None => { let loc = db.lookup_intern_macro_call(mac_file); - (loc.kind.original_call_range(db), SyntaxContext::root(loc.def.edition)) + ( + loc.kind.original_call_range(db, loc.krate), + SyntaxContext::root(loc.def.edition), + ) } } } @@ -468,7 +447,7 @@ impl InFile { Some(it) => it, _ => { let loc = db.lookup_intern_macro_call(mac_file); - loc.kind.original_call_range(db) + loc.kind.original_call_range(db, loc.krate) } } } diff --git a/crates/hir-expand/src/fixup.rs b/crates/hir-expand/src/fixup.rs index fe77e1565987..cba1c7c1d4b0 100644 --- a/crates/hir-expand/src/fixup.rs +++ b/crates/hir-expand/src/fixup.rs @@ -523,6 +523,7 @@ mod tests { fixups.remove, span_map.span_for_range(TextRange::empty(0.into())), DocCommentDesugarMode::Mbe, + |_, _| (true, Vec::new()), ); let actual = format!("{tt}\n"); @@ -698,7 +699,7 @@ fn foo() { } "#, expect![[r#" -fn foo () {a . __ra_fixup ;} +fn foo () {a .__ra_fixup ;} "#]], ) } @@ -713,7 +714,7 @@ fn foo() { } "#, expect![[r#" -fn foo () {a . __ra_fixup ; bar () ;} +fn foo () {a .__ra_fixup ; bar () ;} "#]], ) } diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs index 472ec83ffef5..e1103ef43e0f 100644 --- a/crates/hir-expand/src/lib.rs +++ b/crates/hir-expand/src/lib.rs @@ -25,18 +25,17 @@ mod cfg_process; mod fixup; mod prettify_macro_expansion_; -use attrs::collect_attrs; -use rustc_hash::FxHashMap; use salsa::plumbing::{AsId, FromId}; use stdx::TupleExt; +use thin_vec::ThinVec; use triomphe::Arc; use core::fmt; -use std::hash::Hash; +use std::{hash::Hash, ops}; use base_db::Crate; use either::Either; -use span::{Edition, ErasedFileAstId, FileAstId, Span, SpanAnchor, SyntaxContext}; +use span::{Edition, ErasedFileAstId, FileAstId, Span, SyntaxContext}; use syntax::{ SyntaxNode, SyntaxToken, TextRange, TextSize, ast::{self, AstNode}, @@ -317,9 +316,6 @@ pub enum MacroCallKind { Derive { ast_id: AstId, /// Syntactical index of the invoking `#[derive]` attribute. - /// - /// Outer attributes are counted first, then inner attributes. This does not support - /// out-of-line modules, which may have attributes spread across 2 files! derive_attr_index: AttrId, /// Index of the derive macro in the derive attribute derive_index: u32, @@ -329,17 +325,68 @@ pub enum MacroCallKind { }, Attr { ast_id: AstId, - // FIXME: This shouldn't be here, we can derive this from `invoc_attr_index` - // but we need to fix the `cfg_attr` handling first. + // FIXME: This shouldn't be here, we can derive this from `invoc_attr_index`. attr_args: Option>, - /// Syntactical index of the invoking `#[attribute]`. + /// This contains the list of all *active* attributes (derives and attr macros) preceding this + /// attribute, including this attribute. You can retrieve the [`AttrId`] of the current attribute + /// by calling [`invoc_attr()`] on this. + /// + /// The macro should not see the attributes here. /// - /// Outer attributes are counted first, then inner attributes. This does not support - /// out-of-line modules, which may have attributes spread across 2 files! - invoc_attr_index: AttrId, + /// [`invoc_attr()`]: AttrMacroAttrIds::invoc_attr + censored_attr_ids: AttrMacroAttrIds, }, } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct AttrMacroAttrIds(AttrMacroAttrIdsRepr); + +impl AttrMacroAttrIds { + #[inline] + pub fn from_one(id: AttrId) -> Self { + Self(AttrMacroAttrIdsRepr::One(id)) + } + + #[inline] + pub fn from_many(ids: &[AttrId]) -> Self { + if let &[id] = ids { + Self(AttrMacroAttrIdsRepr::One(id)) + } else { + Self(AttrMacroAttrIdsRepr::ManyDerives(ids.iter().copied().collect())) + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +enum AttrMacroAttrIdsRepr { + One(AttrId), + ManyDerives(ThinVec), +} + +impl ops::Deref for AttrMacroAttrIds { + type Target = [AttrId]; + + #[inline] + fn deref(&self) -> &Self::Target { + match &self.0 { + AttrMacroAttrIdsRepr::One(one) => std::slice::from_ref(one), + AttrMacroAttrIdsRepr::ManyDerives(many) => many, + } + } +} + +impl AttrMacroAttrIds { + #[inline] + pub fn invoc_attr(&self) -> AttrId { + match &self.0 { + AttrMacroAttrIdsRepr::One(it) => *it, + AttrMacroAttrIdsRepr::ManyDerives(it) => { + *it.last().expect("should always have at least one `AttrId`") + } + } + } +} + impl HirFileId { pub fn edition(self, db: &dyn ExpandDatabase) -> Edition { match self { @@ -583,34 +630,20 @@ impl MacroDefId { impl MacroCallLoc { pub fn to_node(&self, db: &dyn ExpandDatabase) -> InFile { - match self.kind { + match &self.kind { MacroCallKind::FnLike { ast_id, .. } => { ast_id.with_value(ast_id.to_node(db).syntax().clone()) } MacroCallKind::Derive { ast_id, derive_attr_index, .. } => { // FIXME: handle `cfg_attr` - ast_id.with_value(ast_id.to_node(db)).map(|it| { - collect_attrs(&it) - .nth(derive_attr_index.ast_index()) - .and_then(|it| match it.1 { - Either::Left(attr) => Some(attr.syntax().clone()), - Either::Right(_) => None, - }) - .unwrap_or_else(|| it.syntax().clone()) - }) + let (attr, _, _, _) = derive_attr_index.find_attr_range(db, self.krate, *ast_id); + ast_id.with_value(attr.syntax().clone()) } - MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => { + MacroCallKind::Attr { ast_id, censored_attr_ids: attr_ids, .. } => { if self.def.is_attribute_derive() { - // FIXME: handle `cfg_attr` - ast_id.with_value(ast_id.to_node(db)).map(|it| { - collect_attrs(&it) - .nth(invoc_attr_index.ast_index()) - .and_then(|it| match it.1 { - Either::Left(attr) => Some(attr.syntax().clone()), - Either::Right(_) => None, - }) - .unwrap_or_else(|| it.syntax().clone()) - }) + let (attr, _, _, _) = + attr_ids.invoc_attr().find_attr_range(db, self.krate, *ast_id); + ast_id.with_value(attr.syntax().clone()) } else { ast_id.with_value(ast_id.to_node(db).syntax().clone()) } @@ -715,7 +748,7 @@ impl MacroCallKind { /// Here we try to roughly match what rustc does to improve diagnostics: fn-like macros /// get the macro path (rustc shows the whole `ast::MacroCall`), attribute macros get the /// attribute's range, and derives get only the specific derive that is being referred to. - pub fn original_call_range(self, db: &dyn ExpandDatabase) -> FileRange { + pub fn original_call_range(self, db: &dyn ExpandDatabase, krate: Crate) -> FileRange { let mut kind = self; let file_id = loop { match kind.file_id() { @@ -737,24 +770,11 @@ impl MacroCallKind { } MacroCallKind::Derive { ast_id, derive_attr_index, .. } => { // FIXME: should be the range of the macro name, not the whole derive - // FIXME: handle `cfg_attr` - collect_attrs(&ast_id.to_node(db)) - .nth(derive_attr_index.ast_index()) - .expect("missing derive") - .1 - .expect_left("derive is a doc comment?") - .syntax() - .text_range() + derive_attr_index.find_attr_range(db, krate, ast_id).2 } // FIXME: handle `cfg_attr` - MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => { - collect_attrs(&ast_id.to_node(db)) - .nth(invoc_attr_index.ast_index()) - .expect("missing attribute") - .1 - .expect_left("attribute macro is a doc comment?") - .syntax() - .text_range() + MacroCallKind::Attr { ast_id, censored_attr_ids: attr_ids, .. } => { + attr_ids.invoc_attr().find_attr_range(db, krate, ast_id).2 } }; @@ -873,7 +893,8 @@ impl ExpansionInfo { let span = self.exp_map.span_at(token.start()); match &self.arg_map { SpanMap::RealSpanMap(_) => { - let file_id = EditionedFileId::from_span(db, span.anchor.file_id).into(); + let file_id = + EditionedFileId::from_span_guess_origin(db, span.anchor.file_id).into(); let anchor_offset = db.ast_id_map(file_id).get_erased(span.anchor.ast_id).text_range().start(); InFile { file_id, value: smallvec::smallvec![span.range + anchor_offset] } @@ -929,7 +950,7 @@ pub fn map_node_range_up_rooted( start = start.min(span.range.start()); end = end.max(span.range.end()); } - let file_id = EditionedFileId::from_span(db, anchor.file_id); + let file_id = EditionedFileId::from_span_guess_origin(db, anchor.file_id); let anchor_offset = db.ast_id_map(file_id.into()).get_erased(anchor.ast_id).text_range().start(); Some(FileRange { file_id, range: TextRange::new(start, end) + anchor_offset }) @@ -955,36 +976,12 @@ pub fn map_node_range_up( start = start.min(span.range.start()); end = end.max(span.range.end()); } - let file_id = EditionedFileId::from_span(db, anchor.file_id); + let file_id = EditionedFileId::from_span_guess_origin(db, anchor.file_id); let anchor_offset = db.ast_id_map(file_id.into()).get_erased(anchor.ast_id).text_range().start(); Some((FileRange { file_id, range: TextRange::new(start, end) + anchor_offset }, ctx)) } -/// Maps up the text range out of the expansion hierarchy back into the original file its from. -/// This version will aggregate the ranges of all spans with the same anchor and syntax context. -pub fn map_node_range_up_aggregated( - db: &dyn ExpandDatabase, - exp_map: &ExpansionSpanMap, - range: TextRange, -) -> FxHashMap<(SpanAnchor, SyntaxContext), TextRange> { - let mut map = FxHashMap::default(); - for span in exp_map.spans_for_range(range) { - let range = map.entry((span.anchor, span.ctx)).or_insert_with(|| span.range); - *range = TextRange::new( - range.start().min(span.range.start()), - range.end().max(span.range.end()), - ); - } - for ((anchor, _), range) in &mut map { - let file_id = EditionedFileId::from_span(db, anchor.file_id); - let anchor_offset = - db.ast_id_map(file_id.into()).get_erased(anchor.ast_id).text_range().start(); - *range += anchor_offset; - } - map -} - /// Looks up the span at the given offset. pub fn span_for_offset( db: &dyn ExpandDatabase, @@ -992,7 +989,7 @@ pub fn span_for_offset( offset: TextSize, ) -> (FileRange, SyntaxContext) { let span = exp_map.span_at(offset); - let file_id = EditionedFileId::from_span(db, span.anchor.file_id); + let file_id = EditionedFileId::from_span_guess_origin(db, span.anchor.file_id); let anchor_offset = db.ast_id_map(file_id.into()).get_erased(span.anchor.ast_id).text_range().start(); (FileRange { file_id, range: span.range + anchor_offset }, span.ctx) @@ -1062,7 +1059,7 @@ impl ExpandTo { } } -intern::impl_internable!(ModPath, attrs::AttrInput); +intern::impl_internable!(ModPath); #[salsa_macros::interned(no_lifetime, debug, revisions = usize::MAX)] #[doc(alias = "MacroFileId")] @@ -1125,6 +1122,14 @@ impl HirFileId { HirFileId::MacroFile(_) => None, } } + + #[inline] + pub fn krate(self, db: &dyn ExpandDatabase) -> Crate { + match self { + HirFileId::FileId(it) => it.krate(db), + HirFileId::MacroFile(it) => it.loc(db).krate, + } + } } impl PartialEq for HirFileId { diff --git a/crates/hir-expand/src/mod_path.rs b/crates/hir-expand/src/mod_path.rs index d84d978cdb7e..e9805e3f86b8 100644 --- a/crates/hir-expand/src/mod_path.rs +++ b/crates/hir-expand/src/mod_path.rs @@ -2,7 +2,7 @@ use std::{ fmt::{self, Display as _}, - iter, + iter::{self, Peekable}, }; use crate::{ @@ -12,10 +12,11 @@ use crate::{ tt, }; use base_db::Crate; -use intern::sym; +use intern::{Symbol, sym}; +use parser::T; use smallvec::SmallVec; use span::{Edition, SyntaxContext}; -use syntax::{AstNode, ast}; +use syntax::{AstNode, SyntaxToken, ast}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ModPath { @@ -64,6 +65,58 @@ impl ModPath { ModPath { kind, segments: SmallVec::new_const() } } + pub fn from_tokens( + db: &dyn ExpandDatabase, + span_for_range: &mut dyn FnMut(::tt::TextRange) -> SyntaxContext, + is_abs: bool, + segments: impl Iterator, + ) -> Option { + let mut segments = segments.peekable(); + let mut result = SmallVec::new_const(); + let path_kind = if is_abs { + PathKind::Abs + } else { + let first = segments.next()?; + match first.kind() { + T![crate] => PathKind::Crate, + T![self] => PathKind::Super(handle_super(&mut segments)), + T![super] => PathKind::Super(1 + handle_super(&mut segments)), + T![ident] => { + let first_text = first.text(); + if first_text == "$crate" { + let ctxt = span_for_range(first.text_range()); + resolve_crate_root(db, ctxt) + .map(PathKind::DollarCrate) + .unwrap_or(PathKind::Crate) + } else { + result.push(Name::new_symbol_root(Symbol::intern(first_text))); + PathKind::Plain + } + } + _ => return None, + } + }; + for segment in segments { + if segment.kind() != T![ident] { + return None; + } + result.push(Name::new_symbol_root(Symbol::intern(segment.text()))); + } + if result.is_empty() { + return None; + } + result.shrink_to_fit(); + return Some(ModPath { kind: path_kind, segments: result }); + + fn handle_super(segments: &mut Peekable>) -> u8 { + let mut result = 0; + while segments.next_if(|it| it.kind() == T![super]).is_some() { + result += 1; + } + result + } + } + pub fn segments(&self) -> &[Name] { &self.segments } diff --git a/crates/hir-expand/src/span_map.rs b/crates/hir-expand/src/span_map.rs index e5a778a95c7c..8b0c0d72cd49 100644 --- a/crates/hir-expand/src/span_map.rs +++ b/crates/hir-expand/src/span_map.rs @@ -1,13 +1,12 @@ //! Span maps for real files and macro expansions. use span::{Span, SyntaxContext}; -use stdx::TupleExt; use syntax::{AstNode, TextRange, ast}; use triomphe::Arc; pub use span::RealSpanMap; -use crate::{HirFileId, MacroCallId, attrs::collect_attrs, db::ExpandDatabase}; +use crate::{HirFileId, MacroCallId, db::ExpandDatabase}; pub type ExpansionSpanMap = span::SpanMap; @@ -110,26 +109,24 @@ pub(crate) fn real_span_map( // them anchors too, but only if they have no attributes attached, as those might be proc-macros // and using different anchors inside of them will prevent spans from being joinable. tree.items().for_each(|item| match &item { - ast::Item::ExternBlock(it) - if !collect_attrs(it).map(TupleExt::tail).any(|it| it.is_left()) => - { + ast::Item::ExternBlock(it) if ast::attrs_including_inner(it).next().is_none() => { if let Some(extern_item_list) = it.extern_item_list() { pairs.extend( extern_item_list.extern_items().map(ast::Item::from).map(item_to_entry), ); } } - ast::Item::Impl(it) if !collect_attrs(it).map(TupleExt::tail).any(|it| it.is_left()) => { + ast::Item::Impl(it) if ast::attrs_including_inner(it).next().is_none() => { if let Some(assoc_item_list) = it.assoc_item_list() { pairs.extend(assoc_item_list.assoc_items().map(ast::Item::from).map(item_to_entry)); } } - ast::Item::Module(it) if !collect_attrs(it).map(TupleExt::tail).any(|it| it.is_left()) => { + ast::Item::Module(it) if ast::attrs_including_inner(it).next().is_none() => { if let Some(item_list) = it.item_list() { pairs.extend(item_list.items().map(item_to_entry)); } } - ast::Item::Trait(it) if !collect_attrs(it).map(TupleExt::tail).any(|it| it.is_left()) => { + ast::Item::Trait(it) if ast::attrs_including_inner(it).next().is_none() => { if let Some(assoc_item_list) = it.assoc_item_list() { pairs.extend(assoc_item_list.assoc_items().map(ast::Item::from).map(item_to_entry)); } diff --git a/crates/hir-ty/src/consteval.rs b/crates/hir-ty/src/consteval.rs index f30ec839a009..69ce2d6bcd26 100644 --- a/crates/hir-ty/src/consteval.rs +++ b/crates/hir-ty/src/consteval.rs @@ -4,6 +4,7 @@ use base_db::Crate; use chalk_ir::{BoundVar, DebruijnIndex, cast::Cast}; use hir_def::{ EnumVariantId, GeneralConstId, HasModule as _, StaticId, + attrs::AttrFlags, expr_store::{Body, HygieneId, path::Path}, hir::{Expr, ExprId}, resolver::{Resolver, ValueNs}, @@ -294,7 +295,7 @@ pub(crate) fn const_eval_discriminant_variant( return Ok(value); } - let repr = db.enum_signature(loc.parent).repr; + let repr = AttrFlags::repr(db, loc.parent.into()); let is_signed = repr.and_then(|repr| repr.int).is_none_or(|int| int.is_signed()); let mir_body = db.monomorphized_mir_body( diff --git a/crates/hir-ty/src/consteval_nextsolver.rs b/crates/hir-ty/src/consteval_nextsolver.rs index da4aff54de37..a7094a86e26d 100644 --- a/crates/hir-ty/src/consteval_nextsolver.rs +++ b/crates/hir-ty/src/consteval_nextsolver.rs @@ -5,6 +5,7 @@ use base_db::Crate; use hir_def::{ EnumVariantId, GeneralConstId, + attrs::AttrFlags, expr_store::{Body, HygieneId, path::Path}, hir::{Expr, ExprId}, resolver::{Resolver, ValueNs}, @@ -199,7 +200,7 @@ pub(crate) fn const_eval_discriminant_variant( return Ok(value); } - let repr = db.enum_signature(loc.parent).repr; + let repr = AttrFlags::repr(db, loc.parent.into()); let is_signed = repr.and_then(|repr| repr.int).is_none_or(|int| int.is_signed()); let mir_body = db.monomorphized_mir_body( diff --git a/crates/hir-ty/src/diagnostics/decl_check.rs b/crates/hir-ty/src/diagnostics/decl_check.rs index 0815e62f87ee..c0e223380bca 100644 --- a/crates/hir-ty/src/diagnostics/decl_check.rs +++ b/crates/hir-ty/src/diagnostics/decl_check.rs @@ -17,8 +17,8 @@ use std::fmt; use hir_def::{ AdtId, ConstId, EnumId, EnumVariantId, FunctionId, HasModule, ItemContainerId, Lookup, - ModuleDefId, ModuleId, StaticId, StructId, TraitId, TypeAliasId, db::DefDatabase, hir::Pat, - item_tree::FieldsShape, signatures::StaticFlags, src::HasSource, + ModuleDefId, ModuleId, StaticId, StructId, TraitId, TypeAliasId, attrs::AttrFlags, + db::DefDatabase, hir::Pat, item_tree::FieldsShape, signatures::StaticFlags, src::HasSource, }; use hir_expand::{ HirFileId, @@ -201,7 +201,7 @@ impl<'a> DeclValidator<'a> { // Don't run the lint on extern "[not Rust]" fn items with the // #[no_mangle] attribute. - let no_mangle = self.db.attrs(func.into()).by_key(sym::no_mangle).exists(); + let no_mangle = AttrFlags::query(self.db, func.into()).contains(AttrFlags::NO_MANGLE); if no_mangle && data.abi.as_ref().is_some_and(|abi| *abi != sym::Rust) { cov_mark::hit!(extern_func_no_mangle_ignored); } else { diff --git a/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs b/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs index 56fd12e1f2b4..6ec24575deff 100644 --- a/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs +++ b/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs @@ -3,7 +3,10 @@ use std::cell::LazyCell; use std::fmt; -use hir_def::{DefWithBodyId, EnumId, EnumVariantId, HasModule, LocalFieldId, ModuleId, VariantId}; +use hir_def::{ + DefWithBodyId, EnumId, EnumVariantId, HasModule, LocalFieldId, ModuleId, VariantId, + attrs::AttrFlags, +}; use intern::sym; use rustc_pattern_analysis::{ IndexVec, PatCx, PrivateUninhabitedField, @@ -113,7 +116,7 @@ impl<'db> MatchCheckCtx<'db> { /// Returns whether the given ADT is from another crate declared `#[non_exhaustive]`. fn is_foreign_non_exhaustive(&self, adt: hir_def::AdtId) -> bool { let is_local = adt.krate(self.db) == self.module.krate(); - !is_local && self.db.attrs(adt.into()).by_key(sym::non_exhaustive).exists() + !is_local && AttrFlags::query(self.db, adt.into()).contains(AttrFlags::NON_EXHAUSTIVE) } fn variant_id_for_adt( diff --git a/crates/hir-ty/src/diagnostics/unsafe_check.rs b/crates/hir-ty/src/diagnostics/unsafe_check.rs index 827585e50693..c439be8b2f35 100644 --- a/crates/hir-ty/src/diagnostics/unsafe_check.rs +++ b/crates/hir-ty/src/diagnostics/unsafe_check.rs @@ -141,7 +141,7 @@ struct UnsafeVisitor<'db> { inside_assignment: bool, inside_union_destructure: bool, callback: &'db mut dyn FnMut(UnsafeDiagnostic), - def_target_features: TargetFeatures, + def_target_features: TargetFeatures<'db>, // FIXME: This needs to be the edition of the span of each call. edition: Edition, } @@ -156,7 +156,7 @@ impl<'db> UnsafeVisitor<'db> { ) -> Self { let resolver = def.resolver(db); let def_target_features = match def { - DefWithBodyId::FunctionId(func) => TargetFeatures::from_attrs(&db.attrs(func.into())), + DefWithBodyId::FunctionId(func) => TargetFeatures::from_fn(db, func), _ => TargetFeatures::default(), }; let edition = resolver.module().krate().data(db).edition; diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index d778cc0e30ed..888818722d0d 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -43,7 +43,7 @@ use hir_def::{ lang_item::{LangItem, LangItemTarget, lang_item}, layout::Integer, resolver::{HasResolver, ResolveValueResult, Resolver, TypeNs, ValueNs}, - signatures::{ConstSignature, StaticSignature}, + signatures::{ConstSignature, EnumSignature, StaticSignature}, type_ref::{ConstRef, LifetimeRefId, TypeRefId}, }; use hir_expand::{mod_path::ModPath, name::Name}; @@ -102,7 +102,7 @@ pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc ctx.collect_static(&db.static_signature(s)), DefWithBodyId::VariantId(v) => { ctx.return_ty = TyBuilder::builtin( - match db.enum_signature(v.lookup(db).parent).variant_body_type() { + match EnumSignature::variant_body_type(db, v.lookup(db).parent) { hir_def::layout::IntegerType::Pointer(signed) => match signed { true => BuiltinType::Int(BuiltinInt::Isize), false => BuiltinType::Uint(BuiltinUint::Usize), diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 261c02386822..57017a72bc21 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -2285,9 +2285,11 @@ impl InferenceContext<'_> { }; let data = self.db.function_signature(func); - let Some(legacy_const_generics_indices) = &data.legacy_const_generics_indices else { + let Some(legacy_const_generics_indices) = data.legacy_const_generics_indices(self.db, func) + else { return Default::default(); }; + let mut legacy_const_generics_indices = Box::<[u32]>::from(legacy_const_generics_indices); // only use legacy const generics if the param count matches with them if data.params.len() + legacy_const_generics_indices.len() != args.len() { @@ -2296,9 +2298,8 @@ impl InferenceContext<'_> { } else { // there are more parameters than there should be without legacy // const params; use them - let mut indices = legacy_const_generics_indices.as_ref().clone(); - indices.sort(); - return indices; + legacy_const_generics_indices.sort_unstable(); + return legacy_const_generics_indices; } } @@ -2316,9 +2317,8 @@ impl InferenceContext<'_> { self.infer_expr(args[arg_idx as usize], &expected, ExprIsRead::Yes); // FIXME: evaluate and unify with the const } - let mut indices = legacy_const_generics_indices.as_ref().clone(); - indices.sort(); - indices + legacy_const_generics_indices.sort_unstable(); + legacy_const_generics_indices } /// Dereferences a single level of immutable referencing. diff --git a/crates/hir-ty/src/layout.rs b/crates/hir-ty/src/layout.rs index 2d0471d7e554..94457efcecbe 100644 --- a/crates/hir-ty/src/layout.rs +++ b/crates/hir-ty/src/layout.rs @@ -4,6 +4,7 @@ use std::fmt; use hir_def::{ AdtId, LocalFieldId, StructId, + attrs::AttrFlags, layout::{LayoutCalculatorError, LayoutData}, }; use la_arena::{Idx, RawIdx}; @@ -189,8 +190,7 @@ pub fn layout_of_ty_ns_query<'db>( TyKind::Adt(def, args) => { match def.inner().id { hir_def::AdtId::StructId(s) => { - let data = db.struct_signature(s); - let repr = data.repr.unwrap_or_default(); + let repr = AttrFlags::repr(db, s.into()).unwrap_or_default(); if repr.simd() { return layout_of_simd_ty(db, s, repr.packed(), &args, trait_env, &target); } diff --git a/crates/hir-ty/src/layout/adt.rs b/crates/hir-ty/src/layout/adt.rs index 2fa01b6b41ab..eb1d7362451b 100644 --- a/crates/hir-ty/src/layout/adt.rs +++ b/crates/hir-ty/src/layout/adt.rs @@ -4,9 +4,9 @@ use std::{cmp, ops::Bound}; use hir_def::{ AdtId, VariantId, + attrs::AttrFlags, signatures::{StructFlags, VariantFields}, }; -use intern::sym; use rustc_abi::{Integer, ReprOptions, TargetDataLayout}; use rustc_index::IndexVec; use smallvec::SmallVec; @@ -54,15 +54,15 @@ pub fn layout_of_adt_ns_query<'db>( r.push(handle_variant(s.into(), s.fields(db))?); ( r, - sig.repr.unwrap_or_default(), + AttrFlags::repr(db, s.into()).unwrap_or_default(), sig.flags.intersects(StructFlags::IS_UNSAFE_CELL | StructFlags::IS_UNSAFE_PINNED), ) } AdtId::UnionId(id) => { - let data = db.union_signature(id); + let repr = AttrFlags::repr(db, id.into()); let mut r = SmallVec::new(); r.push(handle_variant(id.into(), id.fields(db))?); - (r, data.repr.unwrap_or_default(), false) + (r, repr.unwrap_or_default(), false) } AdtId::EnumId(e) => { let variants = e.enum_variants(db); @@ -71,7 +71,7 @@ pub fn layout_of_adt_ns_query<'db>( .iter() .map(|&(v, _, _)| handle_variant(v.into(), v.fields(db))) .collect::, _>>()?; - (r, db.enum_signature(e).repr.unwrap_or_default(), false) + (r, AttrFlags::repr(db, e.into()).unwrap_or_default(), false) } }; let variants = variants @@ -124,27 +124,12 @@ pub(crate) fn layout_of_adt_ns_cycle_result<'db>( } fn layout_scalar_valid_range(db: &dyn HirDatabase, def: AdtId) -> (Bound, Bound) { - let attrs = db.attrs(def.into()); - let get = |name| { - let attr = attrs.by_key(name).tt_values(); - for tree in attr { - if let Some(it) = tree.iter().next_as_view() { - let text = it.to_string().replace('_', ""); - let (text, base) = match text.as_bytes() { - [b'0', b'x', ..] => (&text[2..], 16), - [b'0', b'o', ..] => (&text[2..], 8), - [b'0', b'b', ..] => (&text[2..], 2), - _ => (&*text, 10), - }; - - if let Ok(it) = u128::from_str_radix(text, base) { - return Bound::Included(it); - } - } - } - Bound::Unbounded + let range = AttrFlags::rustc_layout_scalar_valid_range(db, def); + let get = |value| match value { + Some(it) => Bound::Included(it), + None => Bound::Unbounded, }; - (get(sym::rustc_layout_scalar_valid_range_start), get(sym::rustc_layout_scalar_valid_range_end)) + (get(range.start), get(range.end)) } /// Finds the appropriate Integer type and signedness for the given diff --git a/crates/hir-ty/src/method_resolution.rs b/crates/hir-ty/src/method_resolution.rs index 49438151bbff..e118b0bccc96 100644 --- a/crates/hir-ty/src/method_resolution.rs +++ b/crates/hir-ty/src/method_resolution.rs @@ -10,11 +10,11 @@ use chalk_ir::{UniverseIndex, WithKind, cast::Cast}; use hir_def::{ AssocItemId, BlockId, ConstId, FunctionId, HasModule, ImplId, ItemContainerId, Lookup, ModuleId, TraitId, + attrs::AttrFlags, nameres::{DefMap, assoc::ImplItems, block_def_map, crate_def_map}, signatures::{ConstFlags, EnumFlags, FnFlags, StructFlags, TraitFlags, TypeAliasFlags}, }; use hir_expand::name::Name; -use intern::sym; use rustc_hash::{FxHashMap, FxHashSet}; use rustc_type_ir::inherent::{IntoKind, SliceLike}; use smallvec::{SmallVec, smallvec}; @@ -288,7 +288,8 @@ impl TraitImpls { // FIXME: Reservation impls should be considered during coherence checks. If we are // (ever) to implement coherence checks, this filtering should be done by the trait // solver. - if db.attrs(impl_id.into()).by_key(sym::rustc_reservation_impl).exists() { + if AttrFlags::query(db, impl_id.into()).contains(AttrFlags::RUSTC_RESERVATION_IMPL) + { continue; } let target_trait = match db.impl_trait(impl_id) { diff --git a/crates/hir-ty/src/mir/eval/shim.rs b/crates/hir-ty/src/mir/eval/shim.rs index bb4c963a8ae1..e3df04c3ac0e 100644 --- a/crates/hir-ty/src/mir/eval/shim.rs +++ b/crates/hir-ty/src/mir/eval/shim.rs @@ -6,11 +6,12 @@ use std::cmp::{self, Ordering}; use chalk_ir::TyKind; use hir_def::{ CrateRootModuleId, + attrs::AttrFlags, builtin_type::{BuiltinInt, BuiltinUint}, resolver::HasResolver, }; use hir_expand::name::Name; -use intern::{Symbol, sym}; +use intern::sym; use stdx::never; use crate::{ @@ -58,8 +59,8 @@ impl Evaluator<'_> { } let function_data = self.db.function_signature(def); - let attrs = self.db.attrs(def.into()); - let is_intrinsic = attrs.by_key(sym::rustc_intrinsic).exists() + let attrs = AttrFlags::query(self.db, def.into()); + let is_intrinsic = attrs.contains(AttrFlags::RUSTC_INTRINSIC) // Keep this around for a bit until extern "rustc-intrinsic" abis are no longer used || (match &function_data.abi { Some(abi) => *abi == sym::rust_dash_intrinsic, @@ -80,7 +81,7 @@ impl Evaluator<'_> { locals, span, !function_data.has_body() - || attrs.by_key(sym::rustc_intrinsic_must_be_overridden).exists(), + || attrs.contains(AttrFlags::RUSTC_INTRINSIC_MUST_BE_OVERRIDDEN), ); } let is_extern_c = match def.lookup(self.db).container { @@ -100,18 +101,13 @@ impl Evaluator<'_> { .map(|()| true); } - let alloc_fn = - attrs.iter().filter_map(|it| it.path().as_ident()).map(|it| it.symbol()).find(|it| { - [ - &sym::rustc_allocator, - &sym::rustc_deallocator, - &sym::rustc_reallocator, - &sym::rustc_allocator_zeroed, - ] - .contains(it) - }); - if let Some(alloc_fn) = alloc_fn { - self.exec_alloc_fn(alloc_fn, args, destination)?; + if attrs.intersects( + AttrFlags::RUSTC_ALLOCATOR + | AttrFlags::RUSTC_DEALLOCATOR + | AttrFlags::RUSTC_REALLOCATOR + | AttrFlags::RUSTC_ALLOCATOR_ZEROED, + ) { + self.exec_alloc_fn(attrs, args, destination)?; return Ok(true); } if let Some(it) = self.detect_lang_function(def) { @@ -249,12 +245,14 @@ impl Evaluator<'_> { fn exec_alloc_fn( &mut self, - alloc_fn: &Symbol, + alloc_fn: AttrFlags, args: &[IntervalAndTy], destination: Interval, ) -> Result<()> { match alloc_fn { - _ if *alloc_fn == sym::rustc_allocator_zeroed || *alloc_fn == sym::rustc_allocator => { + _ if alloc_fn + .intersects(AttrFlags::RUSTC_ALLOCATOR_ZEROED | AttrFlags::RUSTC_ALLOCATOR) => + { let [size, align] = args else { return Err(MirEvalError::InternalError( "rustc_allocator args are not provided".into(), @@ -265,8 +263,8 @@ impl Evaluator<'_> { let result = self.heap_allocate(size, align)?; destination.write_from_bytes(self, &result.to_bytes())?; } - _ if *alloc_fn == sym::rustc_deallocator => { /* no-op for now */ } - _ if *alloc_fn == sym::rustc_reallocator => { + _ if alloc_fn.contains(AttrFlags::RUSTC_DEALLOCATOR) => { /* no-op for now */ } + _ if alloc_fn.contains(AttrFlags::RUSTC_REALLOCATOR) => { let [ptr, old_size, align, new_size] = args else { return Err(MirEvalError::InternalError( "rustc_allocator args are not provided".into(), @@ -292,14 +290,14 @@ impl Evaluator<'_> { fn detect_lang_function(&self, def: FunctionId) -> Option { use LangItem::*; - let attrs = self.db.attrs(def.into()); + let attrs = AttrFlags::query(self.db, def.into()); - if attrs.by_key(sym::rustc_const_panic_str).exists() { + if attrs.contains(AttrFlags::RUSTC_CONST_PANIC_STR) { // `#[rustc_const_panic_str]` is treated like `lang = "begin_panic"` by rustc CTFE. return Some(LangItem::BeginPanic); } - let candidate = attrs.lang_item()?; + let candidate = attrs.lang_item_with_attrs(self.db, def.into())?; // We want to execute these functions with special logic // `PanicFmt` is not detected here as it's redirected later. if [BeginPanic, SliceLen, DropInPlace].contains(&candidate) { diff --git a/crates/hir-ty/src/next_solver/interner.rs b/crates/hir-ty/src/next_solver/interner.rs index 4fe0b54d6846..08cd8c058118 100644 --- a/crates/hir-ty/src/next_solver/interner.rs +++ b/crates/hir-ty/src/next_solver/interner.rs @@ -3,6 +3,7 @@ use base_db::Crate; use chalk_ir::{ProgramClauseImplication, SeparatorTraitRef, Variances}; +use hir_def::attrs::AttrFlags; use hir_def::lang_item::LangItem; use hir_def::signatures::{FieldData, FnFlags, ImplFlags, StructFlags, TraitFlags}; use hir_def::{AdtId, BlockId, TypeAliasId, VariantId}; @@ -482,28 +483,28 @@ impl AdtDef { let variants = vec![(VariantIdx(0), VariantDef::Struct(struct_id))]; - let mut repr = ReprOptions::default(); - repr.align = data.repr.and_then(|r| r.align); - repr.pack = data.repr.and_then(|r| r.pack); - repr.int = data.repr.and_then(|r| r.int); - + let data_repr = data.repr(db, struct_id); let mut repr_flags = ReprFlags::empty(); if flags.is_box { repr_flags.insert(ReprFlags::IS_LINEAR); } - if data.repr.is_some_and(|r| r.c()) { + if data_repr.is_some_and(|r| r.c()) { repr_flags.insert(ReprFlags::IS_C); } - if data.repr.is_some_and(|r| r.simd()) { + if data_repr.is_some_and(|r| r.simd()) { repr_flags.insert(ReprFlags::IS_SIMD); } - repr.flags = repr_flags; + let mut repr = ReprOptions { + align: data_repr.and_then(|r| r.align), + pack: data_repr.and_then(|r| r.pack), + int: data_repr.and_then(|r| r.int), + flags: repr_flags, + ..ReprOptions::default() + }; (flags, variants, repr) } AdtId::UnionId(union_id) => { - let data = db.union_signature(union_id); - let flags = AdtFlags { is_enum: false, is_union: true, @@ -516,22 +517,24 @@ impl AdtDef { let variants = vec![(VariantIdx(0), VariantDef::Union(union_id))]; - let mut repr = ReprOptions::default(); - repr.align = data.repr.and_then(|r| r.align); - repr.pack = data.repr.and_then(|r| r.pack); - repr.int = data.repr.and_then(|r| r.int); - + let data_repr = AttrFlags::repr(db, union_id.into()); let mut repr_flags = ReprFlags::empty(); if flags.is_box { repr_flags.insert(ReprFlags::IS_LINEAR); } - if data.repr.is_some_and(|r| r.c()) { + if data_repr.is_some_and(|r| r.c()) { repr_flags.insert(ReprFlags::IS_C); } - if data.repr.is_some_and(|r| r.simd()) { + if data_repr.is_some_and(|r| r.simd()) { repr_flags.insert(ReprFlags::IS_SIMD); } - repr.flags = repr_flags; + let mut repr = ReprOptions { + align: data_repr.and_then(|r| r.align), + pack: data_repr.and_then(|r| r.pack), + int: data_repr.and_then(|r| r.int), + flags: repr_flags, + ..ReprOptions::default() + }; (flags, variants, repr) } @@ -555,24 +558,26 @@ impl AdtDef { .map(|(idx, v)| (idx, VariantDef::Enum(v.0))) .collect(); - let data = db.enum_signature(enum_id); - - let mut repr = ReprOptions::default(); - repr.align = data.repr.and_then(|r| r.align); - repr.pack = data.repr.and_then(|r| r.pack); - repr.int = data.repr.and_then(|r| r.int); + let data_repr = AttrFlags::repr(db, enum_id.into()); let mut repr_flags = ReprFlags::empty(); if flags.is_box { repr_flags.insert(ReprFlags::IS_LINEAR); } - if data.repr.is_some_and(|r| r.c()) { + if data_repr.is_some_and(|r| r.c()) { repr_flags.insert(ReprFlags::IS_C); } - if data.repr.is_some_and(|r| r.simd()) { + if data_repr.is_some_and(|r| r.simd()) { repr_flags.insert(ReprFlags::IS_SIMD); } - repr.flags = repr_flags; + + let mut repr = ReprOptions { + align: data_repr.and_then(|r| r.align), + pack: data_repr.and_then(|r| r.pack), + int: data_repr.and_then(|r| r.int), + flags: repr_flags, + ..ReprOptions::default() + }; (flags, variants, repr) } diff --git a/crates/hir-ty/src/target_feature.rs b/crates/hir-ty/src/target_feature.rs index 9d1238701bcf..003b9bb99a3d 100644 --- a/crates/hir-ty/src/target_feature.rs +++ b/crates/hir-ty/src/target_feature.rs @@ -1,31 +1,35 @@ //! Stuff for handling `#[target_feature]` (needed for unsafe check). +use std::borrow::Cow; use std::sync::LazyLock; -use hir_def::attr::Attrs; -use hir_def::tt; -use intern::{Symbol, sym}; +use hir_def::FunctionId; +use hir_def::attrs::AttrFlags; +use intern::Symbol; use rustc_hash::{FxHashMap, FxHashSet}; +use crate::db::HirDatabase; + #[derive(Debug, Default)] -pub struct TargetFeatures { - pub(crate) enabled: FxHashSet, +pub struct TargetFeatures<'db> { + pub(crate) enabled: Cow<'db, FxHashSet>, } -impl TargetFeatures { - pub fn from_attrs(attrs: &Attrs) -> Self { - let mut result = TargetFeatures::from_attrs_no_implications(attrs); +impl<'db> TargetFeatures<'db> { + pub fn from_fn(db: &'db dyn HirDatabase, owner: FunctionId) -> Self { + let mut result = TargetFeatures::from_fn_no_implications(db, owner); result.expand_implications(); result } fn expand_implications(&mut self) { let all_implications = LazyLock::force(&TARGET_FEATURE_IMPLICATIONS); - let mut queue = self.enabled.iter().cloned().collect::>(); + let enabled = self.enabled.to_mut(); + let mut queue = enabled.iter().cloned().collect::>(); while let Some(feature) = queue.pop() { if let Some(implications) = all_implications.get(&feature) { for implication in implications { - if self.enabled.insert(implication.clone()) { + if enabled.insert(implication.clone()) { queue.push(implication.clone()); } } @@ -34,25 +38,9 @@ impl TargetFeatures { } /// Retrieves the target features from the attributes, and does not expand the target features implied by them. - pub(crate) fn from_attrs_no_implications(attrs: &Attrs) -> Self { - let enabled = attrs - .by_key(sym::target_feature) - .tt_values() - .filter_map(|tt| match tt.token_trees().flat_tokens() { - [ - tt::TokenTree::Leaf(tt::Leaf::Ident(enable_ident)), - tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: '=', .. })), - tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { - kind: tt::LitKind::Str, - symbol: features, - .. - })), - ] if enable_ident.sym == sym::enable => Some(features), - _ => None, - }) - .flat_map(|features| features.as_str().split(',').map(Symbol::intern)) - .collect(); - Self { enabled } + pub(crate) fn from_fn_no_implications(db: &'db dyn HirDatabase, owner: FunctionId) -> Self { + let enabled = AttrFlags::target_features(db, owner); + Self { enabled: Cow::Borrowed(enabled) } } } diff --git a/crates/hir-ty/src/tests/incremental.rs b/crates/hir-ty/src/tests/incremental.rs index df9061d23bf5..d24d20405547 100644 --- a/crates/hir-ty/src/tests/incremental.rs +++ b/crates/hir-ty/src/tests/incremental.rs @@ -31,7 +31,6 @@ fn foo() -> i32 { &[("infer_shim", 1)], expect_test::expect![[r#" [ - "source_root_crates_shim", "crate_local_def_map", "file_item_tree_query", "ast_id_map_shim", @@ -40,7 +39,7 @@ fn foo() -> i32 { "infer_shim", "function_signature_shim", "function_signature_with_source_map_shim", - "attrs_shim", + "AttrFlags::query_", "body_shim", "body_with_source_map_shim", "trait_environment_shim", @@ -80,7 +79,7 @@ fn foo() -> i32 { "ast_id_map_shim", "file_item_tree_query", "real_span_map_shim", - "attrs_shim", + "AttrFlags::query_", "function_signature_with_source_map_shim", "function_signature_shim", "body_with_source_map_shim", @@ -119,7 +118,6 @@ fn baz() -> i32 { &[("infer_shim", 3)], expect_test::expect![[r#" [ - "source_root_crates_shim", "crate_local_def_map", "file_item_tree_query", "ast_id_map_shim", @@ -128,7 +126,7 @@ fn baz() -> i32 { "infer_shim", "function_signature_shim", "function_signature_with_source_map_shim", - "attrs_shim", + "AttrFlags::query_", "body_shim", "body_with_source_map_shim", "trait_environment_shim", @@ -136,8 +134,8 @@ fn baz() -> i32 { "expr_scopes_shim", "lang_item", "crate_lang_items", - "attrs_shim", - "attrs_shim", + "AttrFlags::query_", + "AttrFlags::query_", "lang_item", "infer_shim", "function_signature_shim", @@ -191,13 +189,13 @@ fn baz() -> i32 { "ast_id_map_shim", "file_item_tree_query", "real_span_map_shim", - "attrs_shim", + "AttrFlags::query_", "function_signature_with_source_map_shim", "function_signature_shim", "body_with_source_map_shim", "body_shim", - "attrs_shim", - "attrs_shim", + "AttrFlags::query_", + "AttrFlags::query_", "function_signature_with_source_map_shim", "function_signature_shim", "body_with_source_map_shim", @@ -237,7 +235,6 @@ $0", &[("trait_impls_in_crate_shim", 1)], expect_test::expect![[r#" [ - "source_root_crates_shim", "crate_local_def_map", "file_item_tree_query", "ast_id_map_shim", @@ -309,7 +306,6 @@ $0", &[("trait_impls_in_crate_shim", 1)], expect_test::expect![[r#" [ - "source_root_crates_shim", "crate_local_def_map", "file_item_tree_query", "ast_id_map_shim", @@ -382,7 +378,6 @@ $0", &[("trait_impls_in_crate_shim", 1)], expect_test::expect![[r#" [ - "source_root_crates_shim", "crate_local_def_map", "file_item_tree_query", "ast_id_map_shim", @@ -456,7 +451,6 @@ $0", &[("trait_impls_in_crate_shim", 1)], expect_test::expect![[r#" [ - "source_root_crates_shim", "crate_local_def_map", "file_item_tree_query", "ast_id_map_shim", @@ -505,14 +499,14 @@ impl SomeStruct { "real_span_map_shim", "crate_local_def_map", "trait_impls_in_crate_shim", - "attrs_shim", + "AttrFlags::query_", "impl_trait_with_diagnostics_shim", "impl_signature_shim", "impl_signature_with_source_map_shim", "impl_self_ty_with_diagnostics_shim", "struct_signature_shim", "struct_signature_with_source_map_shim", - "attrs_shim", + "AttrFlags::query_", "type_for_adt_tracked", ] "#]], @@ -563,7 +557,6 @@ fn main() { &[("trait_solve_shim", 0)], expect_test::expect![[r#" [ - "source_root_crates_shim", "crate_local_def_map", "file_item_tree_query", "ast_id_map_shim", @@ -572,22 +565,22 @@ fn main() { "TraitItems::query_with_diagnostics_", "body_shim", "body_with_source_map_shim", - "attrs_shim", + "AttrFlags::query_", "ImplItems::of_", "infer_shim", "trait_signature_shim", "trait_signature_with_source_map_shim", - "attrs_shim", + "AttrFlags::query_", "function_signature_shim", "function_signature_with_source_map_shim", - "attrs_shim", + "AttrFlags::query_", "body_shim", "body_with_source_map_shim", "trait_environment_shim", "lang_item", "crate_lang_items", - "attrs_shim", - "attrs_shim", + "AttrFlags::query_", + "AttrFlags::query_", "return_type_impl_traits_shim", "infer_shim", "function_signature_shim", @@ -674,22 +667,22 @@ fn main() { "crate_local_def_map", "TraitItems::query_with_diagnostics_", "body_with_source_map_shim", - "attrs_shim", + "AttrFlags::query_", "body_shim", "ImplItems::of_", "infer_shim", - "attrs_shim", + "AttrFlags::query_", "trait_signature_with_source_map_shim", - "attrs_shim", + "AttrFlags::query_", "function_signature_with_source_map_shim", "function_signature_shim", "body_with_source_map_shim", "body_shim", "trait_environment_shim", "crate_lang_items", - "attrs_shim", - "attrs_shim", - "attrs_shim", + "AttrFlags::query_", + "AttrFlags::query_", + "AttrFlags::query_", "return_type_impl_traits_shim", "infer_shim", "function_signature_with_source_map_shim", diff --git a/crates/hir-ty/src/utils.rs b/crates/hir-ty/src/utils.rs index 209ec7926e82..1ff2015cd2b2 100644 --- a/crates/hir-ty/src/utils.rs +++ b/crates/hir-ty/src/utils.rs @@ -10,6 +10,7 @@ use chalk_ir::{ }; use hir_def::{ EnumId, EnumVariantId, FunctionId, Lookup, TraitId, TypeAliasId, TypeOrConstParamId, + attrs::AttrFlags, db::DefDatabase, hir::generics::WherePredicate, lang_item::LangItem, @@ -266,7 +267,7 @@ pub enum Unsafety { pub fn is_fn_unsafe_to_call( db: &dyn HirDatabase, func: FunctionId, - caller_target_features: &TargetFeatures, + caller_target_features: &TargetFeatures<'_>, call_edition: Edition, ) -> Unsafety { let data = db.function_signature(func); @@ -276,8 +277,7 @@ pub fn is_fn_unsafe_to_call( if data.has_target_feature() { // RFC 2396 . - let callee_target_features = - TargetFeatures::from_attrs_no_implications(&db.attrs(func.into())); + let callee_target_features = TargetFeatures::from_fn_no_implications(db, func); if !caller_target_features.enabled.is_superset(&callee_target_features.enabled) { return Unsafety::Unsafe; } @@ -298,7 +298,7 @@ pub fn is_fn_unsafe_to_call( if is_intrinsic_block { // legacy intrinsics // extern "rust-intrinsic" intrinsics are unsafe unless they have the rustc_safe_intrinsic attribute - if db.attrs(func.into()).by_key(sym::rustc_safe_intrinsic).exists() { + if AttrFlags::query(db, func.into()).contains(AttrFlags::RUSTC_SAFE_INTRINSIC) { Unsafety::Safe } else { Unsafety::Unsafe diff --git a/crates/hir/src/attrs.rs b/crates/hir/src/attrs.rs index 928753ef0ede..f4b5f015a91c 100644 --- a/crates/hir/src/attrs.rs +++ b/crates/hir/src/attrs.rs @@ -2,9 +2,12 @@ use std::ops::ControlFlow; +use cfg::CfgExpr; +use either::Either; use hir_def::{ - AssocItemId, AttrDefId, ModuleDefId, - attr::AttrsWithOwner, + AssocItemId, AttrDefId, FieldId, InternedModuleId, LifetimeParamId, ModuleDefId, + TypeOrConstParamId, + attrs::{AttrFlags, Docs, IsInnerDoc}, expr_store::path::Path, item_scope::ItemInNs, per_ns::Namespace, @@ -15,6 +18,7 @@ use hir_expand::{ name::Name, }; use hir_ty::{db::HirDatabase, method_resolution}; +use intern::Symbol; use crate::{ Adt, AsAssocItem, AssocItem, BuiltinType, Const, ConstParam, DocLinkDef, Enum, ExternCrateDecl, @@ -22,28 +26,161 @@ use crate::{ Struct, Trait, Type, TypeAlias, TypeParam, Union, Variant, VariantDef, }; -pub trait HasAttrs { - fn attrs(self, db: &dyn HirDatabase) -> AttrsWithOwner; +#[derive(Debug, Clone, Copy)] +pub enum AttrsOwner { + AttrDef(AttrDefId), + Field(FieldId), + LifetimeParam(LifetimeParamId), + TypeOrConstParam(TypeOrConstParamId), +} + +impl AttrsOwner { + #[inline] + fn attr_def(&self) -> Option { + match self { + AttrsOwner::AttrDef(it) => Some(*it), + _ => None, + } + } +} + +#[derive(Debug, Clone)] +pub struct AttrsWithOwner { + pub(crate) attrs: AttrFlags, + owner: AttrsOwner, +} + +impl AttrsWithOwner { + fn new(db: &dyn HirDatabase, owner: AttrDefId) -> Self { + Self { attrs: AttrFlags::query(db, owner), owner: AttrsOwner::AttrDef(owner) } + } + + fn new_field(db: &dyn HirDatabase, owner: FieldId) -> Self { + Self { attrs: AttrFlags::query_field(db, owner), owner: AttrsOwner::Field(owner) } + } + + fn new_lifetime_param(db: &dyn HirDatabase, owner: LifetimeParamId) -> Self { + Self { + attrs: AttrFlags::query_lifetime_param(db, owner), + owner: AttrsOwner::LifetimeParam(owner), + } + } + fn new_type_or_const_param(db: &dyn HirDatabase, owner: TypeOrConstParamId) -> Self { + Self { + attrs: AttrFlags::query_type_or_const_param(db, owner), + owner: AttrsOwner::TypeOrConstParam(owner), + } + } + + #[inline] + pub fn is_unstable(&self) -> bool { + self.attrs.contains(AttrFlags::IS_UNSTABLE) + } + + #[inline] + pub fn is_macro_export(&self) -> bool { + self.attrs.contains(AttrFlags::IS_MACRO_EXPORT) + } + + #[inline] + pub fn is_doc_notable_trait(&self) -> bool { + self.attrs.contains(AttrFlags::IS_DOC_NOTABLE_TRAIT) + } + + #[inline] + pub fn is_doc_hidden(&self) -> bool { + self.attrs.contains(AttrFlags::IS_DOC_HIDDEN) + } + + #[inline] + pub fn is_deprecated(&self) -> bool { + self.attrs.contains(AttrFlags::IS_DEPRECATED) + } + + #[inline] + pub fn is_non_exhaustive(&self) -> bool { + self.attrs.contains(AttrFlags::NON_EXHAUSTIVE) + } + + #[inline] + pub fn is_test(&self) -> bool { + self.attrs.contains(AttrFlags::IS_TEST) + } + + #[inline] + pub fn lang(&self, db: &dyn HirDatabase) -> Option<&'static str> { + self.owner + .attr_def() + .and_then(|owner| self.attrs.lang_item_with_attrs(db, owner)) + .map(|lang| lang.name()) + } + + #[inline] + pub fn doc_aliases<'db>(&self, db: &'db dyn HirDatabase) -> &'db [Symbol] { + let owner = match self.owner { + AttrsOwner::AttrDef(it) => Either::Left(it), + AttrsOwner::Field(it) => Either::Right(it), + AttrsOwner::LifetimeParam(_) | AttrsOwner::TypeOrConstParam(_) => return &[], + }; + self.attrs.doc_aliases(db, owner) + } + + #[inline] + pub fn cfgs<'db>(&self, db: &'db dyn HirDatabase) -> Option<&'db CfgExpr> { + let owner = match self.owner { + AttrsOwner::AttrDef(it) => Either::Left(it), + AttrsOwner::Field(it) => Either::Right(it), + AttrsOwner::LifetimeParam(_) | AttrsOwner::TypeOrConstParam(_) => return None, + }; + self.attrs.cfgs(db, owner) + } + + #[inline] + pub fn hir_docs<'db>(&self, db: &'db dyn HirDatabase) -> Option<&'db Docs> { + match self.owner { + AttrsOwner::AttrDef(it) => AttrFlags::docs(db, it).as_deref(), + AttrsOwner::Field(it) => AttrFlags::field_docs(db, it), + AttrsOwner::LifetimeParam(_) | AttrsOwner::TypeOrConstParam(_) => None, + } + } +} + +pub trait HasAttrs: Sized { + #[inline] + fn attrs(self, db: &dyn HirDatabase) -> AttrsWithOwner { + match self.attr_id(db) { + AttrsOwner::AttrDef(it) => AttrsWithOwner::new(db, it), + AttrsOwner::Field(it) => AttrsWithOwner::new_field(db, it), + AttrsOwner::LifetimeParam(it) => AttrsWithOwner::new_lifetime_param(db, it), + AttrsOwner::TypeOrConstParam(it) => AttrsWithOwner::new_type_or_const_param(db, it), + } + } + #[doc(hidden)] - fn attr_id(self) -> AttrDefId; + fn attr_id(self, db: &dyn HirDatabase) -> AttrsOwner; + + #[inline] + fn hir_docs(self, db: &dyn HirDatabase) -> Option<&Docs> { + match self.attr_id(db) { + AttrsOwner::AttrDef(it) => AttrFlags::docs(db, it).as_deref(), + AttrsOwner::Field(it) => AttrFlags::field_docs(db, it), + AttrsOwner::LifetimeParam(_) | AttrsOwner::TypeOrConstParam(_) => None, + } + } } macro_rules! impl_has_attrs { ($(($def:ident, $def_id:ident),)*) => {$( impl HasAttrs for $def { - fn attrs(self, db: &dyn HirDatabase) -> AttrsWithOwner { - let def = AttrDefId::$def_id(self.into()); - AttrsWithOwner::new(db, def) - } - fn attr_id(self) -> AttrDefId { - AttrDefId::$def_id(self.into()) + #[inline] + fn attr_id(self, _db: &dyn HirDatabase) -> AttrsOwner { + AttrsOwner::AttrDef(AttrDefId::$def_id(self.into())) } } )*}; } impl_has_attrs![ - (Field, FieldId), (Variant, EnumVariantId), (Static, StaticId), (Const, ConstId), @@ -52,8 +189,6 @@ impl_has_attrs![ (Macro, MacroId), (Function, FunctionId), (Adt, AdtId), - (Module, ModuleId), - (GenericParam, GenericParamId), (Impl, ImplId), (ExternCrateDecl, ExternCrateId), ]; @@ -61,11 +196,9 @@ impl_has_attrs![ macro_rules! impl_has_attrs_enum { ($($variant:ident),* for $enum:ident) => {$( impl HasAttrs for $variant { - fn attrs(self, db: &dyn HirDatabase) -> AttrsWithOwner { - $enum::$variant(self).attrs(db) - } - fn attr_id(self) -> AttrDefId { - $enum::$variant(self).attr_id() + #[inline] + fn attr_id(self, db: &dyn HirDatabase) -> AttrsOwner { + $enum::$variant(self).attr_id(db) } } )*}; @@ -74,30 +207,46 @@ macro_rules! impl_has_attrs_enum { impl_has_attrs_enum![Struct, Union, Enum for Adt]; impl_has_attrs_enum![TypeParam, ConstParam, LifetimeParam for GenericParam]; -impl HasAttrs for AssocItem { - fn attrs(self, db: &dyn HirDatabase) -> AttrsWithOwner { +impl HasAttrs for Module { + #[inline] + fn attr_id(self, db: &dyn HirDatabase) -> AttrsOwner { + AttrsOwner::AttrDef(AttrDefId::ModuleId(InternedModuleId::new(db, self.id))) + } +} + +impl HasAttrs for GenericParam { + #[inline] + fn attr_id(self, _db: &dyn HirDatabase) -> AttrsOwner { match self { - AssocItem::Function(it) => it.attrs(db), - AssocItem::Const(it) => it.attrs(db), - AssocItem::TypeAlias(it) => it.attrs(db), + GenericParam::TypeParam(it) => AttrsOwner::TypeOrConstParam(it.merge().into()), + GenericParam::ConstParam(it) => AttrsOwner::TypeOrConstParam(it.merge().into()), + GenericParam::LifetimeParam(it) => AttrsOwner::LifetimeParam(it.into()), } } - fn attr_id(self) -> AttrDefId { +} + +impl HasAttrs for AssocItem { + #[inline] + fn attr_id(self, db: &dyn HirDatabase) -> AttrsOwner { match self { - AssocItem::Function(it) => it.attr_id(), - AssocItem::Const(it) => it.attr_id(), - AssocItem::TypeAlias(it) => it.attr_id(), + AssocItem::Function(it) => it.attr_id(db), + AssocItem::Const(it) => it.attr_id(db), + AssocItem::TypeAlias(it) => it.attr_id(db), } } } impl HasAttrs for crate::Crate { - fn attrs(self, db: &dyn HirDatabase) -> AttrsWithOwner { - let def = AttrDefId::ModuleId(self.root_module().id); - AttrsWithOwner::new(db, def) + #[inline] + fn attr_id(self, db: &dyn HirDatabase) -> AttrsOwner { + self.root_module().attr_id(db) } - fn attr_id(self) -> AttrDefId { - AttrDefId::ModuleId(self.root_module().id) +} + +impl HasAttrs for Field { + #[inline] + fn attr_id(self, _db: &dyn HirDatabase) -> AttrsOwner { + AttrsOwner::Field(self.into()) } } @@ -107,21 +256,22 @@ pub fn resolve_doc_path_on( def: impl HasAttrs + Copy, link: &str, ns: Option, - is_inner_doc: bool, + is_inner_doc: IsInnerDoc, ) -> Option { - resolve_doc_path_on_(db, link, def.attr_id(), ns, is_inner_doc) + resolve_doc_path_on_(db, link, def.attr_id(db), ns, is_inner_doc) } fn resolve_doc_path_on_( db: &dyn HirDatabase, link: &str, - attr_id: AttrDefId, + attr_id: AttrsOwner, ns: Option, - is_inner_doc: bool, + is_inner_doc: IsInnerDoc, ) -> Option { let resolver = match attr_id { - AttrDefId::ModuleId(it) => { - if is_inner_doc { + AttrsOwner::AttrDef(AttrDefId::ModuleId(it)) => { + let it = it.loc(db); + if is_inner_doc.yes() { it.resolver(db) } else if let Some(parent) = Module::from(it).parent(db) { parent.id.resolver(db) @@ -129,20 +279,20 @@ fn resolve_doc_path_on_( it.resolver(db) } } - AttrDefId::FieldId(it) => it.parent.resolver(db), - AttrDefId::AdtId(it) => it.resolver(db), - AttrDefId::FunctionId(it) => it.resolver(db), - AttrDefId::EnumVariantId(it) => it.resolver(db), - AttrDefId::StaticId(it) => it.resolver(db), - AttrDefId::ConstId(it) => it.resolver(db), - AttrDefId::TraitId(it) => it.resolver(db), - AttrDefId::TypeAliasId(it) => it.resolver(db), - AttrDefId::ImplId(it) => it.resolver(db), - AttrDefId::ExternBlockId(it) => it.resolver(db), - AttrDefId::UseId(it) => it.resolver(db), - AttrDefId::MacroId(it) => it.resolver(db), - AttrDefId::ExternCrateId(it) => it.resolver(db), - AttrDefId::GenericParamId(_) => return None, + AttrsOwner::AttrDef(AttrDefId::AdtId(it)) => it.resolver(db), + AttrsOwner::AttrDef(AttrDefId::FunctionId(it)) => it.resolver(db), + AttrsOwner::AttrDef(AttrDefId::EnumVariantId(it)) => it.resolver(db), + AttrsOwner::AttrDef(AttrDefId::StaticId(it)) => it.resolver(db), + AttrsOwner::AttrDef(AttrDefId::ConstId(it)) => it.resolver(db), + AttrsOwner::AttrDef(AttrDefId::TraitId(it)) => it.resolver(db), + AttrsOwner::AttrDef(AttrDefId::TypeAliasId(it)) => it.resolver(db), + AttrsOwner::AttrDef(AttrDefId::ImplId(it)) => it.resolver(db), + AttrsOwner::AttrDef(AttrDefId::ExternBlockId(it)) => it.resolver(db), + AttrsOwner::AttrDef(AttrDefId::UseId(it)) => it.resolver(db), + AttrsOwner::AttrDef(AttrDefId::MacroId(it)) => it.resolver(db), + AttrsOwner::AttrDef(AttrDefId::ExternCrateId(it)) => it.resolver(db), + AttrsOwner::Field(it) => it.parent.resolver(db), + AttrsOwner::LifetimeParam(_) | AttrsOwner::TypeOrConstParam(_) => return None, }; let mut modpath = doc_modpath_from_str(link)?; diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index fca0162765ec..db092905eb90 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -153,8 +153,7 @@ pub struct UnresolvedImport { #[derive(Debug, Clone, Eq, PartialEq)] pub struct UnresolvedMacroCall { - pub macro_call: InFile, - pub precise_location: Option, + pub range: InFile, pub path: ModPath, pub is_bang: bool, } @@ -185,8 +184,7 @@ pub struct InactiveCode { #[derive(Debug, Clone, Eq, PartialEq)] pub struct MacroError { - pub node: InFile, - pub precise_location: Option, + pub range: InFile, pub message: String, pub error: bool, pub kind: &'static str, @@ -194,8 +192,7 @@ pub struct MacroError { #[derive(Debug, Clone, Eq, PartialEq)] pub struct MacroExpansionParseError { - pub node: InFile, - pub precise_location: Option, + pub range: InFile, pub errors: Arc<[SyntaxError]>, } @@ -213,12 +210,12 @@ pub struct UnimplementedBuiltinMacro { #[derive(Debug)] pub struct InvalidDeriveTarget { - pub node: InFile, + pub range: InFile, } #[derive(Debug)] pub struct MalformedDerive { - pub node: InFile, + pub range: InFile, } #[derive(Debug)] diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 4eb50c1c31c5..7c0764ece60b 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -48,11 +48,12 @@ use arrayvec::ArrayVec; use base_db::{CrateDisplayName, CrateOrigin, LangCrateOrigin}; use either::Either; use hir_def::{ - AdtId, AssocItemId, AssocItemLoc, AttrDefId, CallableDefId, ConstId, ConstParamId, - CrateRootModuleId, DefWithBodyId, EnumId, EnumVariantId, ExternBlockId, ExternCrateId, - FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, ItemContainerId, LifetimeParamId, - LocalFieldId, Lookup, MacroExpander, MacroId, ModuleId, StaticId, StructId, SyntheticSyntax, - TupleId, TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId, + AdtId, AssocItemId, AssocItemLoc, CallableDefId, ConstId, ConstParamId, CrateRootModuleId, + DefWithBodyId, EnumId, EnumVariantId, ExternBlockId, ExternCrateId, FunctionId, GenericDefId, + HasModule, ImplId, InternedModuleId, ItemContainerId, LifetimeParamId, LocalFieldId, Lookup, + MacroExpander, MacroId, ModuleId, StaticId, StructId, SyntheticSyntax, TupleId, TypeAliasId, + TypeOrConstParamId, TypeParamId, UnionId, + attrs::AttrFlags, expr_store::{ExpressionStoreDiagnostics, ExpressionStoreSourceMap}, hir::{ BindingAnnotation, BindingId, Expr, ExprId, ExprOrPatId, LabelId, Pat, @@ -66,13 +67,12 @@ use hir_def::{ }, per_ns::PerNs, resolver::{HasResolver, Resolver}, - signatures::{ImplFlags, StaticFlags, TraitFlags, VariantFields}, + signatures::{EnumSignature, ImplFlags, StaticFlags, TraitFlags, VariantFields}, src::HasSource as _, visibility::visibility_from_ast, }; use hir_expand::{ - AstId, MacroCallKind, RenderedExpandError, ValueResult, attrs::collect_attrs, - proc_macro::ProcMacroKind, + AstId, MacroCallKind, RenderedExpandError, ValueResult, proc_macro::ProcMacroKind, }; use hir_ty::{ AliasTy, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId, GenericArg, @@ -95,8 +95,8 @@ use smallvec::SmallVec; use span::{AstIdNode, Edition, FileId}; use stdx::{format_to, impl_from, never, variance::PhantomCovariantLifetime}; use syntax::{ - AstNode, AstPtr, SmolStr, SyntaxNode, SyntaxNodePtr, T, TextRange, ToSmolStr, - ast::{self, HasAttrs as _, HasName, HasVisibility as _}, + AstNode, AstPtr, SmolStr, SyntaxNode, SyntaxNodePtr, TextRange, ToSmolStr, + ast::{self, HasName, HasVisibility as _}, format_smolstr, }; use triomphe::{Arc, ThinArc}; @@ -104,7 +104,7 @@ use triomphe::{Arc, ThinArc}; use crate::db::{DefDatabase, HirDatabase}; pub use crate::{ - attrs::{HasAttrs, resolve_doc_path_on}, + attrs::{AttrsWithOwner, HasAttrs, resolve_doc_path_on}, diagnostics::*, has_source::HasSource, semantics::{ @@ -127,7 +127,7 @@ pub use { hir_def::{ Complete, ImportPathConfig, - attr::{AttrSourceMap, Attrs, AttrsWithOwner}, + attrs::{Docs, IsInnerDoc}, find_path::PrefixKind, import_map, lang_item::LangItem, @@ -141,7 +141,6 @@ pub use { }, hir_expand::{ EditionedFileId, ExpandResult, HirFileId, MacroCallId, MacroKind, - attrs::{Attr, AttrId}, change::ChangeWithProcMacros, files::{ FilePosition, FilePositionWrapper, FileRange, FileRangeWrapper, HirFilePosition, @@ -285,11 +284,10 @@ impl Crate { } /// Try to get the root URL of the documentation of a crate. - pub fn get_html_root_url(self: &Crate, db: &dyn HirDatabase) -> Option { + pub fn get_html_root_url(self, db: &dyn HirDatabase) -> Option { // Look for #![doc(html_root_url = "...")] - let attrs = db.attrs(AttrDefId::ModuleId(self.root_module().into())); - let doc_url = attrs.by_key(sym::doc).find_string_value_in_tt(sym::html_root_url); - doc_url.map(|s| s.trim_matches('"').trim_end_matches('/').to_owned() + "/") + let doc_url = AttrFlags::doc_html_root_url(db, self.id); + doc_url.as_ref().map(|s| s.trim_matches('"').trim_end_matches('/').to_owned() + "/") } pub fn cfg<'db>(&self, db: &'db dyn HirDatabase) -> &'db CfgOptions { @@ -634,7 +632,7 @@ impl Module { // FIXME: This is accidentally quadratic. continue; } - emit_def_diagnostic(db, acc, diag, edition); + emit_def_diagnostic(db, acc, diag, edition, def_map.krate()); } if !self.id.is_block_module() { @@ -653,8 +651,9 @@ impl Module { acc.extend(def.diagnostics(db, style_lints)) } ModuleDef::Trait(t) => { + let krate = t.krate(db); for diag in TraitItems::query_with_diagnostics(db, t.id).1.iter() { - emit_def_diagnostic(db, acc, diag, edition); + emit_def_diagnostic(db, acc, diag, edition, krate.id); } for item in t.items(db) { @@ -769,7 +768,7 @@ impl Module { let ast_id_map = db.ast_id_map(file_id); for diag in impl_def.id.impl_items_with_diagnostics(db).1.iter() { - emit_def_diagnostic(db, acc, diag, edition); + emit_def_diagnostic(db, acc, diag, edition, loc.container.krate()); } if inherent_impls.invalid_impls().contains(&impl_def.id) { @@ -793,21 +792,10 @@ impl Module { return None; } let parent = impl_def.id.into(); - let generic_params = db.generic_params(parent); - let lifetime_params = generic_params.iter_lt().map(|(local_id, _)| { - GenericParamId::LifetimeParamId(LifetimeParamId { parent, local_id }) - }); - let type_params = generic_params - .iter_type_or_consts() - .filter(|(_, it)| it.type_param().is_some()) - .map(|(local_id, _)| { - GenericParamId::TypeParamId(TypeParamId::from_unchecked( - TypeOrConstParamId { parent, local_id }, - )) - }); - let res = type_params.chain(lifetime_params).any(|p| { - db.attrs(AttrDefId::GenericParamId(p)).by_key(sym::may_dangle).exists() - }); + let (lifetimes_attrs, type_and_consts_attrs) = + AttrFlags::query_generic_params(db, parent); + let res = lifetimes_attrs.values().any(|it| it.contains(AttrFlags::MAY_DANGLE)) + || type_and_consts_attrs.values().any(|it| it.contains(AttrFlags::MAY_DANGLE)); Some(res) })() .unwrap_or(false); @@ -976,6 +964,17 @@ impl Module { ) -> Option { hir_def::find_path::find_path(db, item.into().into(), self.into(), prefix_kind, true, cfg) } + + #[inline] + pub fn doc_keyword(self, db: &dyn HirDatabase) -> Option { + AttrFlags::doc_keyword(db, InternedModuleId::new(db, self.id)) + } + + /// Whether it has `#[path = "..."]` attribute. + #[inline] + pub fn has_path(&self, db: &dyn HirDatabase) -> bool { + self.attrs(db).attrs.contains(AttrFlags::HAS_PATH) + } } fn macro_call_diagnostics<'db>( @@ -990,31 +989,19 @@ fn macro_call_diagnostics<'db>( if let Some(err) = err { let loc = db.lookup_intern_macro_call(macro_call_id); let file_id = loc.kind.file_id(); - let node = - InFile::new(file_id, db.ast_id_map(file_id).get_erased(loc.kind.erased_ast_id())); + let mut range = precise_macro_call_location(&loc.kind, db, loc.krate); let RenderedExpandError { message, error, kind } = err.render_to_string(db); - let editioned_file_id = EditionedFileId::from_span(db, err.span().anchor.file_id); - let precise_location = if editioned_file_id == file_id { - Some( - err.span().range - + db.ast_id_map(editioned_file_id.into()) - .get_erased(err.span().anchor.ast_id) - .text_range() - .start(), - ) - } else { - None - }; - acc.push(MacroError { node, precise_location, message, error, kind }.into()); + if Some(err.span().anchor.file_id) == file_id.file_id().map(|it| it.editioned_file_id(db)) { + range.value = err.span().range + + db.ast_id_map(file_id).get_erased(err.span().anchor.ast_id).text_range().start(); + } + acc.push(MacroError { range, message, error, kind }.into()); } if !parse_errors.is_empty() { let loc = db.lookup_intern_macro_call(macro_call_id); - let (node, precise_location) = precise_macro_call_location(&loc.kind, db); - acc.push( - MacroExpansionParseError { node, precise_location, errors: parse_errors.clone() } - .into(), - ) + let range = precise_macro_call_location(&loc.kind, db, loc.krate); + acc.push(MacroExpansionParseError { range, errors: parse_errors.clone() }.into()) } } @@ -1038,6 +1025,7 @@ fn emit_macro_def_diagnostics<'db>( acc, &DefDiagnosticKind::MacroDefError { ast, message: e.to_string() }, edition, + m.krate(db).id, ); } } @@ -1047,8 +1035,9 @@ fn emit_def_diagnostic<'db>( acc: &mut Vec>, diag: &DefDiagnostic, edition: Edition, + krate: base_db::Crate, ) { - emit_def_diagnostic_(db, acc, &diag.kind, edition) + emit_def_diagnostic_(db, acc, &diag.kind, edition, krate) } fn emit_def_diagnostic_<'db>( @@ -1056,6 +1045,7 @@ fn emit_def_diagnostic_<'db>( acc: &mut Vec>, diag: &DefDiagnosticKind, edition: Edition, + krate: base_db::Crate, ) { match diag { DefDiagnosticKind::UnresolvedModule { ast: declaration, candidates } => { @@ -1078,8 +1068,7 @@ fn emit_def_diagnostic_<'db>( let RenderedExpandError { message, error, kind } = err.render_to_string(db); acc.push( MacroError { - node: InFile::new(ast.file_id, item.syntax_node_ptr()), - precise_location: None, + range: InFile::new(ast.file_id, item.text_range()), message: format!("{}: {message}", path.display(db, edition)), error, kind, @@ -1109,11 +1098,10 @@ fn emit_def_diagnostic_<'db>( ); } DefDiagnosticKind::UnresolvedMacroCall { ast, path } => { - let (node, precise_location) = precise_macro_call_location(ast, db); + let location = precise_macro_call_location(ast, db, krate); acc.push( UnresolvedMacroCall { - macro_call: node, - precise_location, + range: location, path: path.clone(), is_bang: matches!(ast, MacroCallKind::FnLike { .. }), } @@ -1132,34 +1120,12 @@ fn emit_def_diagnostic_<'db>( ); } DefDiagnosticKind::InvalidDeriveTarget { ast, id } => { - let node = ast.to_node(db); - let derive = node.attrs().nth(*id); - match derive { - Some(derive) => { - acc.push( - InvalidDeriveTarget { - node: ast.with_value(SyntaxNodePtr::from(AstPtr::new(&derive))), - } - .into(), - ); - } - None => stdx::never!("derive diagnostic on item without derive attribute"), - } + let derive = id.find_attr_range(db, krate, *ast).3.path_range(); + acc.push(InvalidDeriveTarget { range: ast.with_value(derive) }.into()); } DefDiagnosticKind::MalformedDerive { ast, id } => { - let node = ast.to_node(db); - let derive = node.attrs().nth(*id); - match derive { - Some(derive) => { - acc.push( - MalformedDerive { - node: ast.with_value(SyntaxNodePtr::from(AstPtr::new(&derive))), - } - .into(), - ); - } - None => stdx::never!("derive diagnostic on item without derive attribute"), - } + let derive = id.find_attr_range(db, krate, *ast).2; + acc.push(MalformedDerive { range: ast.with_value(derive) }.into()); } DefDiagnosticKind::MacroDefError { ast, message } => { let node = ast.to_node(db); @@ -1178,61 +1144,28 @@ fn emit_def_diagnostic_<'db>( fn precise_macro_call_location( ast: &MacroCallKind, db: &dyn HirDatabase, -) -> (InFile, Option) { + krate: base_db::Crate, +) -> InFile { // FIXME: maybe we actually want slightly different ranges for the different macro diagnostics // - e.g. the full attribute for macro errors, but only the name for name resolution match ast { MacroCallKind::FnLike { ast_id, .. } => { let node = ast_id.to_node(db); - ( - ast_id.with_value(SyntaxNodePtr::from(AstPtr::new(&node))), - node.path() - .and_then(|it| it.segment()) - .and_then(|it| it.name_ref()) - .map(|it| it.syntax().text_range()), - ) + let range = node + .path() + .and_then(|it| it.segment()) + .and_then(|it| it.name_ref()) + .map(|it| it.syntax().text_range()); + let range = range.unwrap_or_else(|| node.syntax().text_range()); + ast_id.with_value(range) } MacroCallKind::Derive { ast_id, derive_attr_index, derive_index, .. } => { - let node = ast_id.to_node(db); - // Compute the precise location of the macro name's token in the derive - // list. - let token = (|| { - let derive_attr = collect_attrs(&node) - .nth(derive_attr_index.ast_index()) - .and_then(|x| Either::left(x.1))?; - let token_tree = derive_attr.meta()?.token_tree()?; - let chunk_by = token_tree - .syntax() - .children_with_tokens() - .filter_map(|elem| match elem { - syntax::NodeOrToken::Token(tok) => Some(tok), - _ => None, - }) - .chunk_by(|t| t.kind() == T![,]); - let (_, mut group) = chunk_by - .into_iter() - .filter(|&(comma, _)| !comma) - .nth(*derive_index as usize)?; - group.find(|t| t.kind() == T![ident]) - })(); - ( - ast_id.with_value(SyntaxNodePtr::from(AstPtr::new(&node))), - token.as_ref().map(|tok| tok.text_range()), - ) + let range = derive_attr_index.find_derive_range(db, krate, *ast_id, *derive_index); + ast_id.with_value(range) } - MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => { - let node = ast_id.to_node(db); - let attr = collect_attrs(&node) - .nth(invoc_attr_index.ast_index()) - .and_then(|x| Either::left(x.1)) - .unwrap_or_else(|| { - panic!("cannot find attribute #{}", invoc_attr_index.ast_index()) - }); - - ( - ast_id.with_value(SyntaxNodePtr::from(AstPtr::new(&attr))), - Some(attr.syntax().text_range()), - ) + MacroCallKind::Attr { ast_id, censored_attr_ids: attr_ids, .. } => { + let attr_range = attr_ids.invoc_attr().find_attr_range(db, krate, *ast_id).2; + ast_id.with_value(attr_range) } } } @@ -1449,7 +1382,7 @@ impl Struct { } pub fn repr(self, db: &dyn HirDatabase) -> Option { - db.struct_signature(self.id).repr + AttrFlags::repr(db, self.id.into()) } pub fn kind(self, db: &dyn HirDatabase) -> StructKind { @@ -1465,7 +1398,7 @@ impl Struct { } pub fn is_unstable(self, db: &dyn HirDatabase) -> bool { - db.attrs(self.id.into()).is_unstable() + AttrFlags::query(db, self.id.into()).contains(AttrFlags::IS_UNSTABLE) } pub fn instantiate_infer<'db>(self, infer_ctxt: &InferCtxt<'db>) -> InstantiatedStruct<'db> { @@ -1554,7 +1487,7 @@ impl Union { .collect() } pub fn is_unstable(self, db: &dyn HirDatabase) -> bool { - db.attrs(self.id.into()).is_unstable() + AttrFlags::query(db, self.id.into()).contains(AttrFlags::IS_UNSTABLE) } } @@ -1589,7 +1522,7 @@ impl Enum { } pub fn repr(self, db: &dyn HirDatabase) -> Option { - db.enum_signature(self.id).repr + AttrFlags::repr(db, self.id.into()) } pub fn ty<'db>(self, db: &'db dyn HirDatabase) -> Type<'db> { @@ -1604,7 +1537,7 @@ impl Enum { pub fn variant_body_ty<'db>(self, db: &'db dyn HirDatabase) -> Type<'db> { Type::new_for_crate( self.id.lookup(db).container.krate(), - TyBuilder::builtin(match db.enum_signature(self.id).variant_body_type() { + TyBuilder::builtin(match EnumSignature::variant_body_type(db, self.id) { layout::IntegerType::Pointer(sign) => match sign { true => hir_def::builtin_type::BuiltinType::Int( hir_def::builtin_type::BuiltinInt::Isize, @@ -1643,7 +1576,7 @@ impl Enum { } pub fn is_unstable(self, db: &dyn HirDatabase) -> bool { - db.attrs(self.id.into()).is_unstable() + AttrFlags::query(db, self.id.into()).contains(AttrFlags::IS_UNSTABLE) } } @@ -1744,7 +1677,7 @@ impl Variant { } pub fn is_unstable(self, db: &dyn HirDatabase) -> bool { - db.attrs(self.id.into()).is_unstable() + AttrFlags::query(db, self.id.into()).contains(AttrFlags::IS_UNSTABLE) } pub fn instantiate_infer<'db>(self, infer_ctxt: &InferCtxt<'db>) -> InstantiatedVariant<'db> { @@ -2242,8 +2175,7 @@ fn expr_store_diagnostics<'db>( InactiveCode { node: *node, cfg: cfg.clone(), opts: opts.clone() }.into() } ExpressionStoreDiagnostics::UnresolvedMacroCall { node, path } => UnresolvedMacroCall { - macro_call: (*node).map(|ast_ptr| ast_ptr.into()), - precise_location: None, + range: node.map(|ptr| ptr.text_range()), path: path.clone(), is_bang: true, } @@ -2500,33 +2432,33 @@ impl Function { /// Does this function have `#[test]` attribute? pub fn is_test(self, db: &dyn HirDatabase) -> bool { - db.attrs(self.id.into()).is_test() + self.attrs(db).is_test() } /// is this a `fn main` or a function with an `export_name` of `main`? pub fn is_main(self, db: &dyn HirDatabase) -> bool { - db.attrs(self.id.into()).export_name() == Some(&sym::main) + self.exported_main(db) || self.module(db).is_crate_root() && db.function_signature(self.id).name == sym::main } /// Is this a function with an `export_name` of `main`? pub fn exported_main(self, db: &dyn HirDatabase) -> bool { - db.attrs(self.id.into()).export_name() == Some(&sym::main) + AttrFlags::query(db, self.id.into()).contains(AttrFlags::IS_EXPORT_NAME_MAIN) } /// Does this function have the ignore attribute? pub fn is_ignore(self, db: &dyn HirDatabase) -> bool { - db.attrs(self.id.into()).is_ignore() + AttrFlags::query(db, self.id.into()).contains(AttrFlags::IS_IGNORE) } /// Does this function have `#[bench]` attribute? pub fn is_bench(self, db: &dyn HirDatabase) -> bool { - db.attrs(self.id.into()).is_bench() + AttrFlags::query(db, self.id.into()).contains(AttrFlags::IS_BENCH) } /// Is this function marked as unstable with `#[feature]` attribute? pub fn is_unstable(self, db: &dyn HirDatabase) -> bool { - db.attrs(self.id.into()).is_unstable() + AttrFlags::query(db, self.id.into()).contains(AttrFlags::IS_UNSTABLE) } pub fn is_unsafe_to_call( @@ -2535,9 +2467,8 @@ impl Function { caller: Option, call_edition: Edition, ) -> bool { - let target_features = caller - .map(|caller| hir_ty::TargetFeatures::from_attrs(&db.attrs(caller.id.into()))) - .unwrap_or_default(); + let target_features = + caller.map(|caller| hir_ty::TargetFeatures::from_fn(db, caller.id)).unwrap_or_default(); matches!( hir_ty::is_fn_unsafe_to_call(db, self.id, &target_features, call_edition), hir_ty::Unsafety::Unsafe @@ -2552,14 +2483,6 @@ impl Function { } pub fn as_proc_macro(self, db: &dyn HirDatabase) -> Option { - let attrs = db.attrs(self.id.into()); - // FIXME: Store this in FunctionData flags? - if !(attrs.is_proc_macro() - || attrs.is_proc_macro_attribute() - || attrs.is_proc_macro_derive()) - { - return None; - } let def_map = crate_def_map(db, HasModule::krate(&self.id, db)); def_map.fn_as_proc_macro(self.id).map(|id| Macro { id: id.into() }) } @@ -3024,7 +2947,7 @@ impl Trait { /// `#[rust_analyzer::completions(...)]` mode. pub fn complete(self, db: &dyn HirDatabase) -> Complete { - Complete::extract(true, &self.attrs(db)) + Complete::extract(true, self.attrs(db).attrs) } } @@ -3199,10 +3122,10 @@ impl Macro { let loc = id.lookup(db); let source = loc.source(db); match loc.kind { - ProcMacroKind::CustomDerive => db - .attrs(id.into()) - .parse_proc_macro_derive() - .map_or_else(|| as_name_opt(source.value.name()), |(it, _)| it), + ProcMacroKind::CustomDerive => AttrFlags::derive_info(db, self.id).map_or_else( + || as_name_opt(source.value.name()), + |info| Name::new_symbol_root(info.trait_name.clone()), + ), ProcMacroKind::Bang | ProcMacroKind::Attr => as_name_opt(source.value.name()), } } @@ -3210,7 +3133,7 @@ impl Macro { } pub fn is_macro_export(self, db: &dyn HirDatabase) -> bool { - matches!(self.id, MacroId::MacroRulesId(_) if db.attrs(self.id.into()).by_key(sym::macro_export).exists()) + matches!(self.id, MacroId::MacroRulesId(_) if AttrFlags::query(db, self.id.into()).contains(AttrFlags::IS_MACRO_EXPORT)) } pub fn is_proc_macro(self) -> bool { @@ -4038,18 +3961,10 @@ impl DeriveHelper { } pub fn name(&self, db: &dyn HirDatabase) -> Name { - match self.derive { - makro @ MacroId::Macro2Id(_) => db - .attrs(makro.into()) - .parse_rustc_builtin_macro() - .and_then(|(_, helpers)| helpers.get(self.idx as usize).cloned()), - MacroId::MacroRulesId(_) => None, - makro @ MacroId::ProcMacroId(_) => db - .attrs(makro.into()) - .parse_proc_macro_derive() - .and_then(|(_, helpers)| helpers.get(self.idx as usize).cloned()), - } - .unwrap_or_else(Name::missing) + AttrFlags::derive_info(db, self.derive) + .and_then(|it| it.helpers.get(self.idx as usize)) + .map(|helper| Name::new_symbol_root(helper.clone())) + .unwrap_or_else(Name::missing) } } @@ -4263,7 +4178,7 @@ impl TypeParam { } pub fn is_unstable(self, db: &dyn HirDatabase) -> bool { - db.attrs(GenericParamId::from(self.id).into()).is_unstable() + self.attrs(db).is_unstable() } } diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index b43165fd8ad7..1fe270c7b607 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -21,7 +21,6 @@ use hir_def::{ }; use hir_expand::{ EditionedFileId, ExpandResult, FileRange, HirFileId, InMacroFile, MacroCallId, - attrs::collect_attrs, builtin::{BuiltinFnLikeExpander, EagerExpander}, db::ExpandDatabase, files::{FileRangeWrapper, HirFileRange, InRealFile}, @@ -33,7 +32,7 @@ use intern::{Interned, Symbol, sym}; use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; use smallvec::{SmallVec, smallvec}; -use span::{Edition, FileId, SyntaxContext}; +use span::{FileId, SyntaxContext}; use stdx::TupleExt; use syntax::{ AstNode, AstToken, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, @@ -376,17 +375,14 @@ impl<'db> SemanticsImpl<'db> { } pub fn attach_first_edition(&self, file: FileId) -> Option { - Some(EditionedFileId::new( - self.db, - file, - self.file_to_module_defs(file).next()?.krate().edition(self.db), - )) + let krate = self.file_to_module_defs(file).next()?.krate(); + Some(EditionedFileId::new(self.db, file, krate.edition(self.db), krate.id)) } pub fn parse_guess_edition(&self, file_id: FileId) -> ast::SourceFile { let file_id = self .attach_first_edition(file_id) - .unwrap_or_else(|| EditionedFileId::new(self.db, file_id, Edition::CURRENT)); + .unwrap_or_else(|| EditionedFileId::current_edition_guess_origin(self.db, file_id)); let tree = self.db.parse(file_id).tree(); self.cache(tree.syntax().clone(), file_id.into()); @@ -1187,33 +1183,34 @@ impl<'db> SemanticsImpl<'db> { .zip(Some(item)) }) .map(|(call_id, item)| { - let attr_id = match db.lookup_intern_macro_call(call_id).kind { + let item_range = item.syntax().text_range(); + let loc = db.lookup_intern_macro_call(call_id); + let text_range = match loc.kind { hir_expand::MacroCallKind::Attr { - invoc_attr_index, .. - } => invoc_attr_index.ast_index(), - _ => 0, + censored_attr_ids: attr_ids, + .. + } => { + // FIXME: here, the attribute's text range is used to strip away all + // entries from the start of the attribute "list" up the invoking + // attribute. But in + // ``` + // mod foo { + // #![inner] + // } + // ``` + // we don't wanna strip away stuff in the `mod foo {` range, that is + // here if the id corresponds to an inner attribute we got strip all + // text ranges of the outer ones, and then all of the inner ones up + // to the invoking attribute so that the inbetween is ignored. + // FIXME: Should cfg_attr be handled differently? + let (attr, _, _, _) = attr_ids + .invoc_attr() + .find_attr_range_with_source(db, loc.krate, &item); + let start = attr.syntax().text_range().start(); + TextRange::new(start, item_range.end()) + } + _ => item_range, }; - // FIXME: here, the attribute's text range is used to strip away all - // entries from the start of the attribute "list" up the invoking - // attribute. But in - // ``` - // mod foo { - // #![inner] - // } - // ``` - // we don't wanna strip away stuff in the `mod foo {` range, that is - // here if the id corresponds to an inner attribute we got strip all - // text ranges of the outer ones, and then all of the inner ones up - // to the invoking attribute so that the inbetween is ignored. - let text_range = item.syntax().text_range(); - let start = collect_attrs(&item) - .nth(attr_id) - .map(|attr| match attr.1 { - Either::Left(it) => it.syntax().text_range().start(), - Either::Right(it) => it.syntax().text_range().start(), - }) - .unwrap_or_else(|| text_range.start()); - let text_range = TextRange::new(start, text_range.end()); filter_duplicates(tokens, text_range); process_expansion_for_token(ctx, &mut stack, call_id) }) @@ -1463,6 +1460,14 @@ impl<'db> SemanticsImpl<'db> { FileRangeWrapper { file_id: file_id.file_id(self.db), range } } + pub fn diagnostics_display_range_for_range( + &self, + src: InFile, + ) -> FileRangeWrapper { + let FileRange { file_id, range } = src.original_node_file_range_rooted(self.db); + FileRangeWrapper { file_id: file_id.file_id(self.db), range } + } + fn token_ancestors_with_macros( &self, token: SyntaxToken, diff --git a/crates/hir/src/semantics/child_by_source.rs b/crates/hir/src/semantics/child_by_source.rs index 5019a5987e51..165ac7e4a08d 100644 --- a/crates/hir/src/semantics/child_by_source.rs +++ b/crates/hir/src/semantics/child_by_source.rs @@ -5,7 +5,7 @@ //! node for a *child*, and get its hir. use either::Either; -use hir_expand::{HirFileId, attrs::collect_attrs}; +use hir_expand::HirFileId; use span::AstIdNode; use syntax::{AstPtr, ast}; @@ -94,6 +94,7 @@ impl ChildBySource for ModuleId { impl ChildBySource for ItemScope { fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) { + let krate = file_id.krate(db); self.declarations().for_each(|item| add_module_def(db, res, file_id, item)); self.impls().for_each(|imp| insert_item_loc(db, res, file_id, imp, keys::IMPL)); self.extern_blocks().for_each(|extern_block| { @@ -123,12 +124,10 @@ impl ChildBySource for ItemScope { |(ast_id, calls)| { let adt = ast_id.to_node(db); calls.for_each(|(attr_id, call_id, calls)| { - if let Some((_, Either::Left(attr))) = - collect_attrs(&adt).nth(attr_id.ast_index()) - { - res[keys::DERIVE_MACRO_CALL] - .insert(AstPtr::new(&attr), (attr_id, call_id, calls.into())); - } + // FIXME: Fix cfg_attr handling. + let (attr, _, _, _) = attr_id.find_attr_range_with_source(db, krate, &adt); + res[keys::DERIVE_MACRO_CALL] + .insert(AstPtr::new(&attr), (attr_id, call_id, calls.into())); }); }, ); diff --git a/crates/hir/src/symbols.rs b/crates/hir/src/symbols.rs index d8c624e5c689..9059c88ad66a 100644 --- a/crates/hir/src/symbols.rs +++ b/crates/hir/src/symbols.rs @@ -392,12 +392,12 @@ impl<'a> SymbolCollector<'a> { let mut do_not_complete = Complete::Yes; if let Some(attrs) = def.attrs(self.db) { - do_not_complete = Complete::extract(matches!(def, ModuleDef::Trait(_)), &attrs); + do_not_complete = Complete::extract(matches!(def, ModuleDef::Trait(_)), attrs.attrs); if let Some(trait_do_not_complete) = trait_do_not_complete { do_not_complete = Complete::for_trait_item(trait_do_not_complete, do_not_complete); } - for alias in attrs.doc_aliases() { + for alias in attrs.doc_aliases(self.db) { self.symbols.insert(FileSymbol { name: alias.clone(), def, @@ -441,9 +441,9 @@ impl<'a> SymbolCollector<'a> { let mut do_not_complete = Complete::Yes; if let Some(attrs) = def.attrs(self.db) { - do_not_complete = Complete::extract(matches!(def, ModuleDef::Trait(_)), &attrs); + do_not_complete = Complete::extract(matches!(def, ModuleDef::Trait(_)), attrs.attrs); - for alias in attrs.doc_aliases() { + for alias in attrs.doc_aliases(self.db) { self.symbols.insert(FileSymbol { name: alias.clone(), def, diff --git a/crates/ide-assists/src/handlers/add_missing_match_arms.rs b/crates/ide-assists/src/handlers/add_missing_match_arms.rs index 1ece7ddab101..0fae7fb55dcd 100644 --- a/crates/ide-assists/src/handlers/add_missing_match_arms.rs +++ b/crates/ide-assists/src/handlers/add_missing_match_arms.rs @@ -1,7 +1,7 @@ use std::iter::{self, Peekable}; use either::Either; -use hir::{Adt, AsAssocItem, Crate, HasAttrs, ImportPathConfig, ModuleDef, Semantics, sym}; +use hir::{Adt, AsAssocItem, Crate, HasAttrs, ImportPathConfig, ModuleDef, Semantics}; use ide_db::RootDatabase; use ide_db::assists::ExprFillDefaultMode; use ide_db::syntax_helpers::suggest_name; @@ -402,7 +402,7 @@ impl ExtendedVariant { fn should_be_hidden(self, db: &RootDatabase, krate: Crate) -> bool { match self { ExtendedVariant::Variant { variant: var, .. } => { - var.attrs(db).has_doc_hidden() && var.module(db).krate() != krate + var.attrs(db).is_doc_hidden() && var.module(db).krate() != krate } _ => false, } @@ -425,7 +425,7 @@ impl ExtendedEnum { fn is_non_exhaustive(&self, db: &RootDatabase, krate: Crate) -> bool { match self { ExtendedEnum::Enum { enum_: e, .. } => { - e.attrs(db).by_key(sym::non_exhaustive).exists() && e.module(db).krate() != krate + e.attrs(db).is_non_exhaustive() && e.module(db).krate() != krate } _ => false, } diff --git a/crates/ide-assists/src/handlers/destructure_struct_binding.rs b/crates/ide-assists/src/handlers/destructure_struct_binding.rs index b8c647ac8b71..cfcd1c9f82cc 100644 --- a/crates/ide-assists/src/handlers/destructure_struct_binding.rs +++ b/crates/ide-assists/src/handlers/destructure_struct_binding.rs @@ -1,4 +1,4 @@ -use hir::{HasVisibility, sym}; +use hir::HasVisibility; use ide_db::{ FxHashMap, FxHashSet, assists::AssistId, @@ -93,7 +93,7 @@ fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option) -> let mut buf = String::from("./"); let db = ctx.db(); match parent_module.name(db) { - Some(name) - if !parent_module.is_mod_rs(db) - && parent_module - .attrs(db) - .by_key(sym::path) - .string_value_unescape() - .is_none() => - { + Some(name) if !parent_module.is_mod_rs(db) && !parent_module.has_path(db) => { format_to!(buf, "{}/", name.as_str()) } _ => (), diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index 5008f97447b7..2e488e541718 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -68,7 +68,7 @@ pub mod utils; use hir::Semantics; use ide_db::{EditionedFileId, RootDatabase, base_db::salsa}; -use syntax::{Edition, TextRange}; +use syntax::TextRange; pub(crate) use crate::assist_context::{AssistContext, Assists}; @@ -90,7 +90,7 @@ pub fn assists( let sema = Semantics::new(db); let file_id = sema .attach_first_edition(range.file_id) - .unwrap_or_else(|| EditionedFileId::new(db, range.file_id, Edition::CURRENT)); + .unwrap_or_else(|| EditionedFileId::current_edition_guess_origin(db, range.file_id)); let ctx = AssistContext::new(sema, config, hir::FileRange { file_id, range: range.range }); let mut acc = Assists::new(&ctx, resolve); // the handlers may invoke trait solving related things which accesses salsa structs outside queries diff --git a/crates/ide-assists/src/tests.rs b/crates/ide-assists/src/tests.rs index f4daabfe915d..cbb11c893e28 100644 --- a/crates/ide-assists/src/tests.rs +++ b/crates/ide-assists/src/tests.rs @@ -308,11 +308,13 @@ fn check_with_config( let _tracing = setup_tracing(); let (mut db, file_with_caret_id, range_or_offset) = RootDatabase::with_range_or_offset(before); db.enable_proc_attr_macros(); + let sema = Semantics::new(&db); + let file_with_caret_id = + sema.attach_first_edition(file_with_caret_id.file_id(&db)).unwrap_or(file_with_caret_id); let text_without_caret = db.file_text(file_with_caret_id.file_id(&db)).text(&db).to_string(); let frange = hir::FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; - let sema = Semantics::new(&db); let ctx = AssistContext::new(sema, &config, frange); let resolve = match expected { ExpectedResult::Unresolved => AssistResolveStrategy::None, diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index 20e0302b57d7..0432e6d43858 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -93,16 +93,7 @@ pub fn test_related_attribute_syn(fn_def: &ast::Fn) -> Option { } pub fn has_test_related_attribute(attrs: &hir::AttrsWithOwner) -> bool { - attrs.iter().any(|attr| { - let path = attr.path(); - (|| { - Some( - path.segments().first()?.as_str().starts_with("test") - || path.segments().last()?.as_str().ends_with("test"), - ) - })() - .unwrap_or_default() - }) + attrs.is_test() } #[derive(Clone, Copy, PartialEq)] @@ -128,7 +119,7 @@ pub fn filter_assoc_items( .copied() .filter(|assoc_item| { if ignore_items == IgnoreAssocItems::DocHiddenAttrPresent - && assoc_item.attrs(sema.db).has_doc_hidden() + && assoc_item.attrs(sema.db).is_doc_hidden() { if let hir::AssocItem::Function(f) = assoc_item && !f.has_body(sema.db) diff --git a/crates/ide-completion/src/completions/attribute/lint.rs b/crates/ide-completion/src/completions/attribute/lint.rs index c87c46d98127..df577b8ed02e 100644 --- a/crates/ide-completion/src/completions/attribute/lint.rs +++ b/crates/ide-completion/src/completions/attribute/lint.rs @@ -56,7 +56,7 @@ pub(super) fn complete_lint( }; let mut item = CompletionItem::new(SymbolKind::Attribute, ctx.source_range(), label, ctx.edition); - item.documentation(Documentation::new(description.to_owned())); + item.documentation(Documentation::new_owned(description.to_owned())); item.add_to(acc, ctx.db) } } diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs index dad8a76de87d..fe17b56d445c 100644 --- a/crates/ide-completion/src/completions/flyimport.rs +++ b/crates/ide-completion/src/completions/flyimport.rs @@ -266,7 +266,7 @@ fn import_on_the_fly( let original_item = &import.original_item; !ctx.is_item_hidden(&import.item_to_import) && !ctx.is_item_hidden(original_item) - && ctx.check_stability(original_item.attrs(ctx.db).as_deref()) + && ctx.check_stability(original_item.attrs(ctx.db).as_ref()) }) .filter(|import| filter_excluded_flyimport(ctx, import)) .sorted_by(|a, b| { @@ -313,7 +313,7 @@ fn import_on_the_fly_pat_( let original_item = &import.original_item; !ctx.is_item_hidden(&import.item_to_import) && !ctx.is_item_hidden(original_item) - && ctx.check_stability(original_item.attrs(ctx.db).as_deref()) + && ctx.check_stability(original_item.attrs(ctx.db).as_ref()) }) .sorted_by(|a, b| { let key = |import_path| { diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs index 0058611a6153..b3d91206d5b3 100644 --- a/crates/ide-completion/src/completions/postfix.rs +++ b/crates/ide-completion/src/completions/postfix.rs @@ -444,7 +444,7 @@ fn add_custom_postfix_completions( let body = snippet.postfix_snippet(receiver_text); let mut builder = postfix_snippet(trigger, snippet.description.as_deref().unwrap_or_default(), &body); - builder.documentation(Documentation::new(format!("```rust\n{body}\n```"))); + builder.documentation(Documentation::new_owned(format!("```rust\n{body}\n```"))); for import in imports.into_iter() { builder.add_import(import); } diff --git a/crates/ide-completion/src/completions/snippet.rs b/crates/ide-completion/src/completions/snippet.rs index ead9852eff53..04450aea75bf 100644 --- a/crates/ide-completion/src/completions/snippet.rs +++ b/crates/ide-completion/src/completions/snippet.rs @@ -139,7 +139,7 @@ fn add_custom_completions( }; let body = snip.snippet(); let mut builder = snippet(ctx, cap, trigger, &body); - builder.documentation(Documentation::new(format!("```rust\n{body}\n```"))); + builder.documentation(Documentation::new_owned(format!("```rust\n{body}\n```"))); for import in imports.into_iter() { builder.add_import(import); } diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 28c00cde126c..ebab6c145303 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -553,7 +553,7 @@ impl CompletionContext<'_> { I: hir::HasAttrs + Copy, { let attrs = item.attrs(self.db); - attrs.doc_aliases().map(|it| it.as_str().into()).collect() + attrs.doc_aliases(self.db).iter().map(|it| it.as_str().into()).collect() } /// Check if an item is `#[doc(hidden)]`. @@ -567,7 +567,7 @@ impl CompletionContext<'_> { } /// Checks whether this item should be listed in regards to stability. Returns `true` if we should. - pub(crate) fn check_stability(&self, attrs: Option<&hir::Attrs>) -> bool { + pub(crate) fn check_stability(&self, attrs: Option<&hir::AttrsWithOwner>) -> bool { let Some(attrs) = attrs else { return true; }; @@ -585,15 +585,15 @@ impl CompletionContext<'_> { /// Whether the given trait is an operator trait or not. pub(crate) fn is_ops_trait(&self, trait_: hir::Trait) -> bool { - match trait_.attrs(self.db).lang() { - Some(lang) => OP_TRAIT_LANG_NAMES.contains(&lang.as_str()), + match trait_.attrs(self.db).lang(self.db) { + Some(lang) => OP_TRAIT_LANG_NAMES.contains(&lang), None => false, } } /// Whether the given trait has `#[doc(notable_trait)]` pub(crate) fn is_doc_notable_trait(&self, trait_: hir::Trait) -> bool { - trait_.attrs(self.db).has_doc_notable_trait() + trait_.attrs(self.db).is_doc_notable_trait() } /// Returns the traits in scope, with the [`Drop`] trait removed. @@ -657,7 +657,7 @@ impl CompletionContext<'_> { fn is_visible_impl( &self, vis: &hir::Visibility, - attrs: &hir::Attrs, + attrs: &hir::AttrsWithOwner, defining_crate: hir::Crate, ) -> Visible { if !self.check_stability(Some(attrs)) { @@ -679,14 +679,18 @@ impl CompletionContext<'_> { if self.is_doc_hidden(attrs, defining_crate) { Visible::No } else { Visible::Yes } } - pub(crate) fn is_doc_hidden(&self, attrs: &hir::Attrs, defining_crate: hir::Crate) -> bool { + pub(crate) fn is_doc_hidden( + &self, + attrs: &hir::AttrsWithOwner, + defining_crate: hir::Crate, + ) -> bool { // `doc(hidden)` items are only completed within the defining crate. - self.krate != defining_crate && attrs.has_doc_hidden() + self.krate != defining_crate && attrs.is_doc_hidden() } pub(crate) fn doc_aliases_in_scope(&self, scope_def: ScopeDef) -> Vec { if let Some(attrs) = scope_def.attrs(self.db) { - attrs.doc_aliases().map(|it| it.as_str().into()).collect() + attrs.doc_aliases(self.db).iter().map(|it| it.as_str().into()).collect() } else { vec![] } diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs index 5fb9dc93c93d..be17dc7c1a88 100644 --- a/crates/ide-completion/src/item.rs +++ b/crates/ide-completion/src/item.rs @@ -56,7 +56,8 @@ pub struct CompletionItem { /// Additional info to show in the UI pop up. pub detail: Option, - pub documentation: Option, + // FIXME: Make this with `'db` lifetime. + pub documentation: Option>, /// Whether this item is marked as deprecated pub deprecated: bool, @@ -487,7 +488,8 @@ pub(crate) struct Builder { insert_text: Option, is_snippet: bool, detail: Option, - documentation: Option, + // FIXME: Make this with `'db` lifetime. + documentation: Option>, lookup: Option, kind: CompletionItemKind, text_edit: Option, @@ -643,11 +645,11 @@ impl Builder { self } #[allow(unused)] - pub(crate) fn documentation(&mut self, docs: Documentation) -> &mut Builder { + pub(crate) fn documentation(&mut self, docs: Documentation<'_>) -> &mut Builder { self.set_documentation(Some(docs)) } - pub(crate) fn set_documentation(&mut self, docs: Option) -> &mut Builder { - self.documentation = docs; + pub(crate) fn set_documentation(&mut self, docs: Option>) -> &mut Builder { + self.documentation = docs.map(Documentation::into_owned); self } pub(crate) fn set_deprecated(&mut self, deprecated: bool) -> &mut Builder { diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index 7d23f9d14c66..512cb45b72df 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -10,7 +10,7 @@ pub(crate) mod type_alias; pub(crate) mod union_literal; pub(crate) mod variant; -use hir::{AsAssocItem, HasAttrs, HirDisplay, ModuleDef, ScopeDef, Type, sym}; +use hir::{AsAssocItem, HasAttrs, HirDisplay, ModuleDef, ScopeDef, Type}; use ide_db::text_edit::TextEdit; use ide_db::{ RootDatabase, SnippetCap, SymbolKind, @@ -91,8 +91,7 @@ impl<'a> RenderContext<'a> { } fn is_deprecated(&self, def: impl HasAttrs) -> bool { - let attrs = def.attrs(self.db()); - attrs.by_key(sym::deprecated).exists() + def.attrs(self.db()).is_deprecated() } fn is_deprecated_assoc_item(&self, as_assoc_item: impl AsAssocItem) -> bool { @@ -115,7 +114,7 @@ impl<'a> RenderContext<'a> { } // FIXME: remove this - fn docs(&self, def: impl HasDocs) -> Option { + fn docs(&self, def: impl HasDocs) -> Option> { def.docs(self.db()) } } @@ -321,7 +320,9 @@ pub(crate) fn render_expr( ); let edit = TextEdit::replace(source_range, snippet); item.snippet_edit(ctx.config.snippet_cap?, edit); - item.documentation(Documentation::new(String::from("Autogenerated expression by term search"))); + item.documentation(Documentation::new_owned(String::from( + "Autogenerated expression by term search", + ))); item.set_relevance(crate::CompletionRelevance { type_match: compute_type_match(ctx, &expr.ty(ctx.db)), ..Default::default() @@ -554,7 +555,7 @@ fn res_to_kind(resolution: ScopeDef) -> CompletionItemKind { } } -fn scope_def_docs(db: &RootDatabase, resolution: ScopeDef) -> Option { +fn scope_def_docs(db: &RootDatabase, resolution: ScopeDef) -> Option> { use hir::ModuleDef::*; match resolution { ScopeDef::ModuleDef(Module(it)) => it.docs(db), diff --git a/crates/ide-completion/src/render/literal.rs b/crates/ide-completion/src/render/literal.rs index 6c89e49f94e8..8b14f05b72b2 100644 --- a/crates/ide-completion/src/render/literal.rs +++ b/crates/ide-completion/src/render/literal.rs @@ -180,7 +180,7 @@ impl Variant { } } - fn docs(self, db: &dyn HirDatabase) -> Option { + fn docs(self, db: &dyn HirDatabase) -> Option> { match self { Variant::Struct(it) => it.docs(db), Variant::EnumVariant(it) => it.docs(db), diff --git a/crates/ide-completion/src/render/pattern.rs b/crates/ide-completion/src/render/pattern.rs index 60ec1128233e..63a3d2d01e82 100644 --- a/crates/ide-completion/src/render/pattern.rs +++ b/crates/ide-completion/src/render/pattern.rs @@ -108,7 +108,7 @@ fn build_completion( label: SmolStr, lookup: SmolStr, pat: String, - def: impl HasDocs + Copy, + def: impl HasDocs, adt_ty: hir::Type<'_>, // Missing in context of match statement completions is_variant_missing: bool, diff --git a/crates/ide-completion/src/render/variant.rs b/crates/ide-completion/src/render/variant.rs index 42324b4290a7..967b9091b5ca 100644 --- a/crates/ide-completion/src/render/variant.rs +++ b/crates/ide-completion/src/render/variant.rs @@ -1,7 +1,7 @@ //! Code common to structs, unions, and enum variants. use crate::context::CompletionContext; -use hir::{HasAttrs, HasCrate, HasVisibility, HirDisplay, StructKind, sym}; +use hir::{HasAttrs, HasCrate, HasVisibility, HirDisplay, StructKind}; use ide_db::SnippetCap; use itertools::Itertools; use syntax::SmolStr; @@ -96,8 +96,8 @@ pub(crate) fn visible_fields( .copied() .collect::>(); let has_invisible_field = n_fields - fields.len() > 0; - let is_foreign_non_exhaustive = item.attrs(ctx.db).by_key(sym::non_exhaustive).exists() - && item.krate(ctx.db) != module.krate(); + let is_foreign_non_exhaustive = + item.attrs(ctx.db).is_non_exhaustive() && item.krate(ctx.db) != module.krate(); let fields_omitted = has_invisible_field || is_foreign_non_exhaustive; Some((fields, fields_omitted)) } diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs index 4b3b271ca20e..3b01c7dcc919 100644 --- a/crates/ide-completion/src/tests.rs +++ b/crates/ide-completion/src/tests.rs @@ -158,12 +158,12 @@ pub(crate) fn position( #[rust_analyzer::rust_fixture] ra_fixture: &str, ) -> (RootDatabase, FilePosition) { let mut database = RootDatabase::default(); - let change_fixture = ChangeFixture::parse(&database, ra_fixture); + let change_fixture = ChangeFixture::parse(ra_fixture); database.enable_proc_attr_macros(); database.apply_change(change_fixture.change); let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); let offset = range_or_offset.expect_offset(); - let position = FilePosition { file_id: file_id.file_id(&database), offset }; + let position = FilePosition { file_id: file_id.file_id(), offset }; (database, position) } diff --git a/crates/ide-db/src/defs.rs b/crates/ide-db/src/defs.rs index 61a21ccf2f77..002d4aaba94f 100644 --- a/crates/ide-db/src/defs.rs +++ b/crates/ide-db/src/defs.rs @@ -5,8 +5,10 @@ // FIXME: this badly needs rename/rewrite (matklad, 2020-02-06). +use std::borrow::Cow; + use crate::RootDatabase; -use crate::documentation::{DocsRangeMap, Documentation, HasDocs}; +use crate::documentation::{Documentation, HasDocs}; use crate::famous_defs::FamousDefs; use arrayvec::ArrayVec; use either::Either; @@ -21,7 +23,7 @@ use hir::{ use span::Edition; use stdx::{format_to, impl_from}; use syntax::{ - SyntaxKind, SyntaxNode, SyntaxToken, TextSize, + SyntaxKind, SyntaxNode, SyntaxToken, ast::{self, AstNode}, match_ast, }; @@ -199,21 +201,25 @@ impl Definition { Some(name) } - pub fn docs( + pub fn docs<'db>( &self, - db: &RootDatabase, + db: &'db RootDatabase, famous_defs: Option<&FamousDefs<'_, '_>>, display_target: DisplayTarget, - ) -> Option { - self.docs_with_rangemap(db, famous_defs, display_target).map(|(docs, _)| docs) + ) -> Option> { + self.docs_with_rangemap(db, famous_defs, display_target).map(|docs| match docs { + Either::Left(Cow::Borrowed(docs)) => Documentation::new_borrowed(docs.docs()), + Either::Left(Cow::Owned(docs)) => Documentation::new_owned(docs.into_docs()), + Either::Right(docs) => docs, + }) } - pub fn docs_with_rangemap( + pub fn docs_with_rangemap<'db>( &self, - db: &RootDatabase, + db: &'db RootDatabase, famous_defs: Option<&FamousDefs<'_, '_>>, display_target: DisplayTarget, - ) -> Option<(Documentation, Option)> { + ) -> Option, Documentation<'db>>> { let docs = match self { Definition::Macro(it) => it.docs_with_rangemap(db), Definition::Field(it) => it.docs_with_rangemap(db), @@ -229,15 +235,13 @@ impl Definition { it.docs_with_rangemap(db).or_else(|| { // docs are missing, try to fall back to the docs of the aliased item. let adt = it.ty(db).as_adt()?; - let (docs, range_map) = adt.docs_with_rangemap(db)?; + let mut docs = adt.docs_with_rangemap(db)?.into_owned(); let header_docs = format!( "*This is the documentation for* `{}`\n\n", adt.display(db, display_target) ); - let offset = TextSize::new(header_docs.len() as u32); - let range_map = range_map.shift_docstring_line_range(offset); - let docs = header_docs + docs.as_str(); - Some((Documentation::new(docs), range_map)) + docs.prepend_str(&header_docs); + Some(Cow::Owned(docs)) }) } Definition::BuiltinType(it) => { @@ -246,7 +250,7 @@ impl Definition { let primitive_mod = format!("prim_{}", it.name().display(fd.0.db, display_target.edition)); let doc_owner = find_std_module(fd, &primitive_mod, display_target.edition)?; - doc_owner.docs_with_rangemap(fd.0.db) + doc_owner.docs_with_rangemap(db) }) } Definition::BuiltinLifetime(StaticLifetime) => None, @@ -282,7 +286,7 @@ impl Definition { ); } - return Some((Documentation::new(docs.replace('*', "\\*")), None)); + return Some(Either::Right(Documentation::new_owned(docs.replace('*', "\\*")))); } Definition::ToolModule(_) => None, Definition::DeriveHelper(_) => None, @@ -299,7 +303,7 @@ impl Definition { let item = trait_.items(db).into_iter().find(|it| it.name(db) == name)?; item.docs_with_rangemap(db) }) - .map(|(docs, range_map)| (docs, Some(range_map))) + .map(Either::Left) } pub fn label(&self, db: &RootDatabase, display_target: DisplayTarget) -> String { diff --git a/crates/ide-db/src/documentation.rs b/crates/ide-db/src/documentation.rs index cab19aadfd01..4c4691cca2ca 100644 --- a/crates/ide-db/src/documentation.rs +++ b/crates/ide-db/src/documentation.rs @@ -1,337 +1,100 @@ //! Documentation attribute related utilities. -use either::Either; -use hir::{ - AttrId, AttrSourceMap, AttrsWithOwner, HasAttrs, InFile, - db::{DefDatabase, HirDatabase}, - resolve_doc_path_on, sym, -}; -use itertools::Itertools; -use span::{TextRange, TextSize}; -use syntax::{ - AstToken, - ast::{self, IsString}, -}; +use std::borrow::Cow; + +use hir::{HasAttrs, db::HirDatabase, resolve_doc_path_on}; /// Holds documentation #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Documentation(String); +pub struct Documentation<'db>(Cow<'db, str>); + +impl<'db> Documentation<'db> { + #[inline] + pub fn new_owned(s: String) -> Self { + Documentation(Cow::Owned(s)) + } -impl Documentation { - pub fn new(s: String) -> Self { - Documentation(s) + #[inline] + pub fn new_borrowed(s: &'db str) -> Self { + Documentation(Cow::Borrowed(s)) } + #[inline] + pub fn into_owned(self) -> Documentation<'static> { + Documentation::new_owned(self.0.into_owned()) + } + + #[inline] pub fn as_str(&self) -> &str { &self.0 } } -impl From for String { - fn from(Documentation(string): Documentation) -> Self { - string +pub trait HasDocs: HasAttrs + Copy { + fn docs(self, db: &dyn HirDatabase) -> Option> { + let docs = match self.docs_with_rangemap(db)? { + Cow::Borrowed(docs) => Documentation::new_borrowed(docs.docs()), + Cow::Owned(docs) => Documentation::new_owned(docs.into_docs()), + }; + Some(docs) + } + fn docs_with_rangemap(self, db: &dyn HirDatabase) -> Option> { + self.hir_docs(db).map(Cow::Borrowed) } -} - -pub trait HasDocs: HasAttrs { - fn docs(self, db: &dyn HirDatabase) -> Option; - fn docs_with_rangemap(self, db: &dyn HirDatabase) -> Option<(Documentation, DocsRangeMap)>; fn resolve_doc_path( self, db: &dyn HirDatabase, link: &str, ns: Option, - is_inner_doc: bool, - ) -> Option; -} -/// 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, AttrId)> { - 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 }, idx)) - } - 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 }, idx)) - } - } - } - - pub fn shift_docstring_line_range(self, offset: TextSize) -> DocsRangeMap { - let mapping = self - .mapping - .into_iter() - .map(|(buf_offset, id, base_offset)| { - let buf_offset = buf_offset.checked_add(offset).unwrap(); - (buf_offset, id, base_offset) - }) - .collect_vec(); - DocsRangeMap { source_map: self.source_map, mapping } - } -} - -pub fn docs_with_rangemap( - db: &dyn DefDatabase, - attrs: &AttrsWithOwner, -) -> Option<(Documentation, DocsRangeMap)> { - let docs = attrs - .by_key(sym::doc) - .attrs() - .filter_map(|attr| attr.string_value_unescape().map(|s| (s, attr.id))); - let indent = doc_indent(attrs); - 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: attrs.source_map(db) })) - } -} - -pub fn docs_from_attrs(attrs: &hir::Attrs) -> Option { - let docs = attrs.by_key(sym::doc).attrs().filter_map(|attr| attr.string_value_unescape()); - let indent = doc_indent(attrs); - let mut buf = String::new(); - for doc in docs { - // str::lines doesn't yield anything for the empty string - if !doc.is_empty() { - // We don't trim trailing whitespace from doc comments as multiple trailing spaces - // indicates a hard line break in Markdown. - let lines = doc.lines().map(|line| { - line.char_indices().nth(indent).map_or(line, |(offset, _)| &line[offset..]) - }); - - buf.extend(Itertools::intersperse(lines, "\n")); - } - buf.push('\n'); + is_inner_doc: hir::IsInnerDoc, + ) -> Option { + resolve_doc_path_on(db, self, link, ns, is_inner_doc) } - buf.pop(); - if buf.is_empty() { None } else { Some(buf) } } macro_rules! impl_has_docs { ($($def:ident,)*) => {$( - impl HasDocs for hir::$def { - fn docs(self, db: &dyn HirDatabase) -> Option { - docs_from_attrs(&self.attrs(db)).map(Documentation) - } - fn docs_with_rangemap( - self, - db: &dyn HirDatabase, - ) -> Option<(Documentation, DocsRangeMap)> { - docs_with_rangemap(db, &self.attrs(db)) - } - fn resolve_doc_path( - self, - db: &dyn HirDatabase, - link: &str, - ns: Option, - is_inner_doc: bool, - ) -> Option { - resolve_doc_path_on(db, self, link, ns, is_inner_doc) - } - } + impl HasDocs for hir::$def {} )*}; } impl_has_docs![ Variant, Field, Static, Const, Trait, TypeAlias, Macro, Function, Adt, Module, Impl, Crate, + AssocItem, Struct, Union, Enum, ]; -macro_rules! impl_has_docs_enum { - ($($variant:ident),* for $enum:ident) => {$( - impl HasDocs for hir::$variant { - fn docs(self, db: &dyn HirDatabase) -> Option { - hir::$enum::$variant(self).docs(db) - } - - fn docs_with_rangemap( - self, - db: &dyn HirDatabase, - ) -> Option<(Documentation, DocsRangeMap)> { - hir::$enum::$variant(self).docs_with_rangemap(db) - } - fn resolve_doc_path( - self, - db: &dyn HirDatabase, - link: &str, - ns: Option, - is_inner_doc: bool, - ) -> Option { - hir::$enum::$variant(self).resolve_doc_path(db, link, ns, is_inner_doc) - } - } - )*}; -} - -impl_has_docs_enum![Struct, Union, Enum for Adt]; - -impl HasDocs for hir::AssocItem { - fn docs(self, db: &dyn HirDatabase) -> Option { - match self { - hir::AssocItem::Function(it) => it.docs(db), - hir::AssocItem::Const(it) => it.docs(db), - hir::AssocItem::TypeAlias(it) => it.docs(db), - } - } - - fn docs_with_rangemap(self, db: &dyn HirDatabase) -> Option<(Documentation, DocsRangeMap)> { - match self { - hir::AssocItem::Function(it) => it.docs_with_rangemap(db), - hir::AssocItem::Const(it) => it.docs_with_rangemap(db), - hir::AssocItem::TypeAlias(it) => it.docs_with_rangemap(db), - } - } - - fn resolve_doc_path( - self, - db: &dyn HirDatabase, - link: &str, - ns: Option, - is_inner_doc: bool, - ) -> Option { - match self { - hir::AssocItem::Function(it) => it.resolve_doc_path(db, link, ns, is_inner_doc), - hir::AssocItem::Const(it) => it.resolve_doc_path(db, link, ns, is_inner_doc), - hir::AssocItem::TypeAlias(it) => it.resolve_doc_path(db, link, ns, is_inner_doc), - } - } -} - impl HasDocs for hir::ExternCrateDecl { - fn docs(self, db: &dyn HirDatabase) -> Option { - let crate_docs = docs_from_attrs(&self.resolved_crate(db)?.root_module().attrs(db)); - let decl_docs = docs_from_attrs(&self.attrs(db)); + fn docs(self, db: &dyn HirDatabase) -> Option> { + let crate_docs = self.resolved_crate(db)?.hir_docs(db); + let decl_docs = self.hir_docs(db); match (decl_docs, crate_docs) { (None, None) => None, - (Some(decl_docs), None) => Some(decl_docs), - (None, Some(crate_docs)) => Some(crate_docs), - (Some(mut decl_docs), Some(crate_docs)) => { - decl_docs.push('\n'); - decl_docs.push('\n'); - decl_docs += &crate_docs; - Some(decl_docs) + (Some(docs), None) | (None, Some(docs)) => { + Some(Documentation::new_borrowed(docs.docs())) + } + (Some(decl_docs), Some(crate_docs)) => { + let mut docs = String::with_capacity( + decl_docs.docs().len() + "\n\n".len() + crate_docs.docs().len(), + ); + docs.push_str(decl_docs.docs()); + docs.push_str("\n\n"); + docs.push_str(crate_docs.docs()); + Some(Documentation::new_owned(docs)) } } - .map(Documentation::new) } - fn docs_with_rangemap(self, db: &dyn HirDatabase) -> Option<(Documentation, DocsRangeMap)> { - let crate_docs = docs_with_rangemap(db, &self.resolved_crate(db)?.root_module().attrs(db)); - let decl_docs = docs_with_rangemap(db, &self.attrs(db)); + fn docs_with_rangemap(self, db: &dyn HirDatabase) -> Option> { + let crate_docs = self.resolved_crate(db)?.hir_docs(db); + let decl_docs = self.hir_docs(db); match (decl_docs, crate_docs) { (None, None) => None, - (Some(decl_docs), None) => Some(decl_docs), - (None, Some(crate_docs)) => Some(crate_docs), - ( - Some((Documentation(mut decl_docs), mut decl_range_map)), - Some((Documentation(crate_docs), crate_range_map)), - ) => { - decl_docs.push('\n'); - decl_docs.push('\n'); - let offset = TextSize::new(decl_docs.len() as u32); - decl_docs += &crate_docs; - let crate_range_map = crate_range_map.shift_docstring_line_range(offset); - decl_range_map.mapping.extend(crate_range_map.mapping); - Some((Documentation(decl_docs), decl_range_map)) + (Some(docs), None) | (None, Some(docs)) => Some(Cow::Borrowed(docs)), + (Some(decl_docs), Some(crate_docs)) => { + let mut docs = decl_docs.clone(); + docs.append_str("\n\n"); + docs.append(crate_docs); + Some(Cow::Owned(docs)) } } } - fn resolve_doc_path( - self, - db: &dyn HirDatabase, - link: &str, - ns: Option, - is_inner_doc: bool, - ) -> Option { - resolve_doc_path_on(db, self, link, ns, is_inner_doc) - } -} - -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, - } -} - -fn doc_indent(attrs: &hir::Attrs) -> usize { - let mut min = !0; - for val in attrs.by_key(sym::doc).attrs().filter_map(|attr| attr.string_value_unescape()) { - if let Some(m) = - val.lines().filter_map(|line| line.chars().position(|c| !c.is_whitespace())).min() - { - min = min.min(m); - } - } - min } diff --git a/crates/ide-db/src/rust_doc.rs b/crates/ide-db/src/rust_doc.rs index eacd9b9b4d2f..36a6938af6b8 100644 --- a/crates/ide-db/src/rust_doc.rs +++ b/crates/ide-db/src/rust_doc.rs @@ -33,7 +33,7 @@ pub fn is_rust_fence(s: &str) -> bool { const RUSTDOC_FENCES: [&str; 2] = ["```", "~~~"]; -pub fn format_docs(src: &Documentation) -> String { +pub fn format_docs(src: &Documentation<'_>) -> String { format_docs_(src.as_str()) } diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs index f1d076e874d5..8b53cea7e6d3 100644 --- a/crates/ide-db/src/search.rs +++ b/crates/ide-db/src/search.rs @@ -12,7 +12,7 @@ use either::Either; use hir::{ Adt, AsAssocItem, DefWithBody, EditionedFileId, FileRange, FileRangeWrapper, HasAttrs, HasContainer, HasSource, InFile, InFileWrapper, InRealFile, InlineAsmOperand, ItemContainer, - ModuleSource, PathResolution, Semantics, Visibility, sym, + ModuleSource, PathResolution, Semantics, Visibility, }; use memchr::memmem::Finder; use parser::SyntaxKind; @@ -169,7 +169,7 @@ impl SearchScope { entries.extend( source_root .iter() - .map(|id| (EditionedFileId::new(db, id, crate_data.edition), None)), + .map(|id| (EditionedFileId::new(db, id, crate_data.edition, krate), None)), ); } SearchScope { entries } @@ -183,11 +183,9 @@ impl SearchScope { let source_root = db.file_source_root(root_file).source_root_id(db); let source_root = db.source_root(source_root).source_root(db); - entries.extend( - source_root - .iter() - .map(|id| (EditionedFileId::new(db, id, rev_dep.edition(db)), None)), - ); + entries.extend(source_root.iter().map(|id| { + (EditionedFileId::new(db, id, rev_dep.edition(db), rev_dep.into()), None) + })); } SearchScope { entries } } @@ -201,7 +199,7 @@ impl SearchScope { SearchScope { entries: source_root .iter() - .map(|id| (EditionedFileId::new(db, id, of.edition(db)), None)) + .map(|id| (EditionedFileId::new(db, id, of.edition(db), of.into()), None)) .collect(), } } @@ -368,7 +366,7 @@ impl Definition { if let Definition::Macro(macro_def) = self { return match macro_def.kind(db) { hir::MacroKind::Declarative => { - if macro_def.attrs(db).by_key(sym::macro_export).exists() { + if macro_def.attrs(db).is_macro_export() { SearchScope::reverse_dependencies(db, module.krate()) } else { SearchScope::krate(db, module.krate()) diff --git a/crates/ide-db/src/test_data/test_doc_alias.txt b/crates/ide-db/src/test_data/test_doc_alias.txt index 30d1df4f8e55..427a51055948 100644 --- a/crates/ide-db/src/test_data/test_doc_alias.txt +++ b/crates/ide-db/src/test_data/test_doc_alias.txt @@ -3,7 +3,7 @@ Module { id: ModuleId { krate: Crate( - Id(3000), + Id(2c00), ), block: None, local_id: Idx::(0), @@ -16,7 +16,7 @@ Struct( Struct { id: StructId( - 3401, + 3801, ), }, ), @@ -24,7 +24,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -50,7 +50,7 @@ Struct( Struct { id: StructId( - 3400, + 3800, ), }, ), @@ -58,7 +58,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -84,7 +84,7 @@ Struct( Struct { id: StructId( - 3400, + 3800, ), }, ), @@ -92,7 +92,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -118,7 +118,7 @@ Struct( Struct { id: StructId( - 3400, + 3800, ), }, ), @@ -126,7 +126,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -152,7 +152,7 @@ Struct( Struct { id: StructId( - 3400, + 3800, ), }, ), @@ -160,7 +160,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -186,7 +186,7 @@ Struct( Struct { id: StructId( - 3401, + 3801, ), }, ), @@ -194,7 +194,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -220,7 +220,7 @@ Struct( Struct { id: StructId( - 3400, + 3800, ), }, ), @@ -228,7 +228,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { diff --git a/crates/ide-db/src/test_data/test_symbol_index_collection.txt b/crates/ide-db/src/test_data/test_symbol_index_collection.txt index 973256c470f3..ce93fa59e258 100644 --- a/crates/ide-db/src/test_data/test_symbol_index_collection.txt +++ b/crates/ide-db/src/test_data/test_symbol_index_collection.txt @@ -3,7 +3,7 @@ Module { id: ModuleId { krate: Crate( - Id(3000), + Id(2c00), ), block: None, local_id: Idx::(0), @@ -22,7 +22,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -49,14 +49,14 @@ def: TypeAlias( TypeAlias { id: TypeAliasId( - 6800, + 6c00, ), }, ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -88,7 +88,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -115,14 +115,14 @@ def: Const( Const { id: ConstId( - 6000, + 6400, ), }, ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -147,14 +147,14 @@ def: Const( Const { id: ConstId( - 6002, + 6402, ), }, ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -180,7 +180,7 @@ Enum( Enum { id: EnumId( - 4c00, + 5000, ), }, ), @@ -188,7 +188,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -214,7 +214,7 @@ Macro { id: Macro2Id( Macro2Id( - 4800, + 4c00, ), ), }, @@ -222,7 +222,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -248,7 +248,7 @@ Macro { id: Macro2Id( Macro2Id( - 4800, + 4c00, ), ), }, @@ -256,7 +256,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -281,14 +281,14 @@ def: Static( Static { id: StaticId( - 6400, + 6800, ), }, ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -314,7 +314,7 @@ Struct( Struct { id: StructId( - 4401, + 4801, ), }, ), @@ -322,7 +322,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -348,7 +348,7 @@ Struct( Struct { id: StructId( - 4400, + 4800, ), }, ), @@ -356,7 +356,7 @@ loc: DeclarationLocation { hir_file_id: MacroFile( MacroCallId( - Id(3800), + Id(3c00), ), ), ptr: SyntaxNodePtr { @@ -382,7 +382,7 @@ Struct( Struct { id: StructId( - 4405, + 4805, ), }, ), @@ -390,7 +390,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -418,7 +418,7 @@ Struct( Struct { id: StructId( - 4406, + 4806, ), }, ), @@ -426,7 +426,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -454,7 +454,7 @@ Struct( Struct { id: StructId( - 4407, + 4807, ), }, ), @@ -462,7 +462,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -488,7 +488,7 @@ Struct( Struct { id: StructId( - 4402, + 4802, ), }, ), @@ -496,7 +496,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -521,14 +521,14 @@ def: Trait( Trait { id: TraitId( - 5800, + 5c00, ), }, ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -554,7 +554,7 @@ Macro { id: Macro2Id( Macro2Id( - 4800, + 4c00, ), ), }, @@ -562,7 +562,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -588,7 +588,7 @@ Union( Union { id: UnionId( - 5000, + 5400, ), }, ), @@ -596,7 +596,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -622,7 +622,7 @@ Module { id: ModuleId { krate: Crate( - Id(3000), + Id(2c00), ), block: None, local_id: Idx::(1), @@ -632,7 +632,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -658,7 +658,7 @@ Module { id: ModuleId { krate: Crate( - Id(3000), + Id(2c00), ), block: None, local_id: Idx::(2), @@ -668,7 +668,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -694,7 +694,7 @@ Macro { id: MacroRulesId( MacroRulesId( - 3401, + 3801, ), ), }, @@ -702,7 +702,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -727,14 +727,14 @@ def: Function( Function { id: FunctionId( - 5c02, + 6002, ), }, ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -761,14 +761,14 @@ def: Function( Function { id: FunctionId( - 5c01, + 6001, ), }, ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -796,7 +796,7 @@ Macro { id: MacroRulesId( MacroRulesId( - 3400, + 3800, ), ), }, @@ -804,7 +804,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -829,14 +829,14 @@ def: Function( Function { id: FunctionId( - 5c00, + 6000, ), }, ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -862,7 +862,7 @@ Macro { id: MacroRulesId( MacroRulesId( - 3401, + 3801, ), ), }, @@ -870,7 +870,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -895,14 +895,14 @@ def: Function( Function { id: FunctionId( - 5c03, + 6003, ), }, ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -930,7 +930,7 @@ Module { id: ModuleId { krate: Crate( - Id(3000), + Id(2c00), ), block: None, local_id: Idx::(1), @@ -943,7 +943,7 @@ Struct( Struct { id: StructId( - 4403, + 4803, ), }, ), @@ -951,7 +951,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { @@ -977,7 +977,7 @@ Module { id: ModuleId { krate: Crate( - Id(3000), + Id(2c00), ), block: None, local_id: Idx::(2), @@ -989,14 +989,14 @@ def: Trait( Trait { id: TraitId( - 5800, + 5c00, ), }, ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2001), + Id(3001), ), ), ptr: SyntaxNodePtr { @@ -1022,7 +1022,7 @@ Macro { id: Macro2Id( Macro2Id( - 4800, + 4c00, ), ), }, @@ -1030,7 +1030,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2001), + Id(3001), ), ), ptr: SyntaxNodePtr { @@ -1056,7 +1056,7 @@ Struct( Struct { id: StructId( - 4404, + 4804, ), }, ), @@ -1064,7 +1064,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2001), + Id(3001), ), ), ptr: SyntaxNodePtr { @@ -1090,7 +1090,7 @@ Macro { id: Macro2Id( Macro2Id( - 4800, + 4c00, ), ), }, @@ -1098,7 +1098,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2001), + Id(3001), ), ), ptr: SyntaxNodePtr { @@ -1124,7 +1124,7 @@ Struct( Struct { id: StructId( - 4404, + 4804, ), }, ), @@ -1132,7 +1132,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2001), + Id(3001), ), ), ptr: SyntaxNodePtr { diff --git a/crates/ide-db/src/test_data/test_symbols_exclude_imports.txt b/crates/ide-db/src/test_data/test_symbols_exclude_imports.txt index 22872b577f71..3ab837aa613f 100644 --- a/crates/ide-db/src/test_data/test_symbols_exclude_imports.txt +++ b/crates/ide-db/src/test_data/test_symbols_exclude_imports.txt @@ -13,7 +13,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2001), + Id(3001), ), ), ptr: SyntaxNodePtr { diff --git a/crates/ide-db/src/test_data/test_symbols_with_imports.txt b/crates/ide-db/src/test_data/test_symbols_with_imports.txt index 9f98bf87e2e8..a6a808d616a7 100644 --- a/crates/ide-db/src/test_data/test_symbols_with_imports.txt +++ b/crates/ide-db/src/test_data/test_symbols_with_imports.txt @@ -13,7 +13,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2001), + Id(3001), ), ), ptr: SyntaxNodePtr { @@ -47,7 +47,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(2000), + Id(3000), ), ), ptr: SyntaxNodePtr { diff --git a/crates/ide-db/src/traits.rs b/crates/ide-db/src/traits.rs index 61e28386d072..7b9fdb1e1cf3 100644 --- a/crates/ide-db/src/traits.rs +++ b/crates/ide-db/src/traits.rs @@ -114,8 +114,7 @@ fn assoc_item_of_trait( #[cfg(test)] mod tests { use expect_test::{Expect, expect}; - use hir::FilePosition; - use hir::Semantics; + use hir::{EditionedFileId, FilePosition, Semantics}; use span::Edition; use syntax::ast::{self, AstNode}; use test_fixture::ChangeFixture; @@ -127,10 +126,11 @@ mod tests { #[rust_analyzer::rust_fixture] ra_fixture: &str, ) -> (RootDatabase, FilePosition) { let mut database = RootDatabase::default(); - let change_fixture = ChangeFixture::parse(&database, ra_fixture); + let change_fixture = ChangeFixture::parse(ra_fixture); database.apply_change(change_fixture.change); let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); + let file_id = EditionedFileId::from_span_guess_origin(&database, file_id); let offset = range_or_offset.expect_offset(); (database, FilePosition { file_id, offset }) } diff --git a/crates/ide-diagnostics/src/handlers/inactive_code.rs b/crates/ide-diagnostics/src/handlers/inactive_code.rs index 8611ef653b02..dfa9639f6eb9 100644 --- a/crates/ide-diagnostics/src/handlers/inactive_code.rs +++ b/crates/ide-diagnostics/src/handlers/inactive_code.rs @@ -95,7 +95,7 @@ fn f() { //^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: no is disabled #[cfg(no)] #[cfg(no2)] mod m; - //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: no and no2 are disabled + //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: no is disabled #[cfg(all(not(a), b))] enum E {} //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: b is disabled @@ -130,7 +130,6 @@ trait Bar { /// Tests that `cfg` attributes behind `cfg_attr` is handled properly. #[test] fn inactive_via_cfg_attr() { - cov_mark::check!(cfg_attr_active); check( r#" #[cfg_attr(not(never), cfg(no))] fn f() {} diff --git a/crates/ide-diagnostics/src/handlers/invalid_derive_target.rs b/crates/ide-diagnostics/src/handlers/invalid_derive_target.rs index 8b708f229d00..9aa7aed16964 100644 --- a/crates/ide-diagnostics/src/handlers/invalid_derive_target.rs +++ b/crates/ide-diagnostics/src/handlers/invalid_derive_target.rs @@ -8,7 +8,7 @@ pub(crate) fn invalid_derive_target( ctx: &DiagnosticsContext<'_>, d: &hir::InvalidDeriveTarget, ) -> Diagnostic { - let display_range = ctx.sema.diagnostics_display_range(d.node); + let display_range = ctx.sema.diagnostics_display_range_for_range(d.range); Diagnostic::new( DiagnosticCode::RustcHardError("E0774"), @@ -29,7 +29,7 @@ mod tests { //- minicore:derive mod __ { #[derive()] - //^^^^^^^^^^^ error: `derive` may only be applied to `struct`s, `enum`s and `union`s + // ^^^^^^ error: `derive` may only be applied to `struct`s, `enum`s and `union`s fn main() {} } "#, diff --git a/crates/ide-diagnostics/src/handlers/macro_error.rs b/crates/ide-diagnostics/src/handlers/macro_error.rs index c39e00e178f8..74e65bc19f69 100644 --- a/crates/ide-diagnostics/src/handlers/macro_error.rs +++ b/crates/ide-diagnostics/src/handlers/macro_error.rs @@ -13,7 +13,7 @@ use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, Severity}; // This diagnostic is shown for proc macros that have been specifically disabled via `rust-analyzer.procMacro.ignored`. pub(crate) fn macro_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroError) -> Diagnostic { // Use more accurate position if available. - let display_range = ctx.resolve_precise_location(&d.node, d.precise_location); + let display_range = ctx.sema.diagnostics_display_range_for_range(d.range); Diagnostic::new( DiagnosticCode::Ra(d.kind, if d.error { Severity::Error } else { Severity::WeakWarning }), d.message.clone(), @@ -27,8 +27,10 @@ pub(crate) fn macro_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroError) -> // This diagnostic is shown for macro expansion errors. pub(crate) fn macro_def_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroDefError) -> Diagnostic { // Use more accurate position if available. - let display_range = - ctx.resolve_precise_location(&d.node.map(|it| it.syntax_node_ptr()), d.name); + let display_range = match d.name { + Some(name) => ctx.sema.diagnostics_display_range_for_range(d.node.with_value(name)), + None => ctx.sema.diagnostics_display_range(d.node.map(|it| it.syntax_node_ptr())), + }; Diagnostic::new( DiagnosticCode::Ra("macro-def-error", Severity::Error), d.message.clone(), @@ -135,10 +137,12 @@ macro_rules! env { () => {} } #[rustc_builtin_macro] macro_rules! concat { () => {} } - include!(concat!(env!("OUT_DIR"), "/out.rs")); - //^^^^^^^^^ error: `OUT_DIR` not set, build scripts may have failed to run - //^^^^^^^^^^^^^^^ error: `OUT_DIR` not set, build scripts may have failed to run - //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `OUT_DIR` not set, build scripts may have failed to run + include!(concat!( + // ^^^^^^ error: `OUT_DIR` not set, build scripts may have failed to run + env!( + //^^^ error: `OUT_DIR` not set, build scripts may have failed to run + "OUT_DIR"), "/out.rs")); + //^^^^^^^^^ error: `OUT_DIR` not set, build scripts may have failed to run "#, ); } @@ -185,7 +189,7 @@ fn main() { //^^^^^^^^^^^^^^^^ error: failed to load file `does not exist` include!(concat!("does ", "not ", "exist")); - //^^^^^^^^^^^^^^^^^^^^^^^^^^ error: failed to load file `does not exist` + // ^^^^^^^^^^^^^^^^^^^^^^^^ error: failed to load file `does not exist` env!(invalid); //^^^^^^^ error: expected string literal @@ -292,7 +296,7 @@ include!("include-me.rs"); //- /include-me.rs /// long doc that pushes the diagnostic range beyond the first file's text length #[err] -//^^^^^^error: unresolved macro `err` + // ^^^ error: unresolved macro `err` mod prim_never {} "#, ); diff --git a/crates/ide-diagnostics/src/handlers/malformed_derive.rs b/crates/ide-diagnostics/src/handlers/malformed_derive.rs index 701b30b9b593..7d0c71f4fa7c 100644 --- a/crates/ide-diagnostics/src/handlers/malformed_derive.rs +++ b/crates/ide-diagnostics/src/handlers/malformed_derive.rs @@ -7,7 +7,7 @@ pub(crate) fn malformed_derive( ctx: &DiagnosticsContext<'_>, d: &hir::MalformedDerive, ) -> Diagnostic { - let display_range = ctx.sema.diagnostics_display_range(d.node); + let display_range = ctx.sema.diagnostics_display_range_for_range(d.range); Diagnostic::new( DiagnosticCode::RustcHardError("E0777"), @@ -28,7 +28,7 @@ mod tests { //- minicore:derive mod __ { #[derive = "aaaa"] - //^^^^^^^^^^^^^^^^^^ error: malformed derive input, derive attributes are of the form `#[derive(Derive1, Derive2, ...)]` + // ^^^^^^^^^^^^^^^ error: malformed derive input, derive attributes are of the form `#[derive(Derive1, Derive2, ...)]` struct Foo; } "#, diff --git a/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs b/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs index a87b8c42ac1d..030c82ca0ba7 100644 --- a/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs +++ b/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs @@ -8,8 +8,7 @@ pub(crate) fn unresolved_macro_call( ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMacroCall, ) -> Diagnostic { - // Use more accurate position if available. - let display_range = ctx.resolve_precise_location(&d.macro_call, d.precise_location); + let display_range = ctx.sema.diagnostics_display_range_for_range(d.range); let bang = if d.is_bang { "!" } else { "" }; Diagnostic::new( DiagnosticCode::RustcHardError("unresolved-macro-call"), @@ -76,7 +75,7 @@ self::m!(); self::m2!(); r#" mod _test_inner { #![empty_attr] - //^^^^^^^^^^^^^^ error: unresolved macro `empty_attr` + // ^^^^^^^^^^ error: unresolved macro `empty_attr` } "#, ); diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index a4eb3d47d708..cde0072288f0 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -102,7 +102,7 @@ use ide_db::{ use itertools::Itertools; use syntax::{ AstPtr, Edition, NodeOrToken, SmolStr, SyntaxKind, SyntaxNode, SyntaxNodePtr, T, TextRange, - ast::{self, AstNode, HasAttrs}, + ast::{self, AstNode}, }; // FIXME: Make this an enum @@ -277,31 +277,6 @@ struct DiagnosticsContext<'a> { is_nightly: bool, } -impl DiagnosticsContext<'_> { - fn resolve_precise_location( - &self, - node: &InFile, - precise_location: Option, - ) -> FileRange { - let sema = &self.sema; - (|| { - let precise_location = precise_location?; - let root = sema.parse_or_expand(node.file_id); - match root.covering_element(precise_location) { - syntax::NodeOrToken::Node(it) => Some(sema.original_range(&it)), - syntax::NodeOrToken::Token(it) => { - node.with_value(it).original_file_range_opt(sema.db) - } - } - })() - .map(|frange| ide_db::FileRange { - file_id: frange.file_id.file_id(self.sema.db), - range: frange.range, - }) - .unwrap_or_else(|| sema.diagnostics_display_range(*node)) - } -} - /// Request parser level diagnostics for the given [`FileId`]. pub fn syntax_diagnostics( db: &RootDatabase, @@ -317,7 +292,7 @@ pub fn syntax_diagnostics( let sema = Semantics::new(db); let editioned_file_id = sema .attach_first_edition(file_id) - .unwrap_or_else(|| EditionedFileId::current_edition(db, file_id)); + .unwrap_or_else(|| EditionedFileId::current_edition_guess_origin(db, file_id)); let (file_id, _) = editioned_file_id.unpack(db); @@ -348,7 +323,7 @@ pub fn semantic_diagnostics( let sema = Semantics::new(db); let editioned_file_id = sema .attach_first_edition(file_id) - .unwrap_or_else(|| EditionedFileId::current_edition(db, file_id)); + .unwrap_or_else(|| EditionedFileId::current_edition_guess_origin(db, file_id)); let (file_id, edition) = editioned_file_id.unpack(db); let mut res = Vec::new(); @@ -426,7 +401,7 @@ pub fn semantic_diagnostics( Diagnostic::new( DiagnosticCode::SyntaxError, format!("Syntax Error in Expansion: {err}"), - ctx.resolve_precise_location(&d.node.clone(), d.precise_location), + ctx.sema.diagnostics_display_range_for_range(d.range), ) })); continue; @@ -679,7 +654,7 @@ fn find_outline_mod_lint_severity( let lint_groups = lint_groups(&diag.code, edition); lint_attrs( sema, - ast::AnyHasAttrs::cast(module_source_file.value).expect("SourceFile always has attrs"), + &ast::AnyHasAttrs::cast(module_source_file.value).expect("SourceFile always has attrs"), edition, ) .for_each(|(lint, severity)| { @@ -700,7 +675,7 @@ fn lint_severity_at( .ancestors() .filter_map(ast::AnyHasAttrs::cast) .find_map(|ancestor| { - lint_attrs(sema, ancestor, edition) + lint_attrs(sema, &ancestor, edition) .find_map(|(lint, severity)| lint_groups.contains(&lint).then_some(severity)) }) .or_else(|| { @@ -708,13 +683,13 @@ fn lint_severity_at( }) } +// FIXME: Switch this to analysis' `expand_cfg_attr`. fn lint_attrs<'a>( sema: &'a Semantics<'a, RootDatabase>, - ancestor: ast::AnyHasAttrs, + ancestor: &'a ast::AnyHasAttrs, edition: Edition, ) -> impl Iterator + 'a { - ancestor - .attrs_including_inner() + ast::attrs_including_inner(ancestor) .filter_map(|attr| { attr.as_simple_call().and_then(|(name, value)| match &*name { "allow" | "expect" => Some(Either::Left(iter::once((Severity::Allow, value)))), diff --git a/crates/ide-ssr/src/from_comment.rs b/crates/ide-ssr/src/from_comment.rs index 181cc74a51d4..de26879c2959 100644 --- a/crates/ide-ssr/src/from_comment.rs +++ b/crates/ide-ssr/src/from_comment.rs @@ -17,7 +17,7 @@ pub fn ssr_from_comment( frange: FileRange, ) -> Option<(MatchFinder<'_>, TextRange)> { let comment = { - let file_id = EditionedFileId::current_edition(db, frange.file_id); + let file_id = EditionedFileId::current_edition_guess_origin(db, frange.file_id); let file = db.parse(file_id); file.tree().syntax().token_at_offset(frange.range.start()).find_map(ast::Comment::cast) diff --git a/crates/ide-ssr/src/lib.rs b/crates/ide-ssr/src/lib.rs index 43ad12c1f699..7b2142a9f348 100644 --- a/crates/ide-ssr/src/lib.rs +++ b/crates/ide-ssr/src/lib.rs @@ -125,9 +125,9 @@ impl<'db> MatchFinder<'db> { ) -> Result, SsrError> { restrict_ranges.retain(|range| !range.range.is_empty()); let sema = Semantics::new(db); - let file_id = sema - .attach_first_edition(lookup_context.file_id) - .unwrap_or_else(|| EditionedFileId::current_edition(db, lookup_context.file_id)); + let file_id = sema.attach_first_edition(lookup_context.file_id).unwrap_or_else(|| { + EditionedFileId::current_edition_guess_origin(db, lookup_context.file_id) + }); let resolution_scope = resolving::ResolutionScope::new( &sema, hir::FilePosition { file_id, offset: lookup_context.offset }, diff --git a/crates/ide-ssr/src/search.rs b/crates/ide-ssr/src/search.rs index 72f857ceda90..d23d22b4e898 100644 --- a/crates/ide-ssr/src/search.rs +++ b/crates/ide-ssr/src/search.rs @@ -135,11 +135,9 @@ impl<'db> MatchFinder<'db> { // seems to get put into a single source root. let mut files = Vec::new(); self.search_files_do(|file_id| { - files.push( - self.sema - .attach_first_edition(file_id) - .unwrap_or_else(|| EditionedFileId::current_edition(self.sema.db, file_id)), - ); + files.push(self.sema.attach_first_edition(file_id).unwrap_or_else(|| { + EditionedFileId::current_edition_guess_origin(self.sema.db, file_id) + })); }); SearchScope::files(&files) } diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index c197d559aa89..0ed91cf7f588 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs @@ -13,13 +13,13 @@ use stdx::format_to; use url::Url; use hir::{ - Adt, AsAssocItem, AssocItem, AssocItemContainer, AttrsWithOwner, HasAttrs, db::HirDatabase, sym, + Adt, AsAssocItem, AssocItem, AssocItemContainer, AttrsWithOwner, HasAttrs, db::HirDatabase, }; use ide_db::{ RootDatabase, base_db::{CrateOrigin, LangCrateOrigin, ReleaseChannel, RootQueryDb}, defs::{Definition, NameClass, NameRefClass}, - documentation::{DocsRangeMap, Documentation, HasDocs, docs_with_rangemap}, + documentation::{Documentation, HasDocs}, helpers::pick_best_token, }; use syntax::{ @@ -54,7 +54,7 @@ pub(crate) fn rewrite_links( db: &RootDatabase, markdown: &str, definition: Definition, - range_map: Option, + range_map: Option<&hir::Docs>, ) -> String { let mut cb = broken_link_clone_cb; let doc = Parser::new_with_broken_link_callback(markdown, MARKDOWN_OPTIONS, Some(&mut cb)) @@ -74,9 +74,9 @@ pub(crate) fn rewrite_links( TextRange::new(range.start.try_into().unwrap(), range.end.try_into().unwrap()); let is_inner_doc = range_map .as_ref() - .and_then(|range_map| range_map.map(text_range)) - .map(|(_, attr_id)| attr_id.is_inner_attr()) - .unwrap_or(false); + .and_then(|range_map| range_map.find_ast_range(text_range)) + .map(|(_, is_inner)| is_inner) + .unwrap_or(hir::IsInnerDoc::No); if let Some((target, title)) = rewrite_intra_doc_link(db, definition, target, title, is_inner_doc, link_type) { @@ -187,7 +187,7 @@ pub(crate) fn external_docs( /// Extracts all links from a given markdown text returning the definition text range, link-text /// and the namespace if known. pub(crate) fn extract_definitions_from_docs( - docs: &Documentation, + docs: &Documentation<'_>, ) -> Vec<(TextRange, String, Option)> { Parser::new_with_broken_link_callback( docs.as_str(), @@ -214,7 +214,7 @@ pub(crate) fn resolve_doc_path_for_def( def: Definition, link: &str, ns: Option, - is_inner_doc: bool, + is_inner_doc: hir::IsInnerDoc, ) -> Option { match def { Definition::Module(it) => it.resolve_doc_path(db, link, ns, is_inner_doc), @@ -324,11 +324,11 @@ impl DocCommentToken { let token_start = t.text_range().start(); let abs_in_expansion_offset = token_start + relative_comment_offset + descended_prefix_len; let (attributes, def) = Self::doc_attributes(sema, &node, is_inner)?; - let (docs, doc_mapping) = docs_with_rangemap(sema.db, &attributes)?; + let doc_mapping = attributes.hir_docs(sema.db)?; let (in_expansion_range, link, ns, is_inner) = - extract_definitions_from_docs(&docs).into_iter().find_map(|(range, link, ns)| { - let (mapped, idx) = doc_mapping.map(range)?; - (mapped.value.contains(abs_in_expansion_offset)).then_some((mapped.value, link, ns, idx.is_inner_attr())) + extract_definitions_from_docs(&Documentation::new_borrowed(doc_mapping.docs())).into_iter().find_map(|(range, link, ns)| { + let (mapped, is_inner) = doc_mapping.find_ast_range(range)?; + (mapped.value.contains(abs_in_expansion_offset)).then_some((mapped.value, link, ns, is_inner)) })?; // get the relative range to the doc/attribute in the expansion let in_expansion_relative_range = in_expansion_range - descended_prefix_len - token_start; @@ -416,7 +416,7 @@ fn rewrite_intra_doc_link( def: Definition, target: &str, title: &str, - is_inner_doc: bool, + is_inner_doc: hir::IsInnerDoc, link_type: LinkType, ) -> Option<(String, String)> { let (link, ns) = parse_intra_doc_link(target); @@ -659,14 +659,12 @@ fn filename_and_frag_for_def( Definition::Crate(_) => String::from("index.html"), Definition::Module(m) => match m.name(db) { // `#[doc(keyword = "...")]` is internal used only by rust compiler - Some(name) => { - match m.attrs(db).by_key(sym::doc).find_string_value_in_tt(sym::keyword) { - Some(kw) => { - format!("keyword.{kw}.html") - } - None => format!("{}/index.html", name.as_str()), + Some(name) => match m.doc_keyword(db) { + Some(kw) => { + format!("keyword.{kw}.html") } - } + None => format!("{}/index.html", name.as_str()), + }, None => String::from("index.html"), }, Definition::Trait(t) => { diff --git a/crates/ide/src/doc_links/tests.rs b/crates/ide/src/doc_links/tests.rs index b004c0757674..b1546e4e6e7d 100644 --- a/crates/ide/src/doc_links/tests.rs +++ b/crates/ide/src/doc_links/tests.rs @@ -1,4 +1,4 @@ -use std::iter; +use std::{borrow::Cow, iter}; use expect_test::{Expect, expect}; use hir::Semantics; @@ -6,7 +6,7 @@ use ide_db::{ FilePosition, FileRange, RootDatabase, base_db::salsa, defs::Definition, - documentation::{DocsRangeMap, Documentation, HasDocs}, + documentation::{Documentation, HasDocs}, }; use itertools::Itertools; use syntax::{AstNode, SyntaxNode, ast, match_ast}; @@ -46,8 +46,8 @@ fn check_external_docs( fn check_rewrite(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) { let (analysis, position) = fixture::position(ra_fixture); let sema = &Semantics::new(&analysis.db); - let (cursor_def, docs, range) = def_under_cursor(sema, &position); - let res = rewrite_links(sema.db, docs.as_str(), cursor_def, Some(range)); + let (cursor_def, docs) = def_under_cursor(sema, &position); + let res = rewrite_links(sema.db, docs.docs(), cursor_def, Some(&docs)); expect.assert_eq(&res) } @@ -57,13 +57,14 @@ fn check_doc_links(#[rust_analyzer::rust_fixture] ra_fixture: &str) { let (analysis, position, mut expected) = fixture::annotations(ra_fixture); expected.sort_by_key(key_fn); let sema = &Semantics::new(&analysis.db); - let (cursor_def, docs, range) = def_under_cursor(sema, &position); - let defs = extract_definitions_from_docs(&docs); + let (cursor_def, docs) = def_under_cursor(sema, &position); + let defs = extract_definitions_from_docs(&Documentation::new_borrowed(docs.docs())); let actual: Vec<_> = defs .into_iter() .flat_map(|(text_range, link, ns)| { - let attr = range.map(text_range); - let is_inner_attr = attr.map(|(_file, attr)| attr.is_inner_attr()).unwrap_or(false); + let attr = docs.find_ast_range(text_range); + let is_inner_attr = + attr.map(|(_file, is_inner)| is_inner).unwrap_or(hir::IsInnerDoc::No); let def = salsa::attach(sema.db, || { resolve_doc_path_for_def(sema.db, cursor_def, &link, ns, is_inner_attr) .unwrap_or_else(|| panic!("Failed to resolve {link}")) @@ -80,10 +81,10 @@ fn check_doc_links(#[rust_analyzer::rust_fixture] ra_fixture: &str) { assert_eq!(expected, actual); } -fn def_under_cursor( - sema: &Semantics<'_, RootDatabase>, +fn def_under_cursor<'db>( + sema: &Semantics<'db, RootDatabase>, position: &FilePosition, -) -> (Definition, Documentation, DocsRangeMap) { +) -> (Definition, Cow<'db, hir::Docs>) { let (docs, def) = sema .parse_guess_edition(position.file_id) .syntax() @@ -94,14 +95,14 @@ fn def_under_cursor( .find_map(|it| node_to_def(sema, &it)) .expect("no def found") .unwrap(); - let (docs, range) = docs.expect("no docs found for cursor def"); - (def, docs, range) + let docs = docs.expect("no docs found for cursor def"); + (def, docs) } -fn node_to_def( - sema: &Semantics<'_, RootDatabase>, +fn node_to_def<'db>( + sema: &Semantics<'db, RootDatabase>, node: &SyntaxNode, -) -> Option, Definition)>> { +) -> Option>, Definition)>> { Some(match_ast! { match node { ast::SourceFile(it) => sema.to_def(&it).map(|def| (def.docs_with_rangemap(sema.db), Definition::Module(def))), diff --git a/crates/ide/src/fixture.rs b/crates/ide/src/fixture.rs index fbf89042fae1..1a8591d25dca 100644 --- a/crates/ide/src/fixture.rs +++ b/crates/ide/src/fixture.rs @@ -7,10 +7,10 @@ use crate::{Analysis, AnalysisHost, FileId, FilePosition, FileRange}; /// Creates analysis for a single file. pub(crate) fn file(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> (Analysis, FileId) { let mut host = AnalysisHost::default(); - let change_fixture = ChangeFixture::parse(&host.db, ra_fixture); + let change_fixture = ChangeFixture::parse(ra_fixture); host.db.enable_proc_attr_macros(); host.db.apply_change(change_fixture.change); - (host.analysis(), change_fixture.files[0].file_id(&host.db)) + (host.analysis(), change_fixture.files[0].file_id()) } /// Creates analysis from a multi-file fixture, returns positions marked with $0. @@ -18,23 +18,23 @@ pub(crate) fn position( #[rust_analyzer::rust_fixture] ra_fixture: &str, ) -> (Analysis, FilePosition) { let mut host = AnalysisHost::default(); - let change_fixture = ChangeFixture::parse(&host.db, ra_fixture); + let change_fixture = ChangeFixture::parse(ra_fixture); host.db.enable_proc_attr_macros(); host.db.apply_change(change_fixture.change); let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); let offset = range_or_offset.expect_offset(); - (host.analysis(), FilePosition { file_id: file_id.file_id(&host.db), offset }) + (host.analysis(), FilePosition { file_id: file_id.file_id(), offset }) } /// Creates analysis for a single file, returns range marked with a pair of $0. pub(crate) fn range(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> (Analysis, FileRange) { let mut host = AnalysisHost::default(); - let change_fixture = ChangeFixture::parse(&host.db, ra_fixture); + let change_fixture = ChangeFixture::parse(ra_fixture); host.db.enable_proc_attr_macros(); host.db.apply_change(change_fixture.change); let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); let range = range_or_offset.expect_range(); - (host.analysis(), FileRange { file_id: file_id.file_id(&host.db), range }) + (host.analysis(), FileRange { file_id: file_id.file_id(), range }) } /// Creates analysis for a single file, returns range marked with a pair of $0 or a position marked with $0. @@ -42,11 +42,11 @@ pub(crate) fn range_or_position( #[rust_analyzer::rust_fixture] ra_fixture: &str, ) -> (Analysis, FileId, RangeOrOffset) { let mut host = AnalysisHost::default(); - let change_fixture = ChangeFixture::parse(&host.db, ra_fixture); + let change_fixture = ChangeFixture::parse(ra_fixture); host.db.enable_proc_attr_macros(); host.db.apply_change(change_fixture.change); let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); - (host.analysis(), file_id.file_id(&host.db), range_or_offset) + (host.analysis(), file_id.file_id(), range_or_offset) } /// Creates analysis from a multi-file fixture, returns positions marked with $0. @@ -54,25 +54,24 @@ pub(crate) fn annotations( #[rust_analyzer::rust_fixture] ra_fixture: &str, ) -> (Analysis, FilePosition, Vec<(FileRange, String)>) { let mut host = AnalysisHost::default(); - let change_fixture = ChangeFixture::parse(&host.db, ra_fixture); + let change_fixture = ChangeFixture::parse(ra_fixture); host.db.enable_proc_attr_macros(); host.db.apply_change(change_fixture.change); let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); let offset = range_or_offset.expect_offset(); - let db = &host.db; let annotations = change_fixture .files .iter() .flat_map(|&file_id| { - let file_text = host.analysis().file_text(file_id.file_id(&host.db)).unwrap(); + let file_text = host.analysis().file_text(file_id.file_id()).unwrap(); let annotations = extract_annotations(&file_text); annotations .into_iter() - .map(move |(range, data)| (FileRange { file_id: file_id.file_id(db), range }, data)) + .map(move |(range, data)| (FileRange { file_id: file_id.file_id(), range }, data)) }) .collect(); - (host.analysis(), FilePosition { file_id: file_id.file_id(&host.db), offset }, annotations) + (host.analysis(), FilePosition { file_id: file_id.file_id(), offset }, annotations) } /// Creates analysis from a multi-file fixture with annotations without $0 @@ -80,20 +79,19 @@ pub(crate) fn annotations_without_marker( #[rust_analyzer::rust_fixture] ra_fixture: &str, ) -> (Analysis, Vec<(FileRange, String)>) { let mut host = AnalysisHost::default(); - let change_fixture = ChangeFixture::parse(&host.db, ra_fixture); + let change_fixture = ChangeFixture::parse(ra_fixture); host.db.enable_proc_attr_macros(); host.db.apply_change(change_fixture.change); - let db = &host.db; let annotations = change_fixture .files .iter() .flat_map(|&file_id| { - let file_text = host.analysis().file_text(file_id.file_id(db)).unwrap(); + let file_text = host.analysis().file_text(file_id.file_id()).unwrap(); let annotations = extract_annotations(&file_text); annotations .into_iter() - .map(move |(range, data)| (FileRange { file_id: file_id.file_id(db), range }, data)) + .map(move |(range, data)| (FileRange { file_id: file_id.file_id(), range }, data)) }) .collect(); (host.analysis(), annotations) diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs index 4f172621908c..2f6a8825b9a9 100644 --- a/crates/ide/src/goto_implementation.rs +++ b/crates/ide/src/goto_implementation.rs @@ -355,7 +355,7 @@ trait Bar {} fn test() { #[derive(Copy)] - //^^^^^^^^^^^^^^^ + // ^^^^^^^^^^^^ struct Foo$0; impl Foo {} diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs index 9960e79a5380..ba7fd8a34dac 100644 --- a/crates/ide/src/highlight_related.rs +++ b/crates/ide/src/highlight_related.rs @@ -62,7 +62,7 @@ pub(crate) fn highlight_related( let _p = tracing::info_span!("highlight_related").entered(); let file_id = sema .attach_first_edition(file_id) - .unwrap_or_else(|| EditionedFileId::current_edition(sema.db, file_id)); + .unwrap_or_else(|| EditionedFileId::current_edition_guess_origin(sema.db, file_id)); let syntax = sema.parse(file_id).syntax().clone(); let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind { diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index e4c7d42ffe3b..d7b1905ed08f 100644 --- a/crates/ide/src/hover/render.rs +++ b/crates/ide/src/hover/render.rs @@ -1,5 +1,5 @@ //! Logic for rendering the different hover messages -use std::{env, mem, ops::Not}; +use std::{borrow::Cow, env, mem, ops::Not}; use either::Either; use hir::{ @@ -12,7 +12,7 @@ use ide_db::{ RootDatabase, base_db::salsa, defs::Definition, - documentation::{DocsRangeMap, HasDocs}, + documentation::{Documentation, HasDocs}, famous_defs::FamousDefs, generated::lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES}, syntax_helpers::prettify_macro_expansion, @@ -279,9 +279,9 @@ pub(super) fn keyword( keyword_hints(sema, token, parent, edition, display_target); let doc_owner = find_std_module(&famous_defs, &keyword_mod, edition)?; - let (docs, range_map) = doc_owner.docs_with_rangemap(sema.db)?; + let docs = doc_owner.docs_with_rangemap(sema.db)?; let (markup, range_map) = - markup(Some(docs.into()), Some(range_map), description, None, None, String::new()); + markup(Some(Either::Left(docs)), description, None, None, String::new()); let markup = process_markup(sema.db, Definition::Module(doc_owner), &markup, range_map, config); Some(HoverResult { markup, actions }) } @@ -371,12 +371,12 @@ pub(super) fn process_markup( db: &RootDatabase, def: Definition, markup: &Markup, - markup_range_map: Option, + markup_range_map: Option, config: &HoverConfig, ) -> Markup { let markup = markup.as_str(); let markup = if config.links_in_hover { - rewrite_links(db, markup, def, markup_range_map) + rewrite_links(db, markup, def, markup_range_map.as_ref()) } else { remove_links(markup) }; @@ -485,7 +485,7 @@ pub(super) fn definition( config: &HoverConfig, edition: Edition, display_target: DisplayTarget, -) -> (Markup, Option) { +) -> (Markup, Option) { let mod_path = definition_path(db, &def, edition); let label = match def { Definition::Trait(trait_) => trait_ @@ -521,12 +521,7 @@ pub(super) fn definition( } _ => def.label(db, display_target), }; - let (docs, range_map) = - if let Some((docs, doc_range)) = def.docs_with_rangemap(db, famous_defs, display_target) { - (Some(docs), doc_range) - } else { - (None, None) - }; + let docs = def.docs_with_rangemap(db, famous_defs, display_target); let value = || match def { Definition::Variant(it) => { if !it.parent_enum(db).is_data_carrying(db) { @@ -843,14 +838,7 @@ pub(super) fn definition( } }; - markup( - docs.map(Into::into), - range_map, - desc, - extra.is_empty().not().then_some(extra), - mod_path, - subst_types, - ) + markup(docs, desc, extra.is_empty().not().then_some(extra), mod_path, subst_types) } #[derive(Debug)] @@ -1125,13 +1113,12 @@ fn definition_path(db: &RootDatabase, &def: &Definition, edition: Edition) -> Op } fn markup( - docs: Option, - range_map: Option, + docs: Option, Documentation<'_>>>, rust: String, extra: Option, mod_path: Option, subst_types: String, -) -> (Markup, Option) { +) -> (Markup, Option) { let mut buf = String::new(); if let Some(mod_path) = mod_path @@ -1152,10 +1139,21 @@ fn markup( if let Some(doc) = docs { format_to!(buf, "\n___\n\n"); let offset = TextSize::new(buf.len() as u32); - let buf_range_map = range_map.map(|range_map| range_map.shift_docstring_line_range(offset)); - format_to!(buf, "{}", doc); + let docs_str = match &doc { + Either::Left(docs) => docs.docs(), + Either::Right(docs) => docs.as_str(), + }; + format_to!(buf, "{}", docs_str); + let range_map = match doc { + Either::Left(range_map) => { + let mut range_map = range_map.into_owned(); + range_map.shift_by(offset); + Some(range_map) + } + Either::Right(_) => None, + }; - (buf.into(), buf_range_map) + (buf.into(), range_map) } else { (buf.into(), None) } diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index 424890fe370c..6101b6b09171 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -88,7 +88,7 @@ pub(crate) fn inlay_hints( let sema = Semantics::new(db); let file_id = sema .attach_first_edition(file_id) - .unwrap_or_else(|| EditionedFileId::current_edition(db, file_id)); + .unwrap_or_else(|| EditionedFileId::current_edition_guess_origin(db, file_id)); let file = sema.parse(file_id); let file = file.syntax(); @@ -141,7 +141,7 @@ pub(crate) fn inlay_hints_resolve( let sema = Semantics::new(db); let file_id = sema .attach_first_edition(file_id) - .unwrap_or_else(|| EditionedFileId::current_edition(db, file_id)); + .unwrap_or_else(|| EditionedFileId::current_edition_guess_origin(db, file_id)); let file = sema.parse(file_id); let file = file.syntax(); diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 5349ebb7c82a..5a4341a0a2e1 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -306,7 +306,8 @@ impl Analysis { pub fn parse(&self, file_id: FileId) -> Cancellable { // FIXME edition self.with_db(|db| { - let editioned_file_id_wrapper = EditionedFileId::current_edition(&self.db, file_id); + let editioned_file_id_wrapper = + EditionedFileId::current_edition_guess_origin(&self.db, file_id); db.parse(editioned_file_id_wrapper).tree() }) @@ -335,7 +336,7 @@ impl Analysis { /// supported). pub fn matching_brace(&self, position: FilePosition) -> Cancellable> { self.with_db(|db| { - let file_id = EditionedFileId::current_edition(&self.db, position.file_id); + let file_id = EditionedFileId::current_edition_guess_origin(&self.db, position.file_id); let parse = db.parse(file_id); let file = parse.tree(); matching_brace::matching_brace(&file, position.offset) @@ -396,7 +397,7 @@ impl Analysis { pub fn join_lines(&self, config: &JoinLinesConfig, frange: FileRange) -> Cancellable { self.with_db(|db| { let editioned_file_id_wrapper = - EditionedFileId::current_edition(&self.db, frange.file_id); + EditionedFileId::current_edition_guess_origin(&self.db, frange.file_id); let parse = db.parse(editioned_file_id_wrapper); join_lines::join_lines(config, &parse.tree(), frange.range) }) @@ -437,7 +438,8 @@ impl Analysis { ) -> Cancellable> { // FIXME: Edition self.with_db(|db| { - let editioned_file_id_wrapper = EditionedFileId::current_edition(&self.db, file_id); + let editioned_file_id_wrapper = + EditionedFileId::current_edition_guess_origin(&self.db, file_id); let source_file = db.parse(editioned_file_id_wrapper).tree(); file_structure::file_structure(&source_file, config) }) @@ -468,7 +470,8 @@ impl Analysis { /// Returns the set of folding ranges. pub fn folding_ranges(&self, file_id: FileId) -> Cancellable> { self.with_db(|db| { - let editioned_file_id_wrapper = EditionedFileId::current_edition(&self.db, file_id); + let editioned_file_id_wrapper = + EditionedFileId::current_edition_guess_origin(&self.db, file_id); folding_ranges::folding_ranges(&db.parse(editioned_file_id_wrapper).tree()) }) diff --git a/crates/ide/src/navigation_target.rs b/crates/ide/src/navigation_target.rs index 61b4de0e0e39..87cee52c0471 100644 --- a/crates/ide/src/navigation_target.rs +++ b/crates/ide/src/navigation_target.rs @@ -51,7 +51,8 @@ pub struct NavigationTarget { // FIXME: Symbol pub container_name: Option, pub description: Option, - pub docs: Option, + // FIXME: Use the database lifetime here. + pub docs: Option>, /// In addition to a `name` field, a `NavigationTarget` may also be aliased /// In such cases we want a `NavigationTarget` to be accessible by its alias // FIXME: Symbol @@ -116,7 +117,7 @@ impl NavigationTarget { full_range, SymbolKind::Module, ); - res.docs = module.docs(db); + res.docs = module.docs(db).map(Documentation::into_owned); res.description = Some( module.display(db, module.krate().to_display_target(db)).to_string(), ); @@ -376,7 +377,7 @@ where D::KIND, ) .map(|mut res| { - res.docs = self.docs(db); + res.docs = self.docs(db).map(Documentation::into_owned); res.description = Some(self.display(db, self.krate(db).to_display_target(db)).to_string()); res.container_name = self.container_name(db); @@ -466,7 +467,7 @@ impl TryToNav for hir::ExternCrateDecl { SymbolKind::Module, ); - res.docs = self.docs(db); + res.docs = self.docs(db).map(Documentation::into_owned); res.description = Some(self.display(db, krate.to_display_target(db)).to_string()); res.container_name = container_name(db, *self, edition); res @@ -484,7 +485,7 @@ impl TryToNav for hir::Field { FieldSource::Named(it) => { NavigationTarget::from_named(db, src.with_value(it), SymbolKind::Field).map( |mut res| { - res.docs = self.docs(db); + res.docs = self.docs(db).map(Documentation::into_owned); res.description = Some(self.display(db, krate.to_display_target(db)).to_string()); res @@ -521,7 +522,7 @@ impl TryToNav for hir::Macro { self.kind(db).into(), ) .map(|mut res| { - res.docs = self.docs(db); + res.docs = self.docs(db).map(Documentation::into_owned); res }), ) @@ -806,7 +807,7 @@ pub(crate) fn orig_range_with_focus_r( ) -> UpmappingResult<(FileRange, Option)> { let Some(name) = focus_range else { return orig_range_r(db, hir_file, value) }; - let call_kind = || db.lookup_intern_macro_call(hir_file.macro_file().unwrap()).kind; + let call = || db.lookup_intern_macro_call(hir_file.macro_file().unwrap()); let def_range = || db.lookup_intern_macro_call(hir_file.macro_file().unwrap()).def.definition_range(db); @@ -832,7 +833,8 @@ pub(crate) fn orig_range_with_focus_r( // name lies outside the node, so instead point to the macro call which // *should* contain the name _ => { - let kind = call_kind(); + let call = call(); + let kind = call.kind; let range = kind.clone().original_call_range_with_input(db); //If the focus range is in the attribute/derive body, we // need to point the call site to the entire body, if not, fall back @@ -844,7 +846,7 @@ pub(crate) fn orig_range_with_focus_r( { range } else { - kind.original_call_range(db) + kind.original_call_range(db, call.krate) } } }, @@ -873,11 +875,14 @@ pub(crate) fn orig_range_with_focus_r( }, ), // node is in macro def, just show the focus - _ => ( - // show the macro call - (call_kind().original_call_range(db), None), - Some((focus_range, Some(focus_range))), - ), + _ => { + let call = call(); + ( + // show the macro call + (call.kind.original_call_range(db, call.krate), None), + Some((focus_range, Some(focus_range))), + ) + } } } // lost name? can't happen for single tokens diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index dc509b385838..f9f47b639ec9 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -1099,7 +1099,10 @@ pub(super) struct Foo$0 { check_with_scope( code, Some(&mut |db| { - SearchScope::single_file(EditionedFileId::current_edition(db, FileId::from_raw(2))) + SearchScope::single_file(EditionedFileId::current_edition_guess_origin( + db, + FileId::from_raw(2), + )) }), expect![[r#" quux Function FileId(0) 19..35 26..30 diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 77c84292cf3e..4f6c4aaf2d5e 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -4,15 +4,14 @@ use arrayvec::ArrayVec; use ast::HasName; use cfg::{CfgAtom, CfgExpr}; use hir::{ - AsAssocItem, AttrsWithOwner, HasAttrs, HasCrate, HasSource, ModPath, Name, PathKind, Semantics, - Symbol, db::HirDatabase, sym, + AsAssocItem, HasAttrs, HasCrate, HasSource, ModPath, Name, PathKind, Semantics, Symbol, + db::HirDatabase, sym, }; use ide_assists::utils::{has_test_related_attribute, test_related_attribute_syn}; use ide_db::{ FilePosition, FxHashMap, FxIndexMap, FxIndexSet, RootDatabase, SymbolKind, base_db::RootQueryDb, defs::Definition, - documentation::docs_from_attrs, helpers::visit_file_defs, search::{FileReferenceNode, SearchScope}, }; @@ -319,7 +318,7 @@ pub(crate) fn runnable_fn( def: hir::Function, ) -> Option { let edition = def.krate(sema.db).edition(sema.db); - let under_cfg_test = has_cfg_test(def.module(sema.db).attrs(sema.db)); + let under_cfg_test = has_cfg_test(def.module(sema.db).attrs(sema.db).cfgs(sema.db)); let kind = if !under_cfg_test && def.is_main(sema.db) { RunnableKind::Bin } else { @@ -355,7 +354,7 @@ pub(crate) fn runnable_fn( let update_test = UpdateTest::find_snapshot_macro(sema, &fn_source.file_syntax(sema.db), file_range); - let cfg = def.attrs(sema.db).cfg(); + let cfg = def.attrs(sema.db).cfgs(sema.db).cloned(); Some(Runnable { use_name_in_title: false, nav, kind, cfg, update_test }) } @@ -363,8 +362,8 @@ pub(crate) fn runnable_mod( sema: &Semantics<'_, RootDatabase>, def: hir::Module, ) -> Option { - if !has_test_function_or_multiple_test_submodules(sema, &def, has_cfg_test(def.attrs(sema.db))) - { + let cfg = def.attrs(sema.db).cfgs(sema.db); + if !has_test_function_or_multiple_test_submodules(sema, &def, has_cfg_test(cfg)) { return None; } let path = def @@ -378,8 +377,7 @@ pub(crate) fn runnable_mod( }) .join("::"); - let attrs = def.attrs(sema.db); - let cfg = attrs.cfg(); + let cfg = cfg.cloned(); let nav = NavigationTarget::from_module_to_decl(sema.db, def).call_site(); let module_source = sema.module_definition_node(def); @@ -406,10 +404,10 @@ pub(crate) fn runnable_impl( let display_target = def.module(sema.db).krate().to_display_target(sema.db); let edition = display_target.edition; let attrs = def.attrs(sema.db); - if !has_runnable_doc_test(&attrs) { + if !has_runnable_doc_test(sema.db, &attrs) { return None; } - let cfg = attrs.cfg(); + let cfg = attrs.cfgs(sema.db).cloned(); let nav = def.try_to_nav(sema.db)?.call_site(); let ty = def.self_ty(sema.db); let adt_name = ty.as_adt()?.name(sema.db); @@ -438,8 +436,16 @@ pub(crate) fn runnable_impl( }) } -fn has_cfg_test(attrs: AttrsWithOwner) -> bool { - attrs.cfgs().any(|cfg| matches!(&cfg, CfgExpr::Atom(CfgAtom::Flag(s)) if *s == sym::test)) +fn has_cfg_test(cfg: Option<&CfgExpr>) -> bool { + return cfg.is_some_and(has_cfg_test_impl); + + fn has_cfg_test_impl(cfg: &CfgExpr) -> bool { + match cfg { + CfgExpr::Atom(CfgAtom::Flag(s)) => *s == sym::test, + CfgExpr::Any(cfgs) | CfgExpr::All(cfgs) => cfgs.iter().any(has_cfg_test_impl), + _ => false, + } + } } /// Creates a test mod runnable for outline modules at the top of their definition. @@ -449,8 +455,8 @@ fn runnable_mod_outline_definition( ) -> Option { def.as_source_file_id(sema.db)?; - if !has_test_function_or_multiple_test_submodules(sema, &def, has_cfg_test(def.attrs(sema.db))) - { + let cfg = def.attrs(sema.db).cfgs(sema.db); + if !has_test_function_or_multiple_test_submodules(sema, &def, has_cfg_test(cfg)) { return None; } let path = def @@ -464,8 +470,7 @@ fn runnable_mod_outline_definition( }) .join("::"); - let attrs = def.attrs(sema.db); - let cfg = attrs.cfg(); + let cfg = cfg.cloned(); let mod_source = sema.module_definition_node(def); let mod_syntax = mod_source.file_syntax(sema.db); @@ -503,7 +508,7 @@ fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option { let display_target = krate .unwrap_or_else(|| (*db.all_crates().last().expect("no crate graph present")).into()) .to_display_target(db); - if !has_runnable_doc_test(&attrs) { + if !has_runnable_doc_test(db, &attrs) { return None; } let def_name = def.name(db)?; @@ -547,7 +552,7 @@ fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option { use_name_in_title: false, nav, kind: RunnableKind::DocTest { test_id }, - cfg: attrs.cfg(), + cfg: attrs.cfgs(db).cloned(), update_test: UpdateTest::default(), }; Some(res) @@ -564,15 +569,15 @@ impl TestAttr { } } -fn has_runnable_doc_test(attrs: &hir::Attrs) -> bool { +fn has_runnable_doc_test(db: &RootDatabase, attrs: &hir::AttrsWithOwner) -> bool { const RUSTDOC_FENCES: [&str; 2] = ["```", "~~~"]; const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE: &[&str] = &["", "rust", "should_panic", "edition2015", "edition2018", "edition2021"]; - docs_from_attrs(attrs).is_some_and(|doc| { + attrs.hir_docs(db).is_some_and(|doc| { let mut in_code_block = false; - for line in doc.lines() { + for line in doc.docs().lines() { if let Some(header) = RUSTDOC_FENCES.into_iter().find_map(|fence| line.strip_prefix(fence)) { diff --git a/crates/ide/src/signature_help.rs b/crates/ide/src/signature_help.rs index 86d02b4098bd..2922ef5a212b 100644 --- a/crates/ide/src/signature_help.rs +++ b/crates/ide/src/signature_help.rs @@ -31,7 +31,7 @@ use crate::RootDatabase; /// edited. #[derive(Debug)] pub struct SignatureHelp { - pub doc: Option, + pub doc: Option>, pub signature: String, pub active_parameter: Option, parameters: Vec, @@ -174,7 +174,7 @@ fn signature_help_for_call( let mut fn_params = None; match callable.kind() { hir::CallableKind::Function(func) => { - res.doc = func.docs(db); + res.doc = func.docs(db).map(Documentation::into_owned); format_to!(res.signature, "fn {}", func.name(db).display(db, edition)); let generic_params = GenericDef::Function(func) @@ -196,7 +196,7 @@ fn signature_help_for_call( }); } hir::CallableKind::TupleStruct(strukt) => { - res.doc = strukt.docs(db); + res.doc = strukt.docs(db).map(Documentation::into_owned); format_to!(res.signature, "struct {}", strukt.name(db).display(db, edition)); let generic_params = GenericDef::Adt(strukt.into()) @@ -209,7 +209,7 @@ fn signature_help_for_call( } } hir::CallableKind::TupleEnumVariant(variant) => { - res.doc = variant.docs(db); + res.doc = variant.docs(db).map(Documentation::into_owned); format_to!( res.signature, "enum {}", @@ -314,33 +314,33 @@ fn signature_help_for_generics( let db = sema.db; match generics_def { hir::GenericDef::Function(it) => { - res.doc = it.docs(db); + res.doc = it.docs(db).map(Documentation::into_owned); format_to!(res.signature, "fn {}", it.name(db).display(db, edition)); } hir::GenericDef::Adt(hir::Adt::Enum(it)) => { - res.doc = it.docs(db); + res.doc = it.docs(db).map(Documentation::into_owned); format_to!(res.signature, "enum {}", it.name(db).display(db, edition)); if let Some(variant) = variant { // In paths, generics of an enum can be specified *after* one of its variants. // eg. `None::` // We'll use the signature of the enum, but include the docs of the variant. - res.doc = variant.docs(db); + res.doc = variant.docs(db).map(Documentation::into_owned); } } hir::GenericDef::Adt(hir::Adt::Struct(it)) => { - res.doc = it.docs(db); + res.doc = it.docs(db).map(Documentation::into_owned); format_to!(res.signature, "struct {}", it.name(db).display(db, edition)); } hir::GenericDef::Adt(hir::Adt::Union(it)) => { - res.doc = it.docs(db); + res.doc = it.docs(db).map(Documentation::into_owned); format_to!(res.signature, "union {}", it.name(db).display(db, edition)); } hir::GenericDef::Trait(it) => { - res.doc = it.docs(db); + res.doc = it.docs(db).map(Documentation::into_owned); format_to!(res.signature, "trait {}", it.name(db).display(db, edition)); } hir::GenericDef::TypeAlias(it) => { - res.doc = it.docs(db); + res.doc = it.docs(db).map(Documentation::into_owned); format_to!(res.signature, "type {}", it.name(db).display(db, edition)); } // These don't have generic args that can be specified @@ -495,7 +495,7 @@ fn signature_help_for_tuple_struct_pat( let fields: Vec<_> = if let PathResolution::Def(ModuleDef::Variant(variant)) = path_res { let en = variant.parent_enum(db); - res.doc = en.docs(db); + res.doc = en.docs(db).map(Documentation::into_owned); format_to!( res.signature, "enum {}::{} (", @@ -512,7 +512,7 @@ fn signature_help_for_tuple_struct_pat( match adt { hir::Adt::Struct(it) => { - res.doc = it.docs(db); + res.doc = it.docs(db).map(Documentation::into_owned); format_to!(res.signature, "struct {} (", it.name(db).display(db, edition)); it.fields(db) } @@ -622,7 +622,7 @@ fn signature_help_for_record_<'db>( fields = variant.fields(db); let en = variant.parent_enum(db); - res.doc = en.docs(db); + res.doc = en.docs(db).map(Documentation::into_owned); format_to!( res.signature, "enum {}::{} {{ ", @@ -639,12 +639,12 @@ fn signature_help_for_record_<'db>( match adt { hir::Adt::Struct(it) => { fields = it.fields(db); - res.doc = it.docs(db); + res.doc = it.docs(db).map(Documentation::into_owned); format_to!(res.signature, "struct {} {{ ", it.name(db).display(db, edition)); } hir::Adt::Union(it) => { fields = it.fields(db); - res.doc = it.docs(db); + res.doc = it.docs(db).map(Documentation::into_owned); format_to!(res.signature, "union {} {{ ", it.name(db).display(db, edition)); } _ => return None, @@ -740,12 +740,12 @@ mod tests { #[rust_analyzer::rust_fixture] ra_fixture: &str, ) -> (RootDatabase, FilePosition) { let mut database = RootDatabase::default(); - let change_fixture = ChangeFixture::parse(&database, ra_fixture); + let change_fixture = ChangeFixture::parse(ra_fixture); database.apply_change(change_fixture.change); let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); let offset = range_or_offset.expect_offset(); - let position = FilePosition { file_id: file_id.file_id(&database), offset }; + let position = FilePosition { file_id: file_id.file_id(), offset }; (database, position) } diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs index 694ac22e1993..838a468d5512 100644 --- a/crates/ide/src/static_index.rs +++ b/crates/ide/src/static_index.rs @@ -42,7 +42,8 @@ pub struct ReferenceData { #[derive(Debug)] pub struct TokenStaticData { - pub documentation: Option, + // FIXME: Make this have the lifetime of the database. + pub documentation: Option>, pub hover: Option, pub definition: Option, pub references: Vec, @@ -109,7 +110,7 @@ fn documentation_for_definition( sema: &Semantics<'_, RootDatabase>, def: Definition, scope_node: &SyntaxNode, -) -> Option { +) -> Option> { let famous_defs = match &def { Definition::BuiltinType(_) => Some(FamousDefs(sema, sema.scope(scope_node)?.krate())), _ => None, @@ -124,6 +125,7 @@ fn documentation_for_definition( }) .to_display_target(sema.db), ) + .map(Documentation::into_owned) } // FIXME: This is a weird function diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index e3dc3ed3c742..414e663b0df6 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs @@ -196,7 +196,7 @@ pub(crate) fn highlight( let sema = Semantics::new(db); let file_id = sema .attach_first_edition(file_id) - .unwrap_or_else(|| EditionedFileId::current_edition(db, file_id)); + .unwrap_or_else(|| EditionedFileId::current_edition_guess_origin(db, file_id)); // Determine the root based on the given range. let (root, range_to_highlight) = { diff --git a/crates/ide/src/syntax_highlighting/html.rs b/crates/ide/src/syntax_highlighting/html.rs index 9fd807f031f1..b38db5b3cd12 100644 --- a/crates/ide/src/syntax_highlighting/html.rs +++ b/crates/ide/src/syntax_highlighting/html.rs @@ -14,7 +14,7 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo let sema = Semantics::new(db); let file_id = sema .attach_first_edition(file_id) - .unwrap_or_else(|| EditionedFileId::current_edition(db, file_id)); + .unwrap_or_else(|| EditionedFileId::current_edition_guess_origin(db, file_id)); let file = sema.parse(file_id); let file = file.syntax(); fn rainbowify(seed: u64) -> String { diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs index fb33307249a7..66edd0fadfbf 100644 --- a/crates/ide/src/syntax_highlighting/inject.rs +++ b/crates/ide/src/syntax_highlighting/inject.rs @@ -1,16 +1,13 @@ //! "Recursive" Syntax highlighting for code in doctests and fixtures. -use std::mem; - -use either::Either; -use hir::{EditionedFileId, HirFileId, InFile, Semantics, sym}; +use hir::{EditionedFileId, HirFileId, InFile, Semantics}; use ide_db::{ - SymbolKind, active_parameter::ActiveParameter, defs::Definition, - documentation::docs_with_rangemap, rust_doc::is_rust_fence, + SymbolKind, active_parameter::ActiveParameter, defs::Definition, documentation::Documentation, + rust_doc::is_rust_fence, }; use syntax::{ - AstToken, NodeOrToken, SyntaxNode, TextRange, TextSize, - ast::{self, AstNode, IsString, QuoteOffsets}, + AstToken, SyntaxNode, TextRange, TextSize, + ast::{self, IsString}, }; use crate::{ @@ -123,118 +120,79 @@ pub(super) fn doc_comment( None => return, }; let src_file_id: HirFileId = src_file_id.into(); + let Some(docs) = attributes.hir_docs(sema.db) else { return }; // Extract intra-doc links and emit highlights for them. - if let Some((docs, doc_mapping)) = docs_with_rangemap(sema.db, &attributes) { - extract_definitions_from_docs(&docs) - .into_iter() - .filter_map(|(range, link, ns)| { - doc_mapping - .map(range) - .filter(|(mapping, _)| mapping.file_id == src_file_id) - .and_then(|(InFile { value: mapped_range, .. }, attr_id)| { - Some(mapped_range).zip(resolve_doc_path_for_def( - sema.db, - def, - &link, - ns, - attr_id.is_inner_attr(), - )) - }) - }) - .for_each(|(range, def)| { - hl.add(HlRange { - range, - highlight: module_def_to_hl_tag(def) - | HlMod::Documentation - | HlMod::Injected - | HlMod::IntraDocLink, - binding_hash: None, + extract_definitions_from_docs(&Documentation::new_borrowed(docs.docs())) + .into_iter() + .filter_map(|(range, link, ns)| { + docs.find_ast_range(range) + .filter(|(mapping, _)| mapping.file_id == src_file_id) + .and_then(|(InFile { value: mapped_range, .. }, is_inner)| { + Some(mapped_range) + .zip(resolve_doc_path_for_def(sema.db, def, &link, ns, is_inner)) }) - }); - } + }) + .for_each(|(range, def)| { + hl.add(HlRange { + range, + highlight: module_def_to_hl_tag(def) + | HlMod::Documentation + | HlMod::Injected + | HlMod::IntraDocLink, + binding_hash: None, + }) + }); // Extract doc-test sources from the docs and calculate highlighting for them. let mut inj = Injector::default(); inj.add_unmapped("fn doctest() {\n"); - let attrs_source_map = attributes.source_map(sema.db); - let mut is_codeblock = false; let mut is_doctest = false; - let mut new_comments = Vec::new(); - let mut string; + let mut has_doctests = false; + + let mut docs_offset = TextSize::new(0); + for mut line in docs.docs().split('\n') { + let mut line_docs_offset = docs_offset; + docs_offset += TextSize::of(line) + TextSize::of("\n"); + + match RUSTDOC_FENCES.into_iter().find_map(|fence| line.find(fence)) { + Some(idx) => { + is_codeblock = !is_codeblock; + // Check whether code is rust by inspecting fence guards + let guards = &line[idx + RUSTDOC_FENCE_LENGTH..]; + let is_rust = is_rust_fence(guards); + is_doctest = is_codeblock && is_rust; + continue; + } + None if !is_doctest => continue, + None => (), + } + + // lines marked with `#` should be ignored in output, we skip the `#` char + if line.starts_with('#') { + line_docs_offset += TextSize::of("#"); + line = &line["#".len()..]; + } - for attr in attributes.by_key(sym::doc).attrs() { - let InFile { file_id, value: src } = attrs_source_map.source_of(attr); + let Some((InFile { file_id, value: mapped_range }, _)) = + docs.find_ast_range(TextRange::at(line_docs_offset, TextSize::of(line))) + else { + continue; + }; if file_id != src_file_id { continue; } - let (line, range) = match &src { - Either::Left(it) => { - string = match find_doc_string_in_attr(attr, it) { - Some(it) => it, - None => continue, - }; - let text = string.text(); - let text_range = string.syntax().text_range(); - match string.quote_offsets() { - Some(QuoteOffsets { contents, .. }) => { - (&text[contents - text_range.start()], contents) - } - None => (text, text_range), - } - } - Either::Right(comment) => { - let value = comment.prefix().len(); - let range = comment.syntax().text_range(); - ( - &comment.text()[value..], - TextRange::new(range.start() + TextSize::try_from(value).unwrap(), range.end()), - ) - } - }; - - let mut range_start = range.start(); - for line in line.split('\n') { - let line_len = TextSize::from(line.len() as u32); - let prev_range_start = { - let next_range_start = range_start + line_len + TextSize::from(1); - mem::replace(&mut range_start, next_range_start) - }; - let mut pos = TextSize::from(0); - - match RUSTDOC_FENCES.into_iter().find_map(|fence| line.find(fence)) { - Some(idx) => { - is_codeblock = !is_codeblock; - // Check whether code is rust by inspecting fence guards - let guards = &line[idx + RUSTDOC_FENCE_LENGTH..]; - let is_rust = is_rust_fence(guards); - is_doctest = is_codeblock && is_rust; - continue; - } - None if !is_doctest => continue, - None => (), - } - // whitespace after comment is ignored - if let Some(ws) = line[pos.into()..].chars().next().filter(|c| c.is_whitespace()) { - pos += TextSize::of(ws); - } - // lines marked with `#` should be ignored in output, we skip the `#` char - if line[pos.into()..].starts_with('#') { - pos += TextSize::of('#'); - } - - new_comments.push(TextRange::at(prev_range_start, pos)); - inj.add(&line[pos.into()..], TextRange::new(pos, line_len) + prev_range_start); - inj.add_unmapped("\n"); - } + has_doctests = true; + inj.add(line, mapped_range); + inj.add_unmapped("\n"); } - if new_comments.is_empty() { + if !has_doctests { return; // no need to run an analysis on an empty file } @@ -265,37 +223,6 @@ pub(super) fn doc_comment( } } } - - for range in new_comments { - hl.add(HlRange { - range, - highlight: HlTag::Comment | HlMod::Documentation, - binding_hash: None, - }); - } -} - -fn find_doc_string_in_attr(attr: &hir::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 => { - // We gotta hunt the string token manually here - let text = attr.string_value()?.as_str(); - // FIXME: We just pick the first string literal that has the same text as the doc attribute - // This means technically we might highlight the wrong one - it.syntax() - .descendants_with_tokens() - .filter_map(NodeOrToken::into_token) - .filter_map(ast::String::cast) - .find(|string| string.text().get(1..string.text().len() - 1) == Some(text)) - } - _ => None, - } } fn module_def_to_hl_tag(def: Definition) -> HlTag { diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html index d00f279c8299..53750ae0bac0 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html @@ -42,21 +42,21 @@
//! This is a module to test doc injection.
 //! ```
-//! fn test() {}
+//! fn test() {}
 //! ```
 
 //! Syntactic name ref highlighting testing
 //! ```rust
-//! extern crate self;
-//! extern crate other as otter;
-//! extern crate core;
-//! trait T { type Assoc; }
-//! fn f<Arg>() -> use<Arg> where (): T<Assoc = ()> {}
+//! extern crate self;
+//! extern crate other as otter;
+//! extern crate core;
+//! trait T { type Assoc; }
+//! fn f<Arg>() -> use<Arg> where (): T<Assoc = ()> {}
 //! ```
 mod outline_module;
 
 /// ```
-/// let _ = "early doctests should not go boom";
+/// let _ = "early doctests should not go boom";
 /// ```
 struct Foo {
     bar: bool,
@@ -65,15 +65,15 @@
 /// This is an impl of [`Foo`] with a code block.
 ///
 /// ```
-/// fn foo() {
+/// fn foo() {
 ///
-/// }
+/// }
 /// ```
 impl Foo {
     /// ```
-    /// let _ = "Call me
+    /// let _ = "Call me
     //    KILLER WHALE
-    ///     Ishmael.";
+    ///     Ishmael.";
     /// ```
     pub const bar: bool = true;
 
@@ -82,8 +82,8 @@
     /// # Examples
     ///
     /// ```
-    /// # #![allow(unused_mut)]
-    /// let mut foo: Foo = Foo::new();
+    /// # #![allow(unused_mut)]
+    /// let mut foo: Foo = Foo::new();
     /// ```
     pub const fn new() -> Foo {
         Foo { bar: true }
@@ -94,38 +94,38 @@
     /// # Examples
     ///
     /// ```
-    /// use x::y;
+    /// use x::y;
     ///
-    /// let foo = Foo::new();
+    /// let foo = Foo::new();
     ///
-    /// // calls bar on foo
-    /// assert!(foo.bar());
+    /// // calls bar on foo
+    /// assert!(foo.bar());
     ///
-    /// let bar = foo.bar || Foo::bar;
+    /// let bar = foo.bar || Foo::bar;
     ///
-    /// /* multi-line
-    ///        comment */
+    /// /* multi-line
+    ///        comment */
     ///
-    /// let multi_line_string = "Foo
-    ///   bar\n
-    ///          ";
+    /// let multi_line_string = "Foo
+    ///   bar\n
+    ///          ";
     ///
     /// ```
     ///
     /// ```rust,no_run
-    /// let foobar = Foo::new().bar();
+    /// let foobar = Foo::new().bar();
     /// ```
     ///
     /// ~~~rust,no_run
-    /// // code block with tilde.
-    /// let foobar = Foo::new().bar();
+    /// // code block with tilde.
+    /// let foobar = Foo::new().bar();
     /// ~~~
     ///
     /// ```
-    /// // functions
-    /// fn foo<T, const X: usize>(arg: i32) {
-    ///     let x: T = X;
-    /// }
+    /// // functions
+    /// fn foo<T, const X: usize>(arg: i32) {
+    ///     let x: T = X;
+    /// }
     /// ```
     ///
     /// ```sh
@@ -150,8 +150,8 @@
 }
 
 /// ```
-/// macro_rules! noop { ($expr:expr) => { $expr }}
-/// noop!(1);
+/// macro_rules! noop { ($expr:expr) => { $expr }}
+/// noop!(1);
 /// ```
 macro_rules! noop {
     ($expr:expr) => {
@@ -160,18 +160,18 @@
 }
 
 /// ```rust
-/// let _ = example(&[1, 2, 3]);
+/// let _ = example(&[1, 2, 3]);
 /// ```
 ///
 /// ```
-/// loop {}
+/// loop {}
 #[cfg_attr(not(feature = "false"), doc = "loop {}")]
 #[doc = "loop {}"]
 /// ```
 ///
 #[cfg_attr(feature = "alloc", doc = "```rust")]
 #[cfg_attr(not(feature = "alloc"), doc = "```ignore")]
-/// let _ = example(&alloc::vec![1, 2, 3]);
+/// let _ = example(&alloc::vec![1, 2, 3]);
 /// ```
 pub fn mix_and_match() {}
 
@@ -187,7 +187,7 @@
 /**
     Really, I don't get it
     ```rust
-    let _ = example(&[1, 2, 3]);
+    let _ = example(&[1, 2, 3]);
     ```
     [`block_comments`] tests these without indentation
 */
diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs
index ed55ac5bf04b..0381865fed45 100644
--- a/crates/ide/src/typing.rs
+++ b/crates/ide/src/typing.rs
@@ -75,7 +75,10 @@ pub(crate) fn on_char_typed(
     // FIXME: We are hitting the database here, if we are unlucky this call might block momentarily
     // causing the editor to feel sluggish!
     let edition = Edition::CURRENT_FIXME;
-    let editioned_file_id_wrapper = EditionedFileId::new(db, position.file_id, edition);
+    let editioned_file_id_wrapper = EditionedFileId::from_span_guess_origin(
+        db,
+        span::EditionedFileId::new(position.file_id, edition),
+    );
     let file = &db.parse(editioned_file_id_wrapper);
     let char_matches_position =
         file.tree().syntax().text().char_at(position.offset) == Some(char_typed);
diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs
index fdc583a15cc7..76a2802d294c 100644
--- a/crates/ide/src/typing/on_enter.rs
+++ b/crates/ide/src/typing/on_enter.rs
@@ -51,7 +51,7 @@ use ide_db::text_edit::TextEdit;
 // ![On Enter](https://user-images.githubusercontent.com/48062697/113065578-04c21800-91b1-11eb-82b8-22b8c481e645.gif)
 pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option {
     let editioned_file_id_wrapper =
-        ide_db::base_db::EditionedFileId::current_edition(db, position.file_id);
+        ide_db::base_db::EditionedFileId::current_edition_guess_origin(db, position.file_id);
     let parse = db.parse(editioned_file_id_wrapper);
     let file = parse.tree();
     let token = file.syntax().token_at_offset(position.offset).left_biased()?;
diff --git a/crates/ide/src/view_item_tree.rs b/crates/ide/src/view_item_tree.rs
index 2cd751463bdb..c9a2f31696f4 100644
--- a/crates/ide/src/view_item_tree.rs
+++ b/crates/ide/src/view_item_tree.rs
@@ -12,6 +12,6 @@ pub(crate) fn view_item_tree(db: &RootDatabase, file_id: FileId) -> String {
     let sema = Semantics::new(db);
     let file_id = sema
         .attach_first_edition(file_id)
-        .unwrap_or_else(|| EditionedFileId::current_edition(db, file_id));
+        .unwrap_or_else(|| EditionedFileId::current_edition_guess_origin(db, file_id));
     db.file_item_tree(file_id.into()).pretty_print(db, file_id.edition(db))
 }
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index 97886844a9f9..b941dd7965f4 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -143,7 +143,9 @@ impl flags::AnalysisStats {
                     if !source_root.is_library || self.with_deps {
                         let length = db.file_text(file_id).text(db).lines().count();
                         let item_stats = db
-                            .file_item_tree(EditionedFileId::current_edition(db, file_id).into())
+                            .file_item_tree(
+                                EditionedFileId::current_edition_guess_origin(db, file_id).into(),
+                            )
                             .item_tree_stats()
                             .into();
 
@@ -153,7 +155,9 @@ impl flags::AnalysisStats {
                     } else {
                         let length = db.file_text(file_id).text(db).lines().count();
                         let item_stats = db
-                            .file_item_tree(EditionedFileId::current_edition(db, file_id).into())
+                            .file_item_tree(
+                                EditionedFileId::current_edition_guess_origin(db, file_id).into(),
+                            )
                             .item_tree_stats()
                             .into();
 
diff --git a/crates/rust-analyzer/src/cli/scip.rs b/crates/rust-analyzer/src/cli/scip.rs
index 37f83f6dee67..92bb2c1ce4fa 100644
--- a/crates/rust-analyzer/src/cli/scip.rs
+++ b/crates/rust-analyzer/src/cli/scip.rs
@@ -514,12 +514,12 @@ mod test {
 
     fn position(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> (AnalysisHost, FilePosition) {
         let mut host = AnalysisHost::default();
-        let change_fixture = ChangeFixture::parse(host.raw_database(), ra_fixture);
+        let change_fixture = ChangeFixture::parse(ra_fixture);
         host.raw_database_mut().apply_change(change_fixture.change);
         let (file_id, range_or_offset) =
             change_fixture.file_position.expect("expected a marker ()");
         let offset = range_or_offset.expect_offset();
-        let position = FilePosition { file_id: file_id.file_id(host.raw_database()), offset };
+        let position = FilePosition { file_id: file_id.file_id(), offset };
         (host, position)
     }
 
@@ -870,7 +870,7 @@ pub mod example_mod {
         let s = "/// foo\nfn bar() {}";
 
         let mut host = AnalysisHost::default();
-        let change_fixture = ChangeFixture::parse(host.raw_database(), s);
+        let change_fixture = ChangeFixture::parse(s);
         host.raw_database_mut().apply_change(change_fixture.change);
 
         let analysis = host.analysis();
diff --git a/crates/rust-analyzer/src/cli/ssr.rs b/crates/rust-analyzer/src/cli/ssr.rs
index e3e3a143de03..cc2ab0f07ca0 100644
--- a/crates/rust-analyzer/src/cli/ssr.rs
+++ b/crates/rust-analyzer/src/cli/ssr.rs
@@ -73,7 +73,7 @@ impl flags::Search {
                 let sr = db.source_root(root).source_root(db);
                 for file_id in sr.iter() {
                     for debug_info in match_finder.debug_where_text_equal(
-                        EditionedFileId::current_edition(db, file_id),
+                        EditionedFileId::current_edition_guess_origin(db, file_id),
                         debug_snippet,
                     ) {
                         println!("{debug_info:#?}");
diff --git a/crates/rust-analyzer/src/cli/unresolved_references.rs b/crates/rust-analyzer/src/cli/unresolved_references.rs
index 0362e13b88b7..2cb0fe9eefad 100644
--- a/crates/rust-analyzer/src/cli/unresolved_references.rs
+++ b/crates/rust-analyzer/src/cli/unresolved_references.rs
@@ -141,7 +141,7 @@ fn all_unresolved_references(
 ) -> Vec {
     let file_id = sema
         .attach_first_edition(file_id)
-        .unwrap_or_else(|| EditionedFileId::current_edition(sema.db, file_id));
+        .unwrap_or_else(|| EditionedFileId::current_edition_guess_origin(sema.db, file_id));
     let file = sema.parse(file_id);
     let root = file.syntax();
 
diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs
index d51ddb86d197..0d2366a52b3c 100644
--- a/crates/rust-analyzer/src/lsp/to_proto.rs
+++ b/crates/rust-analyzer/src/lsp/to_proto.rs
@@ -117,7 +117,7 @@ pub(crate) fn diagnostic_severity(severity: Severity) -> lsp_types::DiagnosticSe
     }
 }
 
-pub(crate) fn documentation(documentation: Documentation) -> lsp_types::Documentation {
+pub(crate) fn documentation(documentation: Documentation<'_>) -> lsp_types::Documentation {
     let value = format_docs(&documentation);
     let markup_content = lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value };
     lsp_types::Documentation::MarkupContent(markup_content)
@@ -1960,7 +1960,7 @@ pub(crate) fn markup_content(
         ide::HoverDocFormat::Markdown => lsp_types::MarkupKind::Markdown,
         ide::HoverDocFormat::PlainText => lsp_types::MarkupKind::PlainText,
     };
-    let value = format_docs(&Documentation::new(markup.into()));
+    let value = format_docs(&Documentation::new_owned(markup.into()));
     lsp_types::MarkupContent { kind, value }
 }
 
diff --git a/crates/syntax-bridge/src/lib.rs b/crates/syntax-bridge/src/lib.rs
index 4e525be3fe3c..2d1955d1f651 100644
--- a/crates/syntax-bridge/src/lib.rs
+++ b/crates/syntax-bridge/src/lib.rs
@@ -1,6 +1,6 @@
 //! Conversions between [`SyntaxNode`] and [`tt::TokenTree`].
 
-use std::{fmt, hash::Hash};
+use std::{collections::VecDeque, fmt, hash::Hash};
 
 use intern::Symbol;
 use rustc_hash::{FxHashMap, FxHashSet};
@@ -102,26 +102,34 @@ where
     SpanData: Copy + fmt::Debug,
     SpanMap: SpanMapper>,
 {
-    let mut c = Converter::new(node, map, Default::default(), Default::default(), span, mode);
+    let mut c =
+        Converter::new(node, map, Default::default(), Default::default(), span, mode, |_, _| {
+            (true, Vec::new())
+        });
     convert_tokens(&mut c)
 }
 
 /// Converts a syntax tree to a [`tt::Subtree`] using the provided span map to populate the
 /// subtree's spans. Additionally using the append and remove parameters, the additional tokens can
 /// be injected or hidden from the output.
-pub fn syntax_node_to_token_tree_modified(
+pub fn syntax_node_to_token_tree_modified(
     node: &SyntaxNode,
     map: SpanMap,
     append: FxHashMap>>>,
     remove: FxHashSet,
     call_site: SpanData,
     mode: DocCommentDesugarMode,
+    on_enter: OnEvent,
 ) -> tt::TopSubtree>
 where
     SpanMap: SpanMapper>,
     SpanData: Copy + fmt::Debug,
+    OnEvent: FnMut(
+        &mut PreorderWithTokens,
+        &WalkEvent,
+    ) -> (bool, Vec>>),
 {
-    let mut c = Converter::new(node, map, append, remove, call_site, mode);
+    let mut c = Converter::new(node, map, append, remove, call_site, mode, on_enter);
     convert_tokens(&mut c)
 }
 
@@ -624,9 +632,9 @@ where
     }
 }
 
-struct Converter {
+struct Converter {
     current: Option,
-    current_leaves: Vec>,
+    current_leaves: VecDeque>,
     preorder: PreorderWithTokens,
     range: TextRange,
     punct_offset: Option<(SyntaxToken, TextSize)>,
@@ -636,9 +644,13 @@ struct Converter {
     remove: FxHashSet,
     call_site: S,
     mode: DocCommentDesugarMode,
+    on_event: OnEvent,
 }
 
-impl Converter {
+impl Converter
+where
+    OnEvent: FnMut(&mut PreorderWithTokens, &WalkEvent) -> (bool, Vec>),
+{
     fn new(
         node: &SyntaxNode,
         map: SpanMap,
@@ -646,8 +658,9 @@ impl Converter {
         remove: FxHashSet,
         call_site: S,
         mode: DocCommentDesugarMode,
+        on_enter: OnEvent,
     ) -> Self {
-        let mut this = Converter {
+        let mut converter = Converter {
             current: None,
             preorder: node.preorder_with_tokens(),
             range: node.text_range(),
@@ -656,16 +669,21 @@ impl Converter {
             append,
             remove,
             call_site,
-            current_leaves: vec![],
+            current_leaves: VecDeque::new(),
             mode,
+            on_event: on_enter,
         };
-        let first = this.next_token();
-        this.current = first;
-        this
+        converter.current = converter.next_token();
+        converter
     }
 
     fn next_token(&mut self) -> Option {
         while let Some(ev) = self.preorder.next() {
+            let (keep_event, insert_leaves) = (self.on_event)(&mut self.preorder, &ev);
+            self.current_leaves.extend(insert_leaves);
+            if !keep_event {
+                continue;
+            }
             match ev {
                 WalkEvent::Enter(token) => {
                     if self.remove.contains(&token) {
@@ -675,10 +693,9 @@ impl Converter {
                             }
                             node => {
                                 self.preorder.skip_subtree();
-                                if let Some(mut v) = self.append.remove(&node) {
-                                    v.reverse();
+                                if let Some(v) = self.append.remove(&node) {
                                     self.current_leaves.extend(v);
-                                    return None;
+                                    continue;
                                 }
                             }
                         }
@@ -687,10 +704,9 @@ impl Converter {
                     }
                 }
                 WalkEvent::Leave(ele) => {
-                    if let Some(mut v) = self.append.remove(&ele) {
-                        v.reverse();
+                    if let Some(v) = self.append.remove(&ele) {
                         self.current_leaves.extend(v);
-                        return None;
+                        continue;
                     }
                 }
             }
@@ -715,8 +731,8 @@ impl SynToken {
     }
 }
 
-impl SrcToken, S> for SynToken {
-    fn kind(&self, _ctx: &Converter) -> SyntaxKind {
+impl SrcToken, S> for SynToken {
+    fn kind(&self, _ctx: &Converter) -> SyntaxKind {
         match self {
             SynToken::Ordinary(token) => token.kind(),
             SynToken::Punct { token, offset: i } => {
@@ -728,14 +744,14 @@ impl SrcToken, S> for SynToken {
             }
         }
     }
-    fn to_char(&self, _ctx: &Converter) -> Option {
+    fn to_char(&self, _ctx: &Converter) -> Option {
         match self {
             SynToken::Ordinary(_) => None,
             SynToken::Punct { token: it, offset: i } => it.text().chars().nth(*i),
             SynToken::Leaf(_) => None,
         }
     }
-    fn to_text(&self, _ctx: &Converter) -> SmolStr {
+    fn to_text(&self, _ctx: &Converter) -> SmolStr {
         match self {
             SynToken::Ordinary(token) | SynToken::Punct { token, offset: _ } => token.text().into(),
             SynToken::Leaf(_) => {
@@ -752,10 +768,11 @@ impl SrcToken, S> for SynToken {
     }
 }
 
-impl TokenConverter for Converter
+impl TokenConverter for Converter
 where
     S: Copy,
     SpanMap: SpanMapper,
+    OnEvent: FnMut(&mut PreorderWithTokens, &WalkEvent) -> (bool, Vec>),
 {
     type Token = SynToken;
     fn convert_doc_comment(
@@ -781,10 +798,7 @@ where
             ));
         }
 
-        if let Some(leaf) = self.current_leaves.pop() {
-            if self.current_leaves.is_empty() {
-                self.current = self.next_token();
-            }
+        if let Some(leaf) = self.current_leaves.pop_front() {
             return Some((SynToken::Leaf(leaf), TextRange::empty(TextSize::new(0))));
         }
 
diff --git a/crates/syntax/src/ast.rs b/crates/syntax/src/ast.rs
index 19c1c5ebea33..9220c74ab731 100644
--- a/crates/syntax/src/ast.rs
+++ b/crates/syntax/src/ast.rs
@@ -26,13 +26,15 @@ pub use self::{
     generated::{nodes::*, tokens::*},
     node_ext::{
         AttrKind, FieldKind, Macro, NameLike, NameOrNameRef, PathSegmentKind, SelfParamKind,
-        SlicePatComponents, StructKind, TypeBoundKind, TypeOrConstParam, VisibilityKind,
+        SlicePatComponents, StructKind, TokenTreeChildren, TypeBoundKind, TypeOrConstParam,
+        VisibilityKind,
     },
     operators::{ArithOp, BinaryOp, CmpOp, LogicOp, Ordering, RangeOp, UnaryOp},
     token_ext::{CommentKind, CommentPlacement, CommentShape, IsString, QuoteOffsets, Radix},
     traits::{
         AttrDocCommentIter, DocCommentIter, HasArgList, HasAttrs, HasDocComments, HasGenericArgs,
         HasGenericParams, HasLoopBody, HasModuleItem, HasName, HasTypeBounds, HasVisibility,
+        attrs_including_inner,
     },
 };
 
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index 42b0f5cf2da1..0cbeef3c83f9 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -10,7 +10,7 @@ use parser::SyntaxKind;
 use rowan::{GreenNodeData, GreenTokenData};
 
 use crate::{
-    NodeOrToken, SmolStr, SyntaxElement, SyntaxToken, T, TokenText,
+    NodeOrToken, SmolStr, SyntaxElement, SyntaxElementChildren, SyntaxToken, T, TokenText,
     ast::{
         self, AstNode, AstToken, HasAttrs, HasGenericArgs, HasGenericParams, HasName, SyntaxNode,
         support,
@@ -1115,3 +1115,39 @@ impl ast::OrPat {
             .filter(|it| it.kind() == T![|])
     }
 }
+
+/// An iterator over the elements in an [`ast::TokenTree`].
+///
+/// Does not yield trivia or the delimiters.
+#[derive(Clone)]
+pub struct TokenTreeChildren {
+    iter: SyntaxElementChildren,
+}
+
+impl TokenTreeChildren {
+    #[inline]
+    pub fn new(tt: &ast::TokenTree) -> Self {
+        let mut iter = tt.syntax.children_with_tokens();
+        iter.next(); // Bump the opening delimiter.
+        Self { iter }
+    }
+}
+
+impl Iterator for TokenTreeChildren {
+    type Item = NodeOrToken;
+
+    #[inline]
+    fn next(&mut self) -> Option {
+        self.iter.find_map(|item| match item {
+            NodeOrToken::Node(node) => ast::TokenTree::cast(node).map(NodeOrToken::Node),
+            NodeOrToken::Token(token) => {
+                let kind = token.kind();
+                (!matches!(
+                    kind,
+                    SyntaxKind::WHITESPACE | SyntaxKind::COMMENT | T![')'] | T![']'] | T!['}']
+                ))
+                .then_some(NodeOrToken::Token(token))
+            }
+        })
+    }
+}
diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs
index 4afdda78a0e7..3ca46e375fb4 100644
--- a/crates/syntax/src/ast/token_ext.rs
+++ b/crates/syntax/src/ast/token_ext.rs
@@ -40,8 +40,8 @@ impl ast::Comment {
     }
 
     /// Returns the textual content of a doc comment node as a single string with prefix and suffix
-    /// removed.
-    pub fn doc_comment(&self) -> Option<&str> {
+    /// removed, plus the offset of the returned string from the beginning of the comment.
+    pub fn doc_comment(&self) -> Option<(&str, TextSize)> {
         let kind = self.kind();
         match kind {
             CommentKind { shape, doc: Some(_) } => {
@@ -52,7 +52,7 @@ impl ast::Comment {
                 } else {
                     text
                 };
-                Some(text)
+                Some((text, TextSize::of(prefix)))
             }
             _ => None,
         }
diff --git a/crates/syntax/src/ast/traits.rs b/crates/syntax/src/ast/traits.rs
index 5290f32dd27d..fb23a4cace2d 100644
--- a/crates/syntax/src/ast/traits.rs
+++ b/crates/syntax/src/ast/traits.rs
@@ -4,8 +4,9 @@
 use either::Either;
 
 use crate::{
-    SyntaxElement, SyntaxToken, T,
+    SyntaxElement, SyntaxNode, SyntaxToken, T,
     ast::{self, AstChildren, AstNode, AstToken, support},
+    match_ast,
     syntax_node::SyntaxElementChildren,
 };
 
@@ -76,34 +77,38 @@ pub trait HasAttrs: AstNode {
         self.attrs().filter_map(|x| x.as_simple_atom()).any(|x| x == atom)
     }
 
-    /// Returns all attributes of this node, including inner attributes that may not be directly under this node
-    /// but under a child.
-    fn attrs_including_inner(self) -> impl Iterator
-    where
-        Self: Sized,
-    {
-        let inner_attrs_node = if let Some(it) =
-            support::child::(self.syntax()).and_then(|it| it.stmt_list())
-        {
-            Some(it.syntax)
-        } else if let Some(it) = support::child::(self.syntax()) {
-            Some(it.syntax)
-        } else if let Some(it) = support::child::(self.syntax()) {
-            Some(it.syntax)
-        } else if let Some(it) = support::child::(self.syntax()) {
-            Some(it.syntax)
-        } else if let Some(it) = support::child::(self.syntax()) {
-            Some(it.syntax)
-        } else if let Some(it) = support::child::(self.syntax()) {
-            Some(it.syntax)
-        } else {
-            None
-        };
-
-        self.attrs().chain(inner_attrs_node.into_iter().flat_map(|it| support::children(&it)))
+    /// This may return the same node as called with (with `SourceFile`). The caller has the responsibility
+    /// to avoid duplicate attributes.
+    fn inner_attributes_node(&self) -> Option {
+        let syntax = self.syntax();
+        Some(match_ast! {
+            match syntax {
+                // A `SourceFile` contains the inner attributes of itself.
+                ast::SourceFile(_) => syntax.clone(),
+                ast::ExternBlock(it) => it.extern_item_list()?.syntax().clone(),
+                ast::Fn(it) => it.body()?.stmt_list()?.syntax().clone(),
+                ast::MatchExpr(it) => it.match_arm_list()?.syntax().clone(),
+                ast::Impl(it) => it.assoc_item_list()?.syntax().clone(),
+                ast::Trait(it) => it.assoc_item_list()?.syntax().clone(),
+                ast::Module(it) => it.item_list()?.syntax().clone(),
+                _ => return None,
+            }
+        })
     }
 }
 
+/// Returns all attributes of this node, including inner attributes that may not be directly under this node
+/// but under a child.
+pub fn attrs_including_inner(owner: &dyn HasAttrs) -> impl Iterator + Clone {
+    owner.attrs().filter(|attr| attr.kind().is_outer()).chain(
+        owner
+            .inner_attributes_node()
+            .into_iter()
+            .flat_map(|node| support::children::(&node))
+            .filter(|attr| attr.kind().is_inner()),
+    )
+}
+
 pub trait HasDocComments: HasAttrs {
     fn doc_comments(&self) -> DocCommentIter {
         DocCommentIter { iter: self.syntax().children_with_tokens() }
@@ -118,7 +123,7 @@ impl DocCommentIter {
     #[cfg(test)]
     pub fn doc_comment_text(self) -> Option {
         let docs = itertools::Itertools::join(
-            &mut self.filter_map(|comment| comment.doc_comment().map(ToOwned::to_owned)),
+            &mut self.filter_map(|comment| comment.doc_comment().map(|it| it.0.to_owned())),
             "\n",
         );
         if docs.is_empty() { None } else { Some(docs) }
@@ -151,7 +156,7 @@ impl AttrDocCommentIter {
 impl Iterator for AttrDocCommentIter {
     type Item = Either;
     fn next(&mut self) -> Option {
-        self.iter.by_ref().find_map(|el| match el {
+        self.iter.find_map(|el| match el {
             SyntaxElement::Node(node) => ast::Attr::cast(node).map(Either::Left),
             SyntaxElement::Token(tok) => {
                 ast::Comment::cast(tok).filter(ast::Comment::is_doc).map(Either::Right)
diff --git a/crates/test-fixture/src/lib.rs b/crates/test-fixture/src/lib.rs
index 4413d2f222c1..dab56efd4de4 100644
--- a/crates/test-fixture/src/lib.rs
+++ b/crates/test-fixture/src/lib.rs
@@ -4,7 +4,7 @@ use std::{any::TypeId, mem, str::FromStr, sync};
 use base_db::{
     Crate, CrateDisplayName, CrateGraphBuilder, CrateName, CrateOrigin, CrateWorkspaceData,
     DependencyBuilder, Env, FileChange, FileSet, LangCrateOrigin, SourceDatabase, SourceRoot,
-    Version, VfsPath, salsa,
+    Version, VfsPath,
 };
 use cfg::CfgOptions;
 use hir_expand::{
@@ -37,10 +37,11 @@ pub trait WithFixture: Default + ExpandDatabase + SourceDatabase + 'static {
         #[rust_analyzer::rust_fixture] ra_fixture: &str,
     ) -> (Self, EditionedFileId) {
         let mut db = Self::default();
-        let fixture = ChangeFixture::parse(&db, ra_fixture);
+        let fixture = ChangeFixture::parse(ra_fixture);
         fixture.change.apply(&mut db);
         assert_eq!(fixture.files.len(), 1, "Multiple file found in the fixture");
-        (db, fixture.files[0])
+        let file = EditionedFileId::from_span_guess_origin(&db, fixture.files[0]);
+        (db, file)
     }
 
     #[track_caller]
@@ -48,16 +49,21 @@ pub trait WithFixture: Default + ExpandDatabase + SourceDatabase + 'static {
         #[rust_analyzer::rust_fixture] ra_fixture: &str,
     ) -> (Self, Vec) {
         let mut db = Self::default();
-        let fixture = ChangeFixture::parse(&db, ra_fixture);
+        let fixture = ChangeFixture::parse(ra_fixture);
         fixture.change.apply(&mut db);
         assert!(fixture.file_position.is_none());
-        (db, fixture.files)
+        let files = fixture
+            .files
+            .into_iter()
+            .map(|file| EditionedFileId::from_span_guess_origin(&db, file))
+            .collect();
+        (db, files)
     }
 
     #[track_caller]
     fn with_files(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> Self {
         let mut db = Self::default();
-        let fixture = ChangeFixture::parse(&db, ra_fixture);
+        let fixture = ChangeFixture::parse(ra_fixture);
         fixture.change.apply(&mut db);
         assert!(fixture.file_position.is_none());
         db
@@ -69,7 +75,7 @@ pub trait WithFixture: Default + ExpandDatabase + SourceDatabase + 'static {
         proc_macros: Vec<(String, ProcMacro)>,
     ) -> Self {
         let mut db = Self::default();
-        let fixture = ChangeFixture::parse_with_proc_macros(&db, ra_fixture, proc_macros);
+        let fixture = ChangeFixture::parse_with_proc_macros(ra_fixture, proc_macros);
         fixture.change.apply(&mut db);
         assert!(fixture.file_position.is_none());
         db
@@ -94,12 +100,13 @@ pub trait WithFixture: Default + ExpandDatabase + SourceDatabase + 'static {
         #[rust_analyzer::rust_fixture] ra_fixture: &str,
     ) -> (Self, EditionedFileId, RangeOrOffset) {
         let mut db = Self::default();
-        let fixture = ChangeFixture::parse(&db, ra_fixture);
+        let fixture = ChangeFixture::parse(ra_fixture);
         fixture.change.apply(&mut db);
 
         let (file_id, range_or_offset) = fixture
             .file_position
             .expect("Could not find file position in fixture. Did you forget to add an `$0`?");
+        let file_id = EditionedFileId::from_span_guess_origin(&db, file_id);
         (db, file_id, range_or_offset)
     }
 
@@ -111,23 +118,19 @@ pub trait WithFixture: Default + ExpandDatabase + SourceDatabase + 'static {
 impl WithFixture for DB {}
 
 pub struct ChangeFixture {
-    pub file_position: Option<(EditionedFileId, RangeOrOffset)>,
-    pub files: Vec,
+    pub file_position: Option<(span::EditionedFileId, RangeOrOffset)>,
+    pub files: Vec,
     pub change: ChangeWithProcMacros,
 }
 
 const SOURCE_ROOT_PREFIX: &str = "/";
 
 impl ChangeFixture {
-    pub fn parse(
-        db: &dyn salsa::Database,
-        #[rust_analyzer::rust_fixture] ra_fixture: &str,
-    ) -> ChangeFixture {
-        Self::parse_with_proc_macros(db, ra_fixture, Vec::new())
+    pub fn parse(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> ChangeFixture {
+        Self::parse_with_proc_macros(ra_fixture, Vec::new())
     }
 
     pub fn parse_with_proc_macros(
-        db: &dyn salsa::Database,
         #[rust_analyzer::rust_fixture] ra_fixture: &str,
         mut proc_macro_defs: Vec<(String, ProcMacro)>,
     ) -> ChangeFixture {
@@ -188,7 +191,7 @@ impl ChangeFixture {
             let meta = FileMeta::from_fixture(entry, current_source_root_kind);
             if let Some(range_or_offset) = range_or_offset {
                 file_position =
-                    Some((EditionedFileId::new(db, file_id, meta.edition), range_or_offset));
+                    Some((span::EditionedFileId::new(file_id, meta.edition), range_or_offset));
             }
 
             assert!(meta.path.starts_with(SOURCE_ROOT_PREFIX));
@@ -245,7 +248,7 @@ impl ChangeFixture {
             source_change.change_file(file_id, Some(text));
             let path = VfsPath::new_virtual_path(meta.path);
             file_set.insert(file_id, path);
-            files.push(EditionedFileId::new(db, file_id, meta.edition));
+            files.push(span::EditionedFileId::new(file_id, meta.edition));
             file_id = FileId::from_raw(file_id.index() + 1);
         }