Skip to content

Commit 455ca02

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 57875bd commit 455ca02

File tree

137 files changed

+4953
-3805
lines changed

Some content is hidden

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

137 files changed

+4953
-3805
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
@@ -52,7 +52,7 @@ debug = 2
5252
# local crates
5353
macros = { path = "./crates/macros", version = "0.0.0" }
5454
base-db = { path = "./crates/base-db", version = "0.0.0" }
55-
cfg = { path = "./crates/cfg", version = "0.0.0", features = ["tt"] }
55+
cfg = { path = "./crates/cfg", version = "0.0.0", features = ["tt", "syntax"] }
5656
hir = { path = "./crates/hir", version = "0.0.0" }
5757
hir-def = { path = "./crates/hir-def", version = "0.0.0" }
5858
hir-expand = { path = "./crates/hir-expand", version = "0.0.0" }
@@ -131,7 +131,7 @@ process-wrap = { version = "8.2.1", features = ["std"] }
131131
pulldown-cmark-to-cmark = "10.0.4"
132132
pulldown-cmark = { version = "0.9.6", default-features = false }
133133
rayon = "1.10.0"
134-
rowan = "=0.15.15"
134+
rowan = "=0.15.17"
135135
# Ideally we'd not enable the macros feature but unfortunately the `tracked` attribute does not work
136136
# on impls without it
137137
salsa = { version = "0.24.0", default-features = true, features = [
@@ -167,6 +167,7 @@ tracing-subscriber = { version = "0.3.20", default-features = false, features =
167167
triomphe = { version = "0.1.14", default-features = false, features = ["std"] }
168168
url = "2.5.4"
169169
xshell = "0.2.7"
170+
thin-vec = "0.2.14"
170171
petgraph = { version = "0.8.2", default-features = false }
171172

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

crates/base-db/src/input.rs

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

832-
impl BuiltCrateData {
833-
pub fn root_file_id(&self, db: &dyn salsa::Database) -> EditionedFileId {
834-
EditionedFileId::new(db, self.root_file_id, self.edition)
832+
impl Crate {
833+
pub fn root_file_id(self, db: &dyn salsa::Database) -> EditionedFileId {
834+
let data = self.data(db);
835+
EditionedFileId::new(db, data.root_file_id, data.edition, self)
835836
}
836837
}
837838

0 commit comments

Comments
 (0)