Skip to content

Commit fccfb8c

Browse files
Rewrite attribute handling
Basically, we switch to expanding cfg_attr in AST form, filter irrelevant attributes from the item tree, and move hir-def attributes (non-item-tree) to be flag-based. The main motivation is memory usage, although this also simplifies the code, and fixes some bugs around handling of `cfg_attr`s.
1 parent ea413f6 commit fccfb8c

File tree

135 files changed

+4800
-3735
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

135 files changed

+4800
-3735
lines changed

Cargo.lock

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ debug = 2
5555
[workspace.dependencies]
5656
# local crates
5757
base-db = { path = "./crates/base-db", version = "0.0.0" }
58-
cfg = { path = "./crates/cfg", version = "0.0.0", features = ["tt"] }
58+
cfg = { path = "./crates/cfg", version = "0.0.0", features = ["tt", "syntax"] }
5959
hir = { path = "./crates/hir", version = "0.0.0" }
6060
hir-def = { path = "./crates/hir-def", version = "0.0.0" }
6161
hir-expand = { path = "./crates/hir-expand", version = "0.0.0" }
@@ -135,7 +135,7 @@ process-wrap = { version = "8.2.1", features = ["std"] }
135135
pulldown-cmark-to-cmark = "10.0.4"
136136
pulldown-cmark = { version = "0.9.6", default-features = false }
137137
rayon = "1.10.0"
138-
rowan = "=0.15.15"
138+
rowan = "=0.15.17"
139139
# Ideally we'd not enable the macros feature but unfortunately the `tracked` attribute does not work
140140
# on impls without it
141141
salsa = { version = "0.23.0", default-features = true, features = [
@@ -170,6 +170,7 @@ tracing-subscriber = { version = "0.3.19", default-features = false, features =
170170
triomphe = { version = "0.1.14", default-features = false, features = ["std"] }
171171
url = "2.5.4"
172172
xshell = "0.2.7"
173+
thin-vec = "0.2.14"
173174

174175
# We need to freeze the version of the crate, as the raw-api feature is considered unstable
175176
dashmap = { version = "=6.1.0", features = ["raw-api", "inline"] }
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
//! Defines [`EditionedFileId`], an interned wrapper around [`span::EditionedFileId`] that
2+
//! is interned (so queries can take it) and remembers its crate.
3+
4+
use ::core::num::NonZeroUsize;
5+
use core::fmt;
6+
use std::hash::{Hash, Hasher};
7+
8+
use span::Edition;
9+
use vfs::FileId;
10+
11+
use crate::{Crate, RootQueryDb};
12+
13+
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
14+
pub struct EditionedFileId(
15+
salsa::Id,
16+
std::marker::PhantomData<&'static salsa::plumbing::interned::Value<EditionedFileId>>,
17+
);
18+
19+
const _: () = {
20+
use salsa::plumbing as zalsa_;
21+
use zalsa_::interned as zalsa_struct_;
22+
type Configuration_ = EditionedFileId;
23+
24+
#[derive(Debug, Clone, PartialEq, Eq)]
25+
pub struct EditionedFileIdData {
26+
editioned_file_id: span::EditionedFileId,
27+
krate: Crate,
28+
}
29+
30+
/// We like to include the origin crate in an `EditionedFileId` (for use in the item tree),
31+
/// but this poses us a problem.
32+
///
33+
/// Spans contain `EditionedFileId`s, and we don't want to make them store the crate too
34+
/// because that will increase their size, which will increase memory usage significantly.
35+
/// Furthermore, things using spans do not generally need the crate: they are using the
36+
/// file id for queries like `ast_id_map` or `parse`, which do not care about the crate.
37+
///
38+
/// To solve this, we hash **only the `span::EditionedFileId`**, but on still compare
39+
/// the crate in equality check. This preserves the invariant of `Hash` and `Eq` -
40+
/// although same hashes can be used for different items, same file ids used for multiple
41+
/// crates is a rare thing, and different items always have different hashes. Then,
42+
/// when we only have a `span::EditionedFileId`, we use the `intern()` method to
43+
/// reuse existing file ids, and create new one only if needed. See [`from_span_guess_origin`].
44+
///
45+
/// 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
46+
///
47+
/// [`from_span_guess_origin`]: EditionedFileId::from_span_guess_origin
48+
#[derive(Hash, PartialEq, Eq)]
49+
struct WithoutCrate {
50+
editioned_file_id: span::EditionedFileId,
51+
}
52+
53+
impl Hash for EditionedFileIdData {
54+
#[inline]
55+
fn hash<H: Hasher>(&self, state: &mut H) {
56+
let EditionedFileIdData { editioned_file_id, krate: _ } = *self;
57+
editioned_file_id.hash(state);
58+
}
59+
}
60+
61+
impl zalsa_struct_::HashEqLike<WithoutCrate> for EditionedFileIdData {
62+
#[inline]
63+
fn hash<H: Hasher>(&self, state: &mut H) {
64+
Hash::hash(self, state);
65+
}
66+
67+
#[inline]
68+
fn eq(&self, data: &WithoutCrate) -> bool {
69+
let EditionedFileIdData { editioned_file_id, krate: _ } = *self;
70+
editioned_file_id == data.editioned_file_id
71+
}
72+
}
73+
74+
impl zalsa_struct_::Configuration for EditionedFileId {
75+
const LOCATION: zalsa_::Location = zalsa_::Location { file: file!(), line: 0u32 };
76+
const DEBUG_NAME: &'static str = "EditionedFileId";
77+
const REVISIONS: NonZeroUsize = NonZeroUsize::new(usize::MAX).unwrap();
78+
type Fields<'a> = EditionedFileIdData;
79+
type Struct<'db> = EditionedFileId;
80+
}
81+
82+
impl Configuration_ {
83+
pub fn ingredient(db: &dyn salsa::Database) -> &zalsa_struct_::IngredientImpl<Self> {
84+
static CACHE: zalsa_::IngredientCache<zalsa_struct_::IngredientImpl<Configuration_>> =
85+
zalsa_::IngredientCache::new();
86+
let zalsa = db.zalsa();
87+
CACHE.get_or_create(zalsa, || {
88+
zalsa.lookup_jar_by_type::<zalsa_struct_::JarImpl<Configuration_>>().get_or_create()
89+
})
90+
}
91+
}
92+
93+
impl zalsa_::AsId for EditionedFileId {
94+
fn as_id(&self) -> salsa::Id {
95+
self.0.as_id()
96+
}
97+
}
98+
impl zalsa_::FromId for EditionedFileId {
99+
fn from_id(id: salsa::Id) -> Self {
100+
Self(<salsa::Id>::from_id(id), std::marker::PhantomData)
101+
}
102+
}
103+
104+
unsafe impl Send for EditionedFileId {}
105+
unsafe impl Sync for EditionedFileId {}
106+
107+
impl std::fmt::Debug for EditionedFileId {
108+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109+
Self::default_debug_fmt(*self, f)
110+
}
111+
}
112+
113+
impl zalsa_::SalsaStructInDb for EditionedFileId {
114+
type MemoIngredientMap = zalsa_::MemoIngredientSingletonIndex;
115+
fn lookup_or_create_ingredient_index(aux: &zalsa_::Zalsa) -> zalsa_::IngredientIndices {
116+
aux.lookup_jar_by_type::<zalsa_struct_::JarImpl<Configuration_>>()
117+
.get_or_create()
118+
.into()
119+
}
120+
#[inline]
121+
fn cast(id: zalsa_::Id, type_id: zalsa_::TypeId) -> zalsa_::Option<Self> {
122+
if type_id == zalsa_::TypeId::of::<EditionedFileId>() {
123+
zalsa_::Some(<EditionedFileId as zalsa_::FromId>::from_id(id))
124+
} else {
125+
zalsa_::None
126+
}
127+
}
128+
}
129+
130+
unsafe impl zalsa_::Update for EditionedFileId {
131+
unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
132+
if unsafe { *old_pointer } != new_value {
133+
unsafe { *old_pointer = new_value };
134+
true
135+
} else {
136+
false
137+
}
138+
}
139+
}
140+
141+
impl EditionedFileId {
142+
pub fn from_span(
143+
db: &dyn salsa::Database,
144+
editioned_file_id: span::EditionedFileId,
145+
krate: Crate,
146+
) -> Self {
147+
Configuration_::ingredient(db).intern(
148+
db,
149+
EditionedFileIdData { editioned_file_id, krate },
150+
|_, data| data,
151+
)
152+
}
153+
154+
/// Guesses the crate for the file.
155+
///
156+
/// Only use this if you cannot precisely determine the origin. This can happen in one of two cases:
157+
///
158+
/// 1. The file is not in the module tree.
159+
/// 2. You are latency sensitive and cannot afford calling the def map to precisely compute the origin
160+
/// (e.g. on enter feature, folding, etc.).
161+
pub fn from_span_guess_origin(
162+
db: &dyn RootQueryDb,
163+
editioned_file_id: span::EditionedFileId,
164+
) -> Self {
165+
Configuration_::ingredient(db).intern(db, WithoutCrate { editioned_file_id }, |_, _| {
166+
// FileId not in the database.
167+
let krate = db
168+
.relevant_crates(editioned_file_id.file_id())
169+
.first()
170+
.copied()
171+
.unwrap_or_else(|| db.all_crates()[0]);
172+
EditionedFileIdData { editioned_file_id, krate }
173+
})
174+
}
175+
176+
pub fn editioned_file_id(self, db: &dyn salsa::Database) -> span::EditionedFileId {
177+
let fields = Configuration_::ingredient(db).fields(db, self);
178+
fields.editioned_file_id
179+
}
180+
181+
pub fn krate(self, db: &dyn salsa::Database) -> Crate {
182+
let fields = Configuration_::ingredient(db).fields(db, self);
183+
fields.krate
184+
}
185+
186+
/// Default debug formatting for this struct (may be useful if you define your own `Debug` impl)
187+
pub fn default_debug_fmt(this: Self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188+
zalsa_::with_attached_database(|db| {
189+
let fields = Configuration_::ingredient(db).fields(db, this);
190+
fmt::Debug::fmt(fields, f)
191+
})
192+
.unwrap_or_else(|| {
193+
f.debug_tuple("EditionedFileId").field(&zalsa_::AsId::as_id(&this)).finish()
194+
})
195+
}
196+
}
197+
};
198+
199+
impl EditionedFileId {
200+
#[inline]
201+
pub fn new(db: &dyn salsa::Database, file_id: FileId, edition: Edition, krate: Crate) -> Self {
202+
EditionedFileId::from_span(db, span::EditionedFileId::new(file_id, edition), krate)
203+
}
204+
205+
/// Attaches the current edition and guesses the crate for the file.
206+
///
207+
/// Only use this if you cannot precisely determine the origin. This can happen in one of two cases:
208+
///
209+
/// 1. The file is not in the module tree.
210+
/// 2. You are latency sensitive and cannot afford calling the def map to precisely compute the origin
211+
/// (e.g. on enter feature, folding, etc.).
212+
#[inline]
213+
pub fn current_edition_guess_origin(db: &dyn RootQueryDb, file_id: FileId) -> Self {
214+
Self::from_span_guess_origin(db, span::EditionedFileId::current_edition(file_id))
215+
}
216+
217+
#[inline]
218+
pub fn file_id(self, db: &dyn salsa::Database) -> vfs::FileId {
219+
let id = self.editioned_file_id(db);
220+
id.file_id()
221+
}
222+
223+
#[inline]
224+
pub fn unpack(self, db: &dyn salsa::Database) -> (vfs::FileId, span::Edition) {
225+
let id = self.editioned_file_id(db);
226+
(id.file_id(), id.edition())
227+
}
228+
229+
#[inline]
230+
pub fn edition(self, db: &dyn salsa::Database) -> Edition {
231+
self.editioned_file_id(db).edition()
232+
}
233+
}

crates/base-db/src/input.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -823,9 +823,10 @@ pub(crate) fn transitive_rev_deps(db: &dyn RootQueryDb, of: Crate) -> FxHashSet<
823823
rev_deps
824824
}
825825

826-
impl BuiltCrateData {
827-
pub fn root_file_id(&self, db: &dyn salsa::Database) -> EditionedFileId {
828-
EditionedFileId::new(db, self.root_file_id, self.edition)
826+
impl Crate {
827+
pub fn root_file_id(self, db: &dyn salsa::Database) -> EditionedFileId {
828+
let data = self.data(db);
829+
EditionedFileId::new(db, data.root_file_id, data.edition, self)
829830
}
830831
}
831832

crates/base-db/src/lib.rs

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ pub use salsa_macros;
55

66
// FIXME: Rename this crate, base db is non descriptive
77
mod change;
8+
mod editioned_file_id;
89
mod input;
910

1011
use std::{cell::RefCell, hash::BuildHasherDefault, panic, sync::Once};
1112

1213
pub use crate::{
1314
change::FileChange,
15+
editioned_file_id::EditionedFileId,
1416
input::{
1517
BuiltCrateData, BuiltDependency, Crate, CrateBuilder, CrateBuilderId, CrateDataBuilder,
1618
CrateDisplayName, CrateGraphBuilder, CrateName, CrateOrigin, CratesIdMap, CratesMap,
@@ -24,7 +26,6 @@ pub use query_group::{self};
2426
use rustc_hash::{FxHashSet, FxHasher};
2527
use salsa::{Durability, Setter};
2628
pub use semver::{BuildMetadata, Prerelease, Version, VersionReq};
27-
use span::Edition;
2829
use syntax::{Parse, SyntaxError, ast};
2930
use triomphe::Arc;
3031
pub use vfs::{AnchoredPath, AnchoredPathBuf, FileId, VfsPath, file_set::FileSet};
@@ -168,42 +169,6 @@ impl Files {
168169
}
169170
}
170171

171-
#[salsa_macros::interned(no_lifetime, debug, constructor=from_span, revisions = usize::MAX)]
172-
#[derive(PartialOrd, Ord)]
173-
pub struct EditionedFileId {
174-
pub editioned_file_id: span::EditionedFileId,
175-
}
176-
177-
impl EditionedFileId {
178-
// Salsa already uses the name `new`...
179-
#[inline]
180-
pub fn new(db: &dyn salsa::Database, file_id: FileId, edition: Edition) -> Self {
181-
EditionedFileId::from_span(db, span::EditionedFileId::new(file_id, edition))
182-
}
183-
184-
#[inline]
185-
pub fn current_edition(db: &dyn salsa::Database, file_id: FileId) -> Self {
186-
EditionedFileId::new(db, file_id, Edition::CURRENT)
187-
}
188-
189-
#[inline]
190-
pub fn file_id(self, db: &dyn salsa::Database) -> vfs::FileId {
191-
let id = self.editioned_file_id(db);
192-
id.file_id()
193-
}
194-
195-
#[inline]
196-
pub fn unpack(self, db: &dyn salsa::Database) -> (vfs::FileId, span::Edition) {
197-
let id = self.editioned_file_id(db);
198-
(id.file_id(), id.edition())
199-
}
200-
201-
#[inline]
202-
pub fn edition(self, db: &dyn SourceDatabase) -> Edition {
203-
self.editioned_file_id(db).edition()
204-
}
205-
}
206-
207172
#[salsa_macros::input(debug)]
208173
pub struct FileText {
209174
pub text: Arc<str>,

crates/cfg/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ tracing.workspace = true
1818

1919
# locals deps
2020
tt = { workspace = true, optional = true }
21+
syntax = { workspace = true, optional = true }
2122
intern.workspace = true
2223

2324
[dev-dependencies]

0 commit comments

Comments
 (0)