Skip to content

Commit 9acc026

Browse files
authored
Merge pull request #21566 from Veykril/push-rvmyysvknmyk
fix: Fix `SyntaxContext::root`s technically overlapping valid interneds
2 parents de5824b + 38ff036 commit 9acc026

6 files changed

Lines changed: 137 additions & 58 deletions

File tree

crates/proc-macro-api/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ postcard.workspace = true
3333
semver.workspace = true
3434
rayon.workspace = true
3535

36+
[dev-dependencies]
37+
span.workspace = true
38+
3639
[features]
3740
sysroot-abi = ["proc-macro-srv", "proc-macro-srv/sysroot-abi"]
3841
default = []

crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -189,13 +189,9 @@ impl server::Server for RaSpanServer<'_> {
189189
if first.anchor != second.anchor {
190190
return self.callback.as_mut()?.span_join(first, second);
191191
}
192-
// Differing context, we can't merge these so prefer the one that's root
192+
// Differing context, we can't merge these
193193
if first.ctx != second.ctx {
194-
if first.ctx.is_root() {
195-
return Some(second);
196-
} else if second.ctx.is_root() {
197-
return Some(first);
198-
}
194+
return Some(first);
199195
}
200196
Some(Span {
201197
range: first.range.cover(second.range),

crates/span/src/hygiene.rs

Lines changed: 106 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
//! # The Call-site Hierarchy
2020
//!
2121
//! `ExpnData::call_site` in rustc, `MacroCallLoc::call_site` in rust-analyzer.
22+
#[cfg(feature = "salsa")]
2223
use crate::Edition;
24+
2325
use std::fmt;
2426

2527
/// A syntax context describes a hierarchy tracking order of macro definitions.
@@ -282,7 +284,7 @@ const _: () = {
282284
let fields = SyntaxContext::ingredient(zalsa).data(zalsa, id);
283285
fields.edition
284286
}
285-
None => Edition::from_u32(SyntaxContext::MAX_ID - self.into_u32()),
287+
None => Edition::from_u32(SyntaxContext::MAX_ROOT_ID - self.into_u32()),
286288
}
287289
}
288290

@@ -332,32 +334,9 @@ const _: () = {
332334
}
333335
};
334336

335-
impl SyntaxContext {
336-
#[inline]
337-
pub fn is_root(self) -> bool {
338-
(SyntaxContext::MAX_ID - Edition::LATEST as u32) <= self.into_u32()
339-
&& self.into_u32() <= (SyntaxContext::MAX_ID - Edition::Edition2015 as u32)
340-
}
341-
342-
#[inline]
343-
pub fn remove_root_edition(&mut self) {
344-
if self.is_root() {
345-
*self = Self::root(Edition::Edition2015);
346-
}
347-
}
348-
349-
/// The root context, which is the parent of all other contexts. All `FileId`s have this context.
350-
#[inline]
351-
pub const fn root(edition: Edition) -> Self {
352-
let edition = edition as u32;
353-
// SAFETY: Roots are valid `SyntaxContext`s
354-
unsafe { SyntaxContext::from_u32(SyntaxContext::MAX_ID - edition) }
355-
}
356-
}
357-
358337
#[cfg(feature = "salsa")]
359338
impl<'db> SyntaxContext {
360-
const MAX_ID: u32 = salsa::Id::MAX_U32 - 1;
339+
const MAX_ROOT_ID: u32 = salsa::Id::MAX_U32 + Edition::LATEST as u32;
361340

362341
#[inline]
363342
pub const fn into_u32(self) -> u32 {
@@ -378,7 +357,8 @@ impl<'db> SyntaxContext {
378357
if self.is_root() {
379358
None
380359
} else {
381-
// SAFETY: By our invariant, this is either a root (which we verified it's not) or a valid `salsa::Id`.
360+
// SAFETY: By our invariant, this is either a root (which we verified it's not) or a
361+
// valid `salsa::Id` index.
382362
unsafe { Some(salsa::Id::from_index(self.0)) }
383363
}
384364
}
@@ -389,6 +369,27 @@ impl<'db> SyntaxContext {
389369
unsafe { Self::from_u32(id.index()) }
390370
}
391371

372+
#[inline]
373+
pub fn is_root(self) -> bool {
374+
(SyntaxContext::MAX_ROOT_ID - Edition::LATEST as u32) <= self.into_u32()
375+
&& self.into_u32() <= (SyntaxContext::MAX_ROOT_ID - Edition::Edition2015 as u32)
376+
}
377+
378+
#[inline]
379+
pub fn remove_root_edition(&mut self) {
380+
if self.is_root() {
381+
*self = Self::root(Edition::Edition2015);
382+
}
383+
}
384+
385+
/// The root context, which is the parent of all other contexts. All `FileId`s have this context.
386+
#[inline]
387+
pub const fn root(edition: Edition) -> Self {
388+
let edition = edition as u32;
389+
// SAFETY: Roots are valid `SyntaxContext`s
390+
unsafe { SyntaxContext::from_u32(SyntaxContext::MAX_ROOT_ID - edition) }
391+
}
392+
392393
#[inline]
393394
pub fn outer_mark(
394395
self,
@@ -447,15 +448,8 @@ impl<'db> SyntaxContext {
447448
#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
448449
pub struct SyntaxContext(u32);
449450

450-
#[allow(dead_code)]
451-
const SALSA_MAX_ID_MIRROR: u32 = u32::MAX - 0xFF;
452-
#[cfg(feature = "salsa")]
453-
const _: () = assert!(salsa::Id::MAX_U32 == SALSA_MAX_ID_MIRROR);
454-
455451
#[cfg(not(feature = "salsa"))]
456452
impl SyntaxContext {
457-
const MAX_ID: u32 = SALSA_MAX_ID_MIRROR - 1;
458-
459453
pub const fn into_u32(self) -> u32 {
460454
self.0
461455
}
@@ -496,16 +490,28 @@ impl Transparency {
496490
}
497491
}
498492

493+
#[cfg(feature = "salsa")]
499494
impl fmt::Display for SyntaxContext {
500495
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501496
if self.is_root() {
502-
write!(f, "ROOT{}", Edition::from_u32(SyntaxContext::MAX_ID - self.into_u32()).number())
497+
write!(
498+
f,
499+
"ROOT{}",
500+
Edition::from_u32(SyntaxContext::MAX_ROOT_ID - self.into_u32()).number()
501+
)
503502
} else {
504503
write!(f, "{}", self.into_u32())
505504
}
506505
}
507506
}
508507

508+
#[cfg(not(feature = "salsa"))]
509+
impl fmt::Display for SyntaxContext {
510+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
511+
write!(f, "{}", self.into_u32())
512+
}
513+
}
514+
509515
impl std::fmt::Debug for SyntaxContext {
510516
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
511517
if f.alternate() {
@@ -515,3 +521,69 @@ impl std::fmt::Debug for SyntaxContext {
515521
}
516522
}
517523
}
524+
525+
#[cfg(test)]
526+
mod tests {
527+
use super::*;
528+
529+
#[test]
530+
fn test_root_edition_is_root() {
531+
for edition in Edition::iter() {
532+
let ctx = SyntaxContext::root(edition);
533+
assert!(ctx.is_root(), "{edition} root should be identified as root");
534+
}
535+
}
536+
537+
#[test]
538+
fn test_root_edition_editions() {
539+
let db = salsa::DatabaseImpl::new();
540+
for edition in Edition::iter() {
541+
let ctx = SyntaxContext::root(edition);
542+
assert_eq!(edition, ctx.edition(&db), "{edition} root should have edition {edition}");
543+
}
544+
}
545+
546+
#[test]
547+
fn test_roots_do_not_overlap_with_salsa_ids() {
548+
for edition in Edition::iter() {
549+
let root = SyntaxContext::root(edition);
550+
let root_u32 = root.into_u32();
551+
assert!(
552+
root_u32 >= salsa::Id::MAX_U32,
553+
"Root context for {:?} (value {}) must be >= salsa::Id::MAX_U32 ({}) to avoid collision",
554+
edition,
555+
root_u32,
556+
salsa::Id::MAX_U32
557+
);
558+
}
559+
}
560+
561+
#[test]
562+
fn test_non_root_value_is_not_root() {
563+
for edition in Edition::iter() {
564+
// SAFETY: This is just for testing purposes
565+
let ctx = unsafe { SyntaxContext::from_u32(edition as u32 + 1) };
566+
assert!(!ctx.is_root(), "{edition} root should be identified as root");
567+
}
568+
}
569+
570+
#[test]
571+
fn test_interned_context_round_trips_through_u32() {
572+
let db = salsa::DatabaseImpl::new();
573+
let root = SyntaxContext::root(Edition::Edition2015);
574+
let ctx = SyntaxContext::new(
575+
&db,
576+
None,
577+
Transparency::Opaque,
578+
Edition::Edition2021,
579+
root,
580+
|_| root,
581+
|_| root,
582+
);
583+
584+
// SAFETY: The value was produced by `SyntaxContext::into_u32` above.
585+
let round_tripped = unsafe { SyntaxContext::from_u32(ctx.into_u32()) };
586+
assert_eq!(round_tripped.edition(&db), Edition::Edition2021);
587+
assert_eq!(round_tripped.parent(&db), root);
588+
}
589+
}

crates/span/src/lib.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,20 @@ impl Span {
5151
}
5252
// Differing context, we can't merge these so prefer the one that's root
5353
if self.ctx != other.ctx {
54+
#[cfg(feature = "salsa")]
5455
if self.ctx.is_root() {
5556
return Some(other);
5657
} else if other.ctx.is_root() {
5758
return Some(self);
5859
}
60+
None
61+
} else {
62+
Some(Span {
63+
range: self.range.cover(other.range),
64+
anchor: other.anchor,
65+
ctx: other.ctx,
66+
})
5967
}
60-
Some(Span { range: self.range.cover(other.range), anchor: other.anchor, ctx: other.ctx })
6168
}
6269

6370
pub fn eq_ignoring_ctx(self, other: Self) -> bool {

crates/span/src/map.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use std::{fmt, hash::Hash};
66
use stdx::{always, itertools::Itertools};
77

88
use crate::{
9-
EditionedFileId, ErasedFileAstId, ROOT_ERASED_FILE_AST_ID, Span, SpanAnchor, SyntaxContext,
10-
TextRange, TextSize,
9+
EditionedFileId, ErasedFileAstId, ROOT_ERASED_FILE_AST_ID, Span, SyntaxContext, TextRange,
10+
TextSize,
1111
};
1212

1313
/// Maps absolute text ranges for the corresponding file to the relevant span data.
@@ -220,6 +220,7 @@ impl RealSpanMap {
220220
Self { file_id, pairs, end }
221221
}
222222

223+
#[cfg(feature = "salsa")]
223224
pub fn span_for_range(&self, range: TextRange) -> Span {
224225
assert!(
225226
range.end() <= self.end,
@@ -234,7 +235,7 @@ impl RealSpanMap {
234235
let (offset, ast_id) = self.pairs[idx - 1];
235236
Span {
236237
range: range - offset,
237-
anchor: SpanAnchor { file_id: self.file_id, ast_id },
238+
anchor: crate::SpanAnchor { file_id: self.file_id, ast_id },
238239
ctx: SyntaxContext::root(self.file_id.edition()),
239240
}
240241
}

crates/tt/src/lib.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -765,27 +765,24 @@ impl Subtree {
765765

766766
pub fn pretty(tkns: TokenTreesView<'_>) -> String {
767767
return dispatch_ref! {
768-
match tkns.repr => tt => pretty_impl(tt)
768+
match tkns.repr => tt => pretty_impl(tkns, tt)
769769
};
770770

771771
use crate::storage::TokenTree;
772772

773-
fn tokentree_to_text<S: SpanStorage>(tkn: &TokenTree<S>, tkns: &mut &[TokenTree<S>]) -> String {
773+
fn tokentree_to_text<S: SpanStorage>(
774+
tkns_view: TokenTreesView<'_>,
775+
tkn: &TokenTree<S>,
776+
tkns: &mut &[TokenTree<S>],
777+
) -> String {
774778
match tkn {
775779
TokenTree::Ident { sym, is_raw, .. } => format!("{}{}", is_raw.as_str(), sym),
776-
&TokenTree::Literal { ref text_and_suffix, kind, suffix_len, span: _ } => {
780+
&TokenTree::Literal { ref text_and_suffix, kind, suffix_len, span } => {
777781
format!(
778782
"{}",
779783
Literal {
780784
text_and_suffix: text_and_suffix.clone(),
781-
span: Span {
782-
range: TextRange::empty(TextSize::new(0)),
783-
anchor: span::SpanAnchor {
784-
file_id: span::EditionedFileId::from_raw(0),
785-
ast_id: span::FIXUP_ERASED_FILE_AST_ID_MARKER
786-
},
787-
ctx: span::SyntaxContext::root(span::Edition::Edition2015)
788-
},
785+
span: span.span(tkns_view.span_parts),
789786
kind,
790787
suffix_len
791788
}
@@ -794,7 +791,7 @@ pub fn pretty(tkns: TokenTreesView<'_>) -> String {
794791
TokenTree::Punct { char, .. } => format!("{}", char),
795792
TokenTree::Subtree { len, delim_kind, .. } => {
796793
let (subtree_content, rest) = tkns.split_at(*len as usize);
797-
let content = pretty_impl(subtree_content);
794+
let content = pretty_impl(tkns_view, subtree_content);
798795
*tkns = rest;
799796
let (open, close) = match *delim_kind {
800797
DelimiterKind::Brace => ("{", "}"),
@@ -807,13 +804,16 @@ pub fn pretty(tkns: TokenTreesView<'_>) -> String {
807804
}
808805
}
809806

810-
fn pretty_impl<S: SpanStorage>(mut tkns: &[TokenTree<S>]) -> String {
807+
fn pretty_impl<S: SpanStorage>(
808+
tkns_view: TokenTreesView<'_>,
809+
mut tkns: &[TokenTree<S>],
810+
) -> String {
811811
let mut last = String::new();
812812
let mut last_to_joint = true;
813813

814814
while let Some((tkn, rest)) = tkns.split_first() {
815815
tkns = rest;
816-
last = [last, tokentree_to_text(tkn, &mut tkns)].join(if last_to_joint {
816+
last = [last, tokentree_to_text(tkns_view, tkn, &mut tkns)].join(if last_to_joint {
817817
""
818818
} else {
819819
" "

0 commit comments

Comments
 (0)