@@ -4,7 +4,7 @@ use crate::errors::{
44 NoSyntaxVarsExprRepeat, VarStillRepeating,
55};
66use crate::mbe::macro_parser::{MatchedNonterminal, MatchedSeq, MatchedTokenTree, NamedMatch};
7- use crate::mbe::{self, MetaVarExpr};
7+ use crate::mbe::{self, KleeneOp, MetaVarExpr};
88use rustc_ast::mut_visit::{self, MutVisitor};
99use rustc_ast::token::{self, Delimiter, Token, TokenKind};
1010use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree};
@@ -13,7 +13,7 @@ use rustc_errors::DiagnosticBuilder;
1313use rustc_errors::{pluralize, PResult};
1414use rustc_span::hygiene::{LocalExpnId, Transparency};
1515use rustc_span::symbol::{sym, Ident, MacroRulesNormalizedIdent};
16- use rustc_span::Span;
16+ use rustc_span::{try_insert_metavar_span, Span} ;
1717
1818use smallvec::{smallvec, SmallVec};
1919use std::mem;
@@ -42,6 +42,7 @@ enum Frame<'a> {
4242 tts: &'a [mbe::TokenTree],
4343 idx: usize,
4444 sep: Option<Token>,
45+ kleene_op: KleeneOp,
4546 },
4647}
4748
@@ -207,7 +208,7 @@ pub(super) fn transcribe<'a>(
207208
208209 // Is the repetition empty?
209210 if len == 0 {
210- if seq.kleene.op == mbe:: KleeneOp::OneOrMore {
211+ if seq.kleene.op == KleeneOp::OneOrMore {
211212 // FIXME: this really ought to be caught at macro definition
212213 // time... It happens when the Kleene operator in the matcher and
213214 // the body for the same meta-variable do not match.
@@ -227,6 +228,7 @@ pub(super) fn transcribe<'a>(
227228 idx: 0,
228229 sep: seq.separator.clone(),
229230 tts: &delimited.tts,
231+ kleene_op: seq.kleene.op,
230232 });
231233 }
232234 }
@@ -243,7 +245,8 @@ pub(super) fn transcribe<'a>(
243245 MatchedTokenTree(tt) => {
244246 // `tt`s are emitted into the output stream directly as "raw tokens",
245247 // without wrapping them into groups.
246- result.push(tt.clone());
248+ marker.visit_span(&mut sp);
249+ result.push(maybe_use_metavar_location(cx, &stack, sp, tt));
247250 }
248251 MatchedNonterminal(nt) => {
249252 // Other variables are emitted into the output stream as groups with
@@ -308,6 +311,82 @@ pub(super) fn transcribe<'a>(
308311 }
309312}
310313
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+ /// The old heuristic below is used to improve spans in case of collisions, but diagnostics are
319+ /// still degraded sometimes in those cases.
320+ ///
321+ /// The old heuristic:
322+ ///
323+ /// Usually metavariables `$var` produce interpolated tokens, which have an additional place for
324+ /// keeping both the original span and the metavariable span. For `tt` metavariables that's not the
325+ /// case however, and there's no place for keeping a second span. So we try to give the single
326+ /// produced span a location that would be most useful in practice (the hygiene part of the span
327+ /// must not be changed).
328+ ///
329+ /// Different locations are useful for different purposes:
330+ /// - The original location is useful when we need to report a diagnostic for the original token in
331+ /// isolation, without combining it with any surrounding tokens. This case occurs, but it is not
332+ /// very common in practice.
333+ /// - The metavariable location is useful when we need to somehow combine the token span with spans
334+ /// of its surrounding tokens. This is the most common way to use token spans.
335+ ///
336+ /// So this function replaces the original location with the metavariable location in all cases
337+ /// except these two:
338+ /// - The metavariable is an element of undelimited sequence `$($tt)*`.
339+ /// These are typically used for passing larger amounts of code, and tokens in that code usually
340+ /// combine with each other and not with tokens outside of the sequence.
341+ /// - The metavariable span comes from a different crate, then we prefer the more local span.
342+ fn maybe_use_metavar_location(
343+ cx: &ExtCtxt<'_>,
344+ stack: &[Frame<'_>],
345+ metavar_span: Span,
346+ orig_tt: &TokenTree,
347+ ) -> TokenTree {
348+ let no_collision = match orig_tt {
349+ TokenTree::Token(token, ..) => try_insert_metavar_span(token.span, metavar_span),
350+ TokenTree::Delimited(dspan, ..) => {
351+ try_insert_metavar_span(dspan.open, metavar_span)
352+ && try_insert_metavar_span(dspan.close, metavar_span)
353+ && try_insert_metavar_span(dspan.entire(), metavar_span)
354+ }
355+ };
356+ let undelimited_seq = || {
357+ matches!(
358+ stack.last(),
359+ Some(Frame::Sequence {
360+ tts: [_],
361+ sep: None,
362+ kleene_op: KleeneOp::ZeroOrMore | KleeneOp::OneOrMore,
363+ ..
364+ })
365+ )
366+ };
367+ if no_collision || undelimited_seq() || cx.source_map().is_imported(metavar_span) {
368+ return orig_tt.clone();
369+ }
370+
371+ // Setting metavar spans for the heuristic spans gives better opportunities for combining them
372+ // with neighboring spans even despite their different syntactic contexts.
373+ match orig_tt {
374+ TokenTree::Token(Token { kind, span }, spacing) => {
375+ let span = metavar_span.with_ctxt(span.ctxt());
376+ let _ = try_insert_metavar_span(span, metavar_span);
377+ TokenTree::Token(Token { kind: kind.clone(), span }, *spacing)
378+ }
379+ TokenTree::Delimited(dspan, dspacing, delimiter, tts) => {
380+ let open = metavar_span.with_ctxt(dspan.open.ctxt());
381+ let close = metavar_span.with_ctxt(dspan.close.ctxt());
382+ let _ = try_insert_metavar_span(open, metavar_span);
383+ let _ = try_insert_metavar_span(close, metavar_span);
384+ let dspan = DelimSpan::from_pair(open, close);
385+ TokenTree::Delimited(dspan, *dspacing, *delimiter, tts.clone())
386+ }
387+ }
388+ }
389+
311390/// Lookup the meta-var named `ident` and return the matched token tree from the invocation using
312391/// the set of matches `interpolations`.
313392///
0 commit comments