Skip to content

Commit db72e2f

Browse files
Adjust for salsa::Id::from_u32() being unsafe
This impacts our manual `salsa::Id` wrappers. I refactored them a bit to improve safety.
1 parent 2bba385 commit db72e2f

File tree

5 files changed

+80
-183
lines changed

5 files changed

+80
-183
lines changed

crates/hir-expand/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1051,7 +1051,7 @@ impl ExpandTo {
10511051

10521052
intern::impl_internable!(ModPath, attrs::AttrInput);
10531053

1054-
#[salsa::interned(no_lifetime)]
1054+
#[salsa::interned(no_lifetime, debug)]
10551055
#[doc(alias = "MacroFileId")]
10561056
pub struct MacroCallId {
10571057
pub loc: MacroCallLoc,

crates/hir-ty/src/mapping.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ pub fn from_assoc_type_id(id: AssocTypeId) -> TypeAliasId {
136136

137137
pub fn from_placeholder_idx(db: &dyn HirDatabase, idx: PlaceholderIndex) -> TypeOrConstParamId {
138138
assert_eq!(idx.ui, chalk_ir::UniverseIndex::ROOT);
139-
let interned_id = FromId::from_id(Id::from_u32(idx.idx.try_into().unwrap()));
139+
// SAFETY: We cannot really encapsulate this unfortunately, so just hope this is sound.
140+
let interned_id = FromId::from_id(unsafe { Id::from_u32(idx.idx.try_into().unwrap()) });
140141
db.lookup_intern_type_or_const_param_id(interned_id)
141142
}
142143

@@ -150,7 +151,8 @@ pub fn to_placeholder_idx(db: &dyn HirDatabase, id: TypeOrConstParamId) -> Place
150151

151152
pub fn lt_from_placeholder_idx(db: &dyn HirDatabase, idx: PlaceholderIndex) -> LifetimeParamId {
152153
assert_eq!(idx.ui, chalk_ir::UniverseIndex::ROOT);
153-
let interned_id = FromId::from_id(Id::from_u32(idx.idx.try_into().unwrap()));
154+
// SAFETY: We cannot really encapsulate this unfortunately, so just hope this is sound.
155+
let interned_id = FromId::from_id(unsafe { Id::from_u32(idx.idx.try_into().unwrap()) });
154156
db.lookup_intern_lifetime_param_id(interned_id)
155157
}
156158

crates/proc-macro-api/src/legacy_protocol/msg/flat.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ pub fn deserialize_span_data_index_map(map: &[u32]) -> SpanDataIndexMap {
7272
ast_id: ErasedFileAstId::from_raw(ast_id),
7373
},
7474
range: TextRange::new(start.into(), end.into()),
75-
ctx: SyntaxContext::from_u32(e),
75+
// SAFETY: We only receive spans from the server. If someone mess up the communication UB can happen,
76+
// but that will be their problem.
77+
ctx: unsafe { SyntaxContext::from_u32(e) },
7678
}
7779
})
7880
.collect()

crates/span/src/hygiene.rs

Lines changed: 72 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ use crate::Edition;
2727
#[cfg(feature = "salsa")]
2828
#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
2929
pub struct SyntaxContext(
30-
salsa::Id,
30+
/// # Invariant
31+
///
32+
/// This is either a valid `salsa::Id` or a root `SyntaxContext`.
33+
u32,
3134
std::marker::PhantomData<&'static salsa::plumbing::interned::Value<SyntaxContext>>,
3235
);
3336

@@ -95,10 +98,11 @@ const _: () = {
9598
type Fields<'a> = SyntaxContextData;
9699
type Struct<'a> = SyntaxContext;
97100
fn struct_from_id<'db>(id: salsa::Id) -> Self::Struct<'db> {
98-
SyntaxContext(id, std::marker::PhantomData)
101+
SyntaxContext::from_salsa_id(id)
99102
}
100103
fn deref_struct(s: Self::Struct<'_>) -> salsa::Id {
101-
s.0
104+
s.as_salsa_id()
105+
.expect("`SyntaxContext::deref_structs()` called on a root `SyntaxContext`")
102106
}
103107
}
104108
impl SyntaxContext {
@@ -115,12 +119,12 @@ const _: () = {
115119
}
116120
impl zalsa_::AsId for SyntaxContext {
117121
fn as_id(&self) -> salsa::Id {
118-
self.0
122+
self.as_salsa_id().expect("`SyntaxContext::as_id()` called on a root `SyntaxContext`")
119123
}
120124
}
121125
impl zalsa_::FromId for SyntaxContext {
122126
fn from_id(id: salsa::Id) -> Self {
123-
Self(id, std::marker::PhantomData)
127+
Self::from_salsa_id(id)
124128
}
125129
}
126130
unsafe impl Send for SyntaxContext {}
@@ -210,90 +214,78 @@ const _: () = {
210214
where
211215
Db: ?Sized + zalsa_::Database,
212216
{
213-
if self.is_root() {
214-
return None;
215-
}
216-
let fields = SyntaxContext::ingredient(db).fields(db.as_dyn_database(), self);
217-
std::clone::Clone::clone(&fields.outer_expn)
217+
let id = self.as_salsa_id()?;
218+
let fields = SyntaxContext::ingredient(db).data(db.as_dyn_database(), id);
219+
fields.outer_expn
218220
}
219221

220222
pub fn outer_transparency<Db>(self, db: &'db Db) -> Transparency
221223
where
222224
Db: ?Sized + zalsa_::Database,
223225
{
224-
if self.is_root() {
225-
return Transparency::Opaque;
226-
}
227-
let fields = SyntaxContext::ingredient(db).fields(db.as_dyn_database(), self);
228-
std::clone::Clone::clone(&fields.outer_transparency)
226+
let Some(id) = self.as_salsa_id() else { return Transparency::Opaque };
227+
let fields = SyntaxContext::ingredient(db).data(db.as_dyn_database(), id);
228+
fields.outer_transparency
229229
}
230230

231231
pub fn edition<Db>(self, db: &'db Db) -> Edition
232232
where
233233
Db: ?Sized + zalsa_::Database,
234234
{
235-
if self.is_root() {
236-
return Edition::from_u32(SyntaxContext::MAX_ID - self.0.as_u32());
235+
match self.as_salsa_id() {
236+
Some(id) => {
237+
let fields = SyntaxContext::ingredient(db).data(db.as_dyn_database(), id);
238+
fields.edition
239+
}
240+
None => Edition::from_u32(SyntaxContext::MAX_ID - self.into_u32()),
237241
}
238-
let fields = SyntaxContext::ingredient(db).fields(db.as_dyn_database(), self);
239-
std::clone::Clone::clone(&fields.edition)
240242
}
241243

242244
pub fn parent<Db>(self, db: &'db Db) -> SyntaxContext
243245
where
244246
Db: ?Sized + zalsa_::Database,
245247
{
246-
if self.is_root() {
247-
return self;
248+
match self.as_salsa_id() {
249+
Some(id) => {
250+
let fields = SyntaxContext::ingredient(db).data(db.as_dyn_database(), id);
251+
fields.parent
252+
}
253+
None => self,
248254
}
249-
let fields = SyntaxContext::ingredient(db).fields(db.as_dyn_database(), self);
250-
std::clone::Clone::clone(&fields.parent)
251255
}
252256

253257
/// This context, but with all transparent and semi-transparent expansions filtered away.
254258
pub fn opaque<Db>(self, db: &'db Db) -> SyntaxContext
255259
where
256260
Db: ?Sized + zalsa_::Database,
257261
{
258-
if self.is_root() {
259-
return self;
262+
match self.as_salsa_id() {
263+
Some(id) => {
264+
let fields = SyntaxContext::ingredient(db).data(db.as_dyn_database(), id);
265+
fields.opaque
266+
}
267+
None => self,
260268
}
261-
let fields = SyntaxContext::ingredient(db).fields(db.as_dyn_database(), self);
262-
std::clone::Clone::clone(&fields.opaque)
263269
}
264270

265271
/// This context, but with all transparent expansions filtered away.
266272
pub fn opaque_and_semitransparent<Db>(self, db: &'db Db) -> SyntaxContext
267273
where
268274
Db: ?Sized + zalsa_::Database,
269275
{
270-
if self.is_root() {
271-
return self;
276+
match self.as_salsa_id() {
277+
Some(id) => {
278+
let fields = SyntaxContext::ingredient(db).data(db.as_dyn_database(), id);
279+
fields.opaque_and_semitransparent
280+
}
281+
None => self,
272282
}
273-
let fields = SyntaxContext::ingredient(db).fields(db.as_dyn_database(), self);
274-
std::clone::Clone::clone(&fields.opaque_and_semitransparent)
275-
}
276-
277-
pub fn default_debug_fmt(this: Self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278-
salsa::with_attached_database(|db| {
279-
let fields = SyntaxContext::ingredient(db).fields(db.as_dyn_database(), this);
280-
let mut f = f.debug_struct("SyntaxContextData");
281-
let f = f.field("outer_expn", &fields.outer_expn);
282-
let f = f.field("outer_transparency", &fields.outer_expn);
283-
let f = f.field("edition", &fields.edition);
284-
let f = f.field("parent", &fields.parent);
285-
let f = f.field("opaque", &fields.opaque);
286-
let f = f.field("opaque_and_semitransparent", &fields.opaque_and_semitransparent);
287-
f.finish()
288-
})
289-
.unwrap_or_else(|| {
290-
f.debug_tuple("SyntaxContextData").field(&zalsa_::AsId::as_id(&this)).finish()
291-
})
292283
}
293284
}
294285
};
295286

296287
impl SyntaxContext {
288+
#[inline]
297289
pub fn is_root(self) -> bool {
298290
(SyntaxContext::MAX_ID - Edition::LATEST as u32) <= self.into_u32()
299291
&& self.into_u32() <= (SyntaxContext::MAX_ID - Edition::Edition2015 as u32)
@@ -307,22 +299,46 @@ impl SyntaxContext {
307299
}
308300

309301
/// The root context, which is the parent of all other contexts. All [`FileId`]s have this context.
302+
#[inline]
310303
pub const fn root(edition: Edition) -> Self {
311304
let edition = edition as u32;
312-
SyntaxContext::from_u32(SyntaxContext::MAX_ID - edition)
305+
// SAFETY: Roots are valid `SyntaxContext`s
306+
unsafe { SyntaxContext::from_u32(SyntaxContext::MAX_ID - edition) }
313307
}
314308
}
315309

316310
#[cfg(feature = "salsa")]
317311
impl SyntaxContext {
318312
const MAX_ID: u32 = salsa::Id::MAX_U32 - 1;
319313

314+
#[inline]
320315
pub const fn into_u32(self) -> u32 {
321-
self.0.as_u32()
316+
self.0
322317
}
323318

324-
pub const fn from_u32(u32: u32) -> Self {
325-
Self(salsa::Id::from_u32(u32), std::marker::PhantomData)
319+
/// # Safety
320+
///
321+
/// The ID must be a valid `SyntaxContext`.
322+
#[inline]
323+
pub const unsafe fn from_u32(u32: u32) -> Self {
324+
// INVARIANT: Our precondition.
325+
Self(u32, std::marker::PhantomData)
326+
}
327+
328+
#[inline]
329+
fn as_salsa_id(self) -> Option<salsa::Id> {
330+
if self.is_root() {
331+
None
332+
} else {
333+
// SAFETY: By our invariant, this is either a root (which we verified it's not) or a valid `salsa::Id`.
334+
unsafe { Some(salsa::Id::from_u32(self.0)) }
335+
}
336+
}
337+
338+
#[inline]
339+
fn from_salsa_id(id: salsa::Id) -> Self {
340+
// SAFETY: This comes from a Salsa ID.
341+
unsafe { Self::from_u32(id.as_u32()) }
326342
}
327343
}
328344
#[cfg(not(feature = "salsa"))]
@@ -342,7 +358,10 @@ impl SyntaxContext {
342358
self.0
343359
}
344360

345-
pub const fn from_u32(u32: u32) -> Self {
361+
/// # Safety
362+
///
363+
/// None. This is always safe to call without the `salsa` feature.
364+
pub const unsafe fn from_u32(u32: u32) -> Self {
346365
Self(u32)
347366
}
348367
}

0 commit comments

Comments
 (0)