Skip to content

Commit b8494cc

Browse files
committed
Auto merge of #119673 - petrochenkov:dialoc5, r=<try>
macro_rules: Preserve all metavariable spans in a global side table This is the first part of #119412. TODO: write a more detailed description after a perf run.
2 parents fde0e98 + a454c64 commit b8494cc

29 files changed

+226
-123
lines changed

compiler/rustc_expand/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#![feature(if_let_guard)]
77
#![feature(let_chains)]
88
#![feature(macro_metavar_expr)]
9+
#![feature(map_try_insert)]
910
#![feature(proc_macro_diagnostic)]
1011
#![feature(proc_macro_internals)]
1112
#![feature(proc_macro_span)]

compiler/rustc_expand/src/mbe/transcribe.rs

+40-8
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use rustc_errors::DiagnosticBuilder;
1313
use rustc_errors::{pluralize, PResult};
1414
use rustc_span::hygiene::{LocalExpnId, Transparency};
1515
use rustc_span::symbol::{sym, Ident, MacroRulesNormalizedIdent};
16-
use rustc_span::Span;
16+
use rustc_span::{with_metavar_spans, Span};
1717

1818
use smallvec::{smallvec, SmallVec};
1919
use std::mem;
@@ -245,6 +245,7 @@ pub(super) fn transcribe<'a>(
245245
MatchedTokenTree(tt) => {
246246
// `tt`s are emitted into the output stream directly as "raw tokens",
247247
// without wrapping them into groups.
248+
marker.visit_span(&mut sp);
248249
result.push(maybe_use_metavar_location(cx, &stack, sp, tt));
249250
}
250251
MatchedNonterminal(nt) => {
@@ -310,6 +311,17 @@ pub(super) fn transcribe<'a>(
310311
}
311312
}
312313

314+
/// Store the metavariable span for this original span into a side table.
315+
/// FIXME: Try to put the metavariable span into `SpanData` instead of a side table (#118517).
316+
/// An optimal encoding for inlined spans will need to be selected to minimize regressions.
317+
/// The side table approach is relatively good, but not perfect due to collisions.
318+
/// In particular, collisions happen when token is passed as an argument through several macro
319+
/// calls, like in recursive macros.
320+
/// The old heuristic below is used to improve spans in case of collisions, but diagnostics are
321+
/// still degraded sometimes in those cases.
322+
///
323+
/// The old heuristic:
324+
///
313325
/// Usually metavariables `$var` produce interpolated tokens, which have an additional place for
314326
/// keeping both the original span and the metavariable span. For `tt` metavariables that's not the
315327
/// case however, and there's no place for keeping a second span. So we try to give the single
@@ -329,10 +341,6 @@ pub(super) fn transcribe<'a>(
329341
/// These are typically used for passing larger amounts of code, and tokens in that code usually
330342
/// combine with each other and not with tokens outside of the sequence.
331343
/// - The metavariable span comes from a different crate, then we prefer the more local span.
332-
///
333-
/// FIXME: Find a way to keep both original and metavariable spans for all tokens without
334-
/// regressing compilation time too much. Several experiments for adding such spans were made in
335-
/// the past (PR #95580, #118517, #118671) and all showed some regressions.
336344
fn maybe_use_metavar_location(
337345
cx: &ExtCtxt<'_>,
338346
stack: &[Frame<'_>],
@@ -348,18 +356,42 @@ fn maybe_use_metavar_location(
348356
..
349357
})
350358
);
351-
if undelimited_seq || cx.source_map().is_imported(metavar_span) {
359+
if undelimited_seq {
360+
return orig_tt.clone();
361+
}
362+
363+
let insert = |mspans: &mut FxHashMap<_, _>, s, ms| match mspans.try_insert(s, ms) {
364+
Ok(_) => true,
365+
Err(err) => *err.entry.get() == ms,
366+
};
367+
let no_collision = match orig_tt {
368+
TokenTree::Token(token, ..) => {
369+
with_metavar_spans(|mspans| insert(mspans, token.span, metavar_span))
370+
}
371+
TokenTree::Delimited(dspan, ..) => with_metavar_spans(|mspans| {
372+
insert(mspans, dspan.open, metavar_span)
373+
&& insert(mspans, dspan.close, metavar_span)
374+
&& insert(mspans, dspan.entire(), metavar_span)
375+
}),
376+
};
377+
if no_collision || cx.source_map().is_imported(metavar_span) {
352378
return orig_tt.clone();
353379
}
354380

381+
// Setting metavar spans for the heuristic spans gives better opportunities for combining them
382+
// with neighboring spans even despite their different syntactic contexts.
355383
match orig_tt {
356384
TokenTree::Token(Token { kind, span }, spacing) => {
357385
let span = metavar_span.with_ctxt(span.ctxt());
386+
with_metavar_spans(|mspans| insert(mspans, span, metavar_span));
358387
TokenTree::Token(Token { kind: kind.clone(), span }, *spacing)
359388
}
360389
TokenTree::Delimited(dspan, dspacing, delimiter, tts) => {
361-
let open = metavar_span.shrink_to_lo().with_ctxt(dspan.open.ctxt());
362-
let close = metavar_span.shrink_to_hi().with_ctxt(dspan.close.ctxt());
390+
let open = metavar_span.with_ctxt(dspan.open.ctxt());
391+
let close = metavar_span.with_ctxt(dspan.close.ctxt());
392+
with_metavar_spans(|mspans| {
393+
insert(mspans, open, metavar_span) && insert(mspans, close, metavar_span)
394+
});
363395
let dspan = DelimSpan::from_pair(open, close);
364396
TokenTree::Delimited(dspan, *dspacing, *delimiter, tts.clone())
365397
}

compiler/rustc_span/src/lib.rs

+58-14
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#![feature(core_io_borrowed_buf)]
2727
#![feature(if_let_guard)]
2828
#![feature(let_chains)]
29+
#![feature(map_try_insert)]
2930
#![feature(min_specialization)]
3031
#![feature(negative_impls)]
3132
#![feature(new_uninit)]
@@ -74,6 +75,7 @@ pub mod fatal_error;
7475

7576
pub mod profiling;
7677

78+
use rustc_data_structures::fx::FxHashMap;
7779
use rustc_data_structures::stable_hasher::{Hash128, Hash64, HashStable, StableHasher};
7880
use rustc_data_structures::sync::{FreezeLock, FreezeWriteGuard, Lock, Lrc};
7981

@@ -100,6 +102,7 @@ mod tests;
100102
pub struct SessionGlobals {
101103
symbol_interner: symbol::Interner,
102104
span_interner: Lock<span_encoding::SpanInterner>,
105+
metavar_spans: Lock<FxHashMap<Span, Span>>,
103106
hygiene_data: Lock<hygiene::HygieneData>,
104107

105108
/// A reference to the source map in the `Session`. It's an `Option`
@@ -117,6 +120,7 @@ impl SessionGlobals {
117120
SessionGlobals {
118121
symbol_interner: symbol::Interner::fresh(),
119122
span_interner: Lock::new(span_encoding::SpanInterner::default()),
123+
metavar_spans: Default::default(),
120124
hygiene_data: Lock::new(hygiene::HygieneData::new(edition)),
121125
source_map: Lock::new(None),
122126
}
@@ -170,6 +174,11 @@ pub fn create_default_session_globals_then<R>(f: impl FnOnce() -> R) -> R {
170174
// deserialization.
171175
scoped_tls::scoped_thread_local!(static SESSION_GLOBALS: SessionGlobals);
172176

177+
#[inline]
178+
pub fn with_metavar_spans<R>(f: impl FnOnce(&mut FxHashMap<Span, Span>) -> R) -> R {
179+
with_session_globals(|session_globals| f(&mut session_globals.metavar_spans.lock()))
180+
}
181+
173182
// FIXME: We should use this enum or something like it to get rid of the
174183
// use of magic `/rust/1.x/...` paths across the board.
175184
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Decodable)]
@@ -825,29 +834,64 @@ impl Span {
825834
)
826835
}
827836

837+
/// Check if you can select metavar spans for the given spans to get matching contexts.
838+
fn try_metavars(a: SpanData, b: SpanData, a_orig: Span, b_orig: Span) -> (SpanData, SpanData) {
839+
let get = |mspans: &FxHashMap<_, _>, s| mspans.get(&s).copied();
840+
match with_metavar_spans(|mspans| (get(mspans, a_orig), get(mspans, b_orig))) {
841+
(None, None) => {}
842+
(Some(meta_a), None) => {
843+
let meta_a = meta_a.data();
844+
if meta_a.ctxt == b.ctxt {
845+
return (meta_a, b);
846+
}
847+
}
848+
(None, Some(meta_b)) => {
849+
let meta_b = meta_b.data();
850+
if a.ctxt == meta_b.ctxt {
851+
return (a, meta_b);
852+
}
853+
}
854+
(Some(meta_a), Some(meta_b)) => {
855+
let meta_b = meta_b.data();
856+
if a.ctxt == meta_b.ctxt {
857+
return (a, meta_b);
858+
}
859+
let meta_a = meta_a.data();
860+
if meta_a.ctxt == b.ctxt {
861+
return (meta_a, b);
862+
} else if meta_a.ctxt == meta_b.ctxt {
863+
return (meta_a, meta_b);
864+
}
865+
}
866+
}
867+
868+
(a, b)
869+
}
870+
828871
/// Prepare two spans to a combine operation like `to` or `between`.
829-
/// FIXME: consider using declarative macro metavariable spans for the given spans if they are
830-
/// better suitable for combining (#119412).
831872
fn prepare_to_combine(
832873
a_orig: Span,
833874
b_orig: Span,
834875
) -> Result<(SpanData, SpanData, Option<LocalDefId>), Span> {
835876
let (a, b) = (a_orig.data(), b_orig.data());
877+
if a.ctxt == b.ctxt {
878+
return Ok((a, b, if a.parent == b.parent { a.parent } else { None }));
879+
}
836880

837-
if a.ctxt != b.ctxt {
838-
// Context mismatches usually happen when procedural macros combine spans copied from
839-
// the macro input with spans produced by the macro (`Span::*_site`).
840-
// In that case we consider the combined span to be produced by the macro and return
841-
// the original macro-produced span as the result.
842-
// Otherwise we just fall back to returning the first span.
843-
// Combining locations typically doesn't make sense in case of context mismatches.
844-
// `is_root` here is a fast path optimization.
845-
let a_is_callsite = a.ctxt.is_root() || a.ctxt == b.span().source_callsite().ctxt();
846-
return Err(if a_is_callsite { b_orig } else { a_orig });
881+
let (a, b) = Span::try_metavars(a, b, a_orig, b_orig);
882+
if a.ctxt == b.ctxt {
883+
return Ok((a, b, if a.parent == b.parent { a.parent } else { None }));
847884
}
848885

849-
let parent = if a.parent == b.parent { a.parent } else { None };
850-
Ok((a, b, parent))
886+
// Context mismatches usually happen when procedural macros combine spans copied from
887+
// the macro input with spans produced by the macro (`Span::*_site`).
888+
// In that case we consider the combined span to be produced by the macro and return
889+
// the original macro-produced span as the result.
890+
// Otherwise we just fall back to returning the first span.
891+
// Combining locations typically doesn't make sense in case of context mismatches.
892+
// `is_root` here is a fast path optimization.
893+
let a_is_callsite = a.ctxt.is_root() || a.ctxt == b.span().source_callsite().ctxt();
894+
Err(if a_is_callsite { b_orig } else { a_orig })
851895
}
852896

853897
/// This span, but in a larger context, may switch to the metavariable span if suitable.

tests/coverage/no_spans.cov-map

+8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
Function name: no_spans::affected_function
2+
Raw bytes (9): 0x[01, 01, 00, 01, 01, 1a, 1c, 00, 1d]
3+
Number of files: 1
4+
- file 0 => global file 1
5+
Number of expressions: 0
6+
Number of file 0 mappings: 1
7+
- Code(Counter(0)) at (prev + 26, 28) to (start + 0, 29)
8+
19
Function name: no_spans::affected_function::{closure#0}
210
Raw bytes (9): 0x[01, 01, 00, 01, 01, 1b, 0c, 00, 0e]
311
Number of files: 1

tests/coverage/no_spans_if_not.cov-map

+12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
Function name: no_spans_if_not::affected_function
2+
Raw bytes (21): 0x[01, 01, 01, 01, 00, 03, 01, 16, 1c, 01, 12, 02, 02, 0d, 00, 0f, 00, 02, 0d, 00, 0f]
3+
Number of files: 1
4+
- file 0 => global file 1
5+
Number of expressions: 1
6+
- expression 0 operands: lhs = Counter(0), rhs = Zero
7+
Number of file 0 mappings: 3
8+
- Code(Counter(0)) at (prev + 22, 28) to (start + 1, 18)
9+
- Code(Expression(0, Sub)) at (prev + 2, 13) to (start + 0, 15)
10+
= (c0 - Zero)
11+
- Code(Zero) at (prev + 2, 13) to (start + 0, 15)
12+
113
Function name: no_spans_if_not::main
214
Raw bytes (9): 0x[01, 01, 00, 01, 01, 0b, 01, 02, 02]
315
Number of files: 1

tests/ui/anon-params/anon-params-edition-hygiene.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// check-pass
12
// edition:2018
23
// aux-build:anon-params-edition-hygiene.rs
34

@@ -8,7 +9,6 @@
89
extern crate anon_params_edition_hygiene;
910

1011
generate_trait_2015_ident!(u8);
11-
// FIXME: Edition hygiene doesn't work correctly with `tt`s in this case.
12-
generate_trait_2015_tt!(u8); //~ ERROR expected one of `:`, `@`, or `|`, found `)`
12+
generate_trait_2015_tt!(u8);
1313

1414
fn main() {}

tests/ui/anon-params/anon-params-edition-hygiene.stderr

-23
This file was deleted.

tests/ui/editions/edition-keywords-2018-2018-parsing.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ macro_rules! local_passes_ident {
1616
($i: ident) => ($i) //~ ERROR macro expansion ends with an incomplete expression
1717
}
1818
macro_rules! local_passes_tt {
19-
($i: tt) => ($i) //~ ERROR macro expansion ends with an incomplete expression
19+
($i: tt) => ($i)
2020
}
2121

2222
pub fn check_async() {
@@ -34,7 +34,7 @@ pub fn check_async() {
3434
if passes_tt!(r#async) == 1 {} // OK
3535
if local_passes_ident!(async) == 1 {} // Error reported above in the macro
3636
if local_passes_ident!(r#async) == 1 {} // OK
37-
if local_passes_tt!(async) == 1 {} // Error reported above in the macro
37+
if local_passes_tt!(async) == 1 {} //~ ERROR macro expansion ends with an incomplete expression
3838
if local_passes_tt!(r#async) == 1 {} // OK
3939
module::async(); //~ ERROR expected identifier, found keyword `async`
4040
module::r#async(); // OK

tests/ui/editions/edition-keywords-2018-2018-parsing.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,10 @@ LL | ($i: ident) => ($i)
6868
| ^ expected one of `move`, `|`, or `||`
6969

7070
error: macro expansion ends with an incomplete expression: expected one of `move`, `|`, or `||`
71-
--> $DIR/edition-keywords-2018-2018-parsing.rs:19:20
71+
--> $DIR/edition-keywords-2018-2018-parsing.rs:37:30
7272
|
73-
LL | ($i: tt) => ($i)
74-
| ^ expected one of `move`, `|`, or `||`
73+
LL | if local_passes_tt!(async) == 1 {}
74+
| ^ expected one of `move`, `|`, or `||`
7575

7676
error[E0308]: mismatched types
7777
--> $DIR/edition-keywords-2018-2018-parsing.rs:42:33

tests/ui/fmt/format-args-capture-first-literal-is-macro.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ extern crate format_string_proc_macro;
66
macro_rules! identity_mbe {
77
($tt:tt) => {
88
$tt
9-
//~^ ERROR there is no argument named `a`
109
};
1110
}
1211

@@ -16,6 +15,7 @@ fn main() {
1615
format!(identity_pm!("{a}"));
1716
//~^ ERROR there is no argument named `a`
1817
format!(identity_mbe!("{a}"));
18+
//~^ ERROR there is no argument named `a`
1919
format!(concat!("{a}"));
2020
//~^ ERROR there is no argument named `a`
2121
}

tests/ui/fmt/format-args-capture-first-literal-is-macro.stderr

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: there is no argument named `a`
2-
--> $DIR/format-args-capture-first-literal-is-macro.rs:16:26
2+
--> $DIR/format-args-capture-first-literal-is-macro.rs:15:26
33
|
44
LL | format!(identity_pm!("{a}"));
55
| ^^^^^
@@ -8,10 +8,10 @@ LL | format!(identity_pm!("{a}"));
88
= note: to avoid ambiguity, `format_args!` cannot capture variables when the format string is expanded from a macro
99

1010
error: there is no argument named `a`
11-
--> $DIR/format-args-capture-first-literal-is-macro.rs:8:9
11+
--> $DIR/format-args-capture-first-literal-is-macro.rs:17:27
1212
|
13-
LL | $tt
14-
| ^^^
13+
LL | format!(identity_mbe!("{a}"));
14+
| ^^^^^
1515
|
1616
= note: did you intend to capture a variable `a` from the surrounding scope?
1717
= note: to avoid ambiguity, `format_args!` cannot capture variables when the format string is expanded from a macro

tests/ui/lint/wide_pointer_comparisons.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,12 @@ fn main() {
110110
{
111111
macro_rules! cmp {
112112
($a:tt, $b:tt) => { $a == $b }
113-
//~^ WARN ambiguous wide pointer comparison
114113
}
115114

115+
// FIXME: This lint uses some custom span combination logic.
116+
// Rewrite it to adapt to the new metavariable span rules.
116117
cmp!(a, b);
118+
//~^ WARN ambiguous wide pointer comparison
117119
}
118120

119121
{

0 commit comments

Comments
 (0)