Skip to content

Commit 4e41880

Browse files
committed
More detail when expecting expression but encountering bad macro argument
Partially address #71039.
1 parent 1be1e84 commit 4e41880

27 files changed

+200
-67
lines changed

compiler/rustc_ast/src/attr/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ impl MetaItem {
342342
let span = span.with_hi(segments.last().unwrap().ident.span.hi());
343343
Path { span, segments, tokens: None }
344344
}
345-
Some(TokenTree::Token(Token { kind: token::Interpolated(nt), .. }, _)) => match &**nt {
345+
Some(TokenTree::Token(Token { kind: token::Interpolated(nt), .. }, _)) => match &nt.0 {
346346
token::Nonterminal::NtMeta(item) => return item.meta(item.path.span),
347347
token::Nonterminal::NtPath(path) => (**path).clone(),
348348
_ => return None,

compiler/rustc_ast/src/mut_visit.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -764,7 +764,10 @@ pub fn visit_token<T: MutVisitor>(t: &mut Token, vis: &mut T) {
764764
return; // Avoid visiting the span for the second time.
765765
}
766766
token::Interpolated(nt) => {
767-
visit_nonterminal(Lrc::make_mut(nt), vis);
767+
let nt = Lrc::make_mut(nt);
768+
let (nt, sp) = (&mut nt.0, &mut nt.1);
769+
vis.visit_span(sp);
770+
visit_nonterminal(nt, vis);
768771
}
769772
_ => {}
770773
}

compiler/rustc_ast/src/token.rs

+35-18
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ impl Lit {
110110
Ident(name, false) if name.is_bool_lit() => Some(Lit::new(Bool, name, None)),
111111
Literal(token_lit) => Some(token_lit),
112112
Interpolated(ref nt)
113-
if let NtExpr(expr) | NtLiteral(expr) = &**nt
113+
if let NtExpr(expr) | NtLiteral(expr) = &nt.0
114114
&& let ast::ExprKind::Lit(token_lit) = expr.kind =>
115115
{
116116
Some(token_lit)
@@ -314,7 +314,7 @@ pub enum TokenKind {
314314
/// - It prevents `Token` from implementing `Copy`.
315315
/// It adds complexity and likely slows things down. Please don't add new
316316
/// occurrences of this token kind!
317-
Interpolated(Lrc<Nonterminal>),
317+
Interpolated(Lrc<(Nonterminal, Span)>),
318318

319319
/// A doc comment token.
320320
/// `Symbol` is the doc comment's data excluding its "quotes" (`///`, `/**`, etc)
@@ -421,7 +421,7 @@ impl Token {
421421
/// if they keep spans or perform edition checks.
422422
pub fn uninterpolated_span(&self) -> Span {
423423
match &self.kind {
424-
Interpolated(nt) => nt.span(),
424+
Interpolated(nt) => nt.0.use_span(),
425425
_ => self.span,
426426
}
427427
}
@@ -464,7 +464,7 @@ impl Token {
464464
ModSep | // global path
465465
Lifetime(..) | // labeled loop
466466
Pound => true, // expression attributes
467-
Interpolated(ref nt) => matches!(**nt, NtLiteral(..) |
467+
Interpolated(ref nt) => matches!(&nt.0, NtLiteral(..) |
468468
NtExpr(..) |
469469
NtBlock(..) |
470470
NtPath(..)),
@@ -488,7 +488,7 @@ impl Token {
488488
| DotDot | DotDotDot | DotDotEq // ranges
489489
| Lt | BinOp(Shl) // associated path
490490
| ModSep => true, // global path
491-
Interpolated(ref nt) => matches!(**nt, NtLiteral(..) |
491+
Interpolated(ref nt) => matches!(&nt.0, NtLiteral(..) |
492492
NtPat(..) |
493493
NtBlock(..) |
494494
NtPath(..)),
@@ -511,7 +511,7 @@ impl Token {
511511
Lifetime(..) | // lifetime bound in trait object
512512
Lt | BinOp(Shl) | // associated path
513513
ModSep => true, // global path
514-
Interpolated(ref nt) => matches!(**nt, NtTy(..) | NtPath(..)),
514+
Interpolated(ref nt) => matches!(&nt.0, NtTy(..) | NtPath(..)),
515515
// For anonymous structs or unions, which only appear in specific positions
516516
// (type of struct fields or union fields), we don't consider them as regular types
517517
_ => false,
@@ -522,7 +522,7 @@ impl Token {
522522
pub fn can_begin_const_arg(&self) -> bool {
523523
match self.kind {
524524
OpenDelim(Delimiter::Brace) => true,
525-
Interpolated(ref nt) => matches!(**nt, NtExpr(..) | NtBlock(..) | NtLiteral(..)),
525+
Interpolated(ref nt) => matches!(&nt.0, NtExpr(..) | NtBlock(..) | NtLiteral(..)),
526526
_ => self.can_begin_literal_maybe_minus(),
527527
}
528528
}
@@ -576,7 +576,7 @@ impl Token {
576576
match self.uninterpolate().kind {
577577
Literal(..) | BinOp(Minus) => true,
578578
Ident(name, false) if name.is_bool_lit() => true,
579-
Interpolated(ref nt) => match &**nt {
579+
Interpolated(ref nt) => match &nt.0 {
580580
NtLiteral(_) => true,
581581
NtExpr(e) => match &e.kind {
582582
ast::ExprKind::Lit(_) => true,
@@ -597,9 +597,9 @@ impl Token {
597597
/// otherwise returns the original token.
598598
pub fn uninterpolate(&self) -> Cow<'_, Token> {
599599
match &self.kind {
600-
Interpolated(nt) => match **nt {
600+
Interpolated(nt) => match &nt.0 {
601601
NtIdent(ident, is_raw) => {
602-
Cow::Owned(Token::new(Ident(ident.name, is_raw), ident.span))
602+
Cow::Owned(Token::new(Ident(ident.name, *is_raw), ident.span))
603603
}
604604
NtLifetime(ident) => Cow::Owned(Token::new(Lifetime(ident.name), ident.span)),
605605
_ => Cow::Borrowed(self),
@@ -614,8 +614,8 @@ impl Token {
614614
// We avoid using `Token::uninterpolate` here because it's slow.
615615
match &self.kind {
616616
&Ident(name, is_raw) => Some((Ident::new(name, self.span), is_raw)),
617-
Interpolated(nt) => match **nt {
618-
NtIdent(ident, is_raw) => Some((ident, is_raw)),
617+
Interpolated(nt) => match &nt.0 {
618+
NtIdent(ident, is_raw) => Some((*ident, *is_raw)),
619619
_ => None,
620620
},
621621
_ => None,
@@ -628,8 +628,8 @@ impl Token {
628628
// We avoid using `Token::uninterpolate` here because it's slow.
629629
match &self.kind {
630630
&Lifetime(name) => Some(Ident::new(name, self.span)),
631-
Interpolated(nt) => match **nt {
632-
NtLifetime(ident) => Some(ident),
631+
Interpolated(nt) => match &nt.0 {
632+
NtLifetime(ident) => Some(*ident),
633633
_ => None,
634634
},
635635
_ => None,
@@ -655,7 +655,7 @@ impl Token {
655655
/// Returns `true` if the token is an interpolated path.
656656
fn is_path(&self) -> bool {
657657
if let Interpolated(nt) = &self.kind
658-
&& let NtPath(..) = **nt
658+
&& let NtPath(..) = &nt.0
659659
{
660660
return true;
661661
}
@@ -668,7 +668,7 @@ impl Token {
668668
/// (which happens while parsing the result of macro expansion)?
669669
pub fn is_whole_expr(&self) -> bool {
670670
if let Interpolated(nt) = &self.kind
671-
&& let NtExpr(_) | NtLiteral(_) | NtPath(_) | NtBlock(_) = **nt
671+
&& let NtExpr(_) | NtLiteral(_) | NtPath(_) | NtBlock(_) = &nt.0
672672
{
673673
return true;
674674
}
@@ -679,7 +679,7 @@ impl Token {
679679
/// Is the token an interpolated block (`$b:block`)?
680680
pub fn is_whole_block(&self) -> bool {
681681
if let Interpolated(nt) = &self.kind
682-
&& let NtBlock(..) = **nt
682+
&& let NtBlock(..) = &nt.0
683683
{
684684
return true;
685685
}
@@ -927,7 +927,7 @@ impl fmt::Display for NonterminalKind {
927927
}
928928

929929
impl Nonterminal {
930-
pub fn span(&self) -> Span {
930+
pub fn use_span(&self) -> Span {
931931
match self {
932932
NtItem(item) => item.span,
933933
NtBlock(block) => block.span,
@@ -941,6 +941,23 @@ impl Nonterminal {
941941
NtVis(vis) => vis.span,
942942
}
943943
}
944+
945+
pub fn descr(&self) -> &'static str {
946+
match self {
947+
NtItem(..) => "item",
948+
NtBlock(..) => "block",
949+
NtStmt(..) => "statement",
950+
NtPat(..) => "pattern",
951+
NtExpr(..) => "expression",
952+
NtLiteral(..) => "literal",
953+
NtTy(..) => "type",
954+
NtIdent(..) => "identifier",
955+
NtLifetime(..) => "lifetime",
956+
NtMeta(..) => "attribute",
957+
NtPath(..) => "path",
958+
NtVis(..) => "visibility",
959+
}
960+
}
944961
}
945962

946963
impl PartialEq for Nonterminal {

compiler/rustc_ast/src/tokenstream.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -477,13 +477,13 @@ impl TokenStream {
477477

478478
fn flatten_token(token: &Token, spacing: Spacing) -> TokenTree {
479479
match &token.kind {
480-
token::Interpolated(nt) if let token::NtIdent(ident, is_raw) = **nt => {
480+
token::Interpolated(nt) if let token::NtIdent(ident, is_raw) = nt.0 => {
481481
TokenTree::Token(Token::new(token::Ident(ident.name, is_raw), ident.span), spacing)
482482
}
483483
token::Interpolated(nt) => TokenTree::Delimited(
484484
DelimSpan::from_single(token.span),
485485
Delimiter::Invisible,
486-
TokenStream::from_nonterminal_ast(nt).flattened(),
486+
TokenStream::from_nonterminal_ast(&nt.0).flattened(),
487487
),
488488
_ => TokenTree::Token(token.clone(), spacing),
489489
}

compiler/rustc_ast_pretty/src/pprust/state.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -825,7 +825,7 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
825825
}
826826
token::Eof => "<eof>".into(),
827827

828-
token::Interpolated(ref nt) => self.nonterminal_to_string(nt).into(),
828+
token::Interpolated(ref nt) => self.nonterminal_to_string(&nt.0).into(),
829829
}
830830
}
831831

compiler/rustc_expand/src/mbe/diagnostics.rs

+6
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ pub(super) fn failed_to_match_macro<'cx>(
6767
&& (matches!(expected_token.kind, TokenKind::Interpolated(_))
6868
|| matches!(token.kind, TokenKind::Interpolated(_)))
6969
{
70+
if let TokenKind::Interpolated(node) = &expected_token.kind {
71+
err.span_label(node.1, "");
72+
}
73+
if let TokenKind::Interpolated(node) = &token.kind {
74+
err.span_label(node.1, "");
75+
}
7076
err.note("captured metavariables except for `:tt`, `:ident` and `:lifetime` cannot be compared to other tokens");
7177
err.note("see <https://doc.rust-lang.org/nightly/reference/macros-by-example.html#forwarding-a-matched-fragment> for more information");
7278

compiler/rustc_expand/src/mbe/macro_parser.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ pub(crate) enum NamedMatch {
397397
MatchedTokenTree(rustc_ast::tokenstream::TokenTree),
398398

399399
// A metavar match of any type other than `tt`.
400-
MatchedNonterminal(Lrc<Nonterminal>),
400+
MatchedNonterminal(Lrc<(Nonterminal, rustc_span::Span)>),
401401
}
402402

403403
/// Performs a token equality check, ignoring syntax context (that is, an unhygienic comparison)
@@ -692,7 +692,7 @@ impl TtParser {
692692
Ok(nt) => nt,
693693
};
694694
let m = match nt {
695-
ParseNtResult::Nt(nt) => MatchedNonterminal(Lrc::new(nt)),
695+
ParseNtResult::Nt(nt) => MatchedNonterminal(Lrc::new((nt, span))),
696696
ParseNtResult::Tt(tt) => MatchedTokenTree(tt),
697697
};
698698
mp.push_match(next_metavar, seq_depth, m);

compiler/rustc_expand/src/proc_macro.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ impl MultiItemModifier for DeriveProcMacro {
126126
Annotatable::Stmt(stmt) => token::NtStmt(stmt),
127127
_ => unreachable!(),
128128
};
129-
TokenStream::token_alone(token::Interpolated(Lrc::new(nt)), DUMMY_SP)
129+
TokenStream::token_alone(token::Interpolated(Lrc::new((nt, span))), DUMMY_SP)
130130
} else {
131131
item.to_tokens()
132132
};

compiler/rustc_expand/src/proc_macro_server.rs

+9-4
Original file line numberDiff line numberDiff line change
@@ -226,18 +226,23 @@ impl FromInternal<(TokenStream, &mut Rustc<'_, '_>)> for Vec<TokenTree<TokenStre
226226
}));
227227
}
228228

229-
Interpolated(nt) if let NtIdent(ident, is_raw) = *nt => trees
230-
.push(TokenTree::Ident(Ident { sym: ident.name, is_raw, span: ident.span })),
229+
Interpolated(ref nt) if let NtIdent(ident, is_raw) = &nt.0 => {
230+
trees.push(TokenTree::Ident(Ident {
231+
sym: ident.name,
232+
is_raw: *is_raw,
233+
span: ident.span,
234+
}))
235+
}
231236

232237
Interpolated(nt) => {
233-
let stream = TokenStream::from_nonterminal_ast(&nt);
238+
let stream = TokenStream::from_nonterminal_ast(&nt.0);
234239
// A hack used to pass AST fragments to attribute and derive
235240
// macros as a single nonterminal token instead of a token
236241
// stream. Such token needs to be "unwrapped" and not
237242
// represented as a delimited group.
238243
// FIXME: It needs to be removed, but there are some
239244
// compatibility issues (see #73345).
240-
if crate::base::nt_pretty_printing_compatibility_hack(&nt, rustc.sess()) {
245+
if crate::base::nt_pretty_printing_compatibility_hack(&nt.0, rustc.sess()) {
241246
trees.extend(Self::from_internal((stream, rustc)));
242247
} else {
243248
trees.push(TokenTree::Group(Group {

compiler/rustc_parse/src/parser/attr.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ impl<'a> Parser<'a> {
249249
/// The delimiters or `=` are still put into the resulting token stream.
250250
pub fn parse_attr_item(&mut self, capture_tokens: bool) -> PResult<'a, ast::AttrItem> {
251251
let item = match &self.token.kind {
252-
token::Interpolated(nt) => match &**nt {
252+
token::Interpolated(nt) => match &nt.0 {
253253
Nonterminal::NtMeta(item) => Some(item.clone().into_inner()),
254254
_ => None,
255255
},
@@ -369,7 +369,7 @@ impl<'a> Parser<'a> {
369369
/// ```
370370
pub fn parse_meta_item(&mut self) -> PResult<'a, ast::MetaItem> {
371371
let nt_meta = match &self.token.kind {
372-
token::Interpolated(nt) => match &**nt {
372+
token::Interpolated(nt) => match &nt.0 {
373373
token::NtMeta(e) => Some(e.clone()),
374374
_ => None,
375375
},

compiler/rustc_parse/src/parser/diagnostics.rs

+56-2
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ use crate::parser;
2424
use rustc_ast as ast;
2525
use rustc_ast::ptr::P;
2626
use rustc_ast::token::{self, Delimiter, Lit, LitKind, TokenKind};
27+
use rustc_ast::tokenstream::AttrTokenTree;
2728
use rustc_ast::util::parser::AssocOp;
2829
use rustc_ast::{
2930
AngleBracketedArg, AngleBracketedArgs, AnonConst, AttrVec, BinOpKind, BindingAnnotation, Block,
30-
BlockCheckMode, Expr, ExprKind, GenericArg, Generics, Item, ItemKind, Param, Pat, PatKind,
31-
Path, PathSegment, QSelf, Ty, TyKind,
31+
BlockCheckMode, Expr, ExprKind, GenericArg, Generics, HasTokens, Item, ItemKind, Param, Pat,
32+
PatKind, Path, PathSegment, QSelf, Ty, TyKind,
3233
};
3334
use rustc_ast_pretty::pprust;
3435
use rustc_data_structures::fx::FxHashSet;
@@ -2252,6 +2253,59 @@ impl<'a> Parser<'a> {
22522253
err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp));
22532254
}
22542255
err.span_label(span, "expected expression");
2256+
2257+
// Walk the chain of macro expansions for the current token to point at how the original
2258+
// code was interpreted. This helps the user realize when a macro argument of one type is
2259+
// later reinterpreted as a different type, like `$x:expr` being reinterpreted as `$x:pat`
2260+
// in a subsequent macro invocation (#71039).
2261+
let mut tok = self.token.clone();
2262+
let mut labels = vec![];
2263+
while let TokenKind::Interpolated(node) = &tok.kind {
2264+
let tokens = node.0.tokens();
2265+
labels.push(node.clone());
2266+
if let Some(tokens) = tokens
2267+
&& let tokens = tokens.to_attr_token_stream()
2268+
&& let tokens = tokens.0.deref()
2269+
&& let [AttrTokenTree::Token(token, _)] = &tokens[..]
2270+
{
2271+
tok = token.clone();
2272+
} else {
2273+
break;
2274+
}
2275+
}
2276+
let mut iter = labels.into_iter().peekable();
2277+
let mut show_link = false;
2278+
while let Some(node) = iter.next() {
2279+
let descr = node.0.descr();
2280+
if let Some(next) = iter.peek() {
2281+
let next_descr = next.0.descr();
2282+
if next_descr != descr {
2283+
err.span_label(next.1, format!("this macro fragment matcher is {next_descr}"));
2284+
err.span_label(node.1, format!("this macro fragment matcher is {descr}"));
2285+
err.span_label(
2286+
next.0.use_span(),
2287+
format!("this is expected to be {next_descr}"),
2288+
);
2289+
err.span_label(
2290+
node.0.use_span(),
2291+
format!(
2292+
"this is interpreted as {}, but it is expected to be {}",
2293+
next_descr, descr,
2294+
),
2295+
);
2296+
show_link = true;
2297+
} else {
2298+
err.span_label(node.1, "");
2299+
}
2300+
}
2301+
}
2302+
if show_link {
2303+
err.note(
2304+
"when forwarding a matched fragment to another macro-by-example, matchers in the \
2305+
second macro will see an opaque AST of the fragment type, not the underlying \
2306+
tokens",
2307+
);
2308+
}
22552309
err
22562310
}
22572311

compiler/rustc_parse/src/parser/expr.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ use thin_vec::{thin_vec, ThinVec};
4646
macro_rules! maybe_whole_expr {
4747
($p:expr) => {
4848
if let token::Interpolated(nt) = &$p.token.kind {
49-
match &**nt {
49+
match &nt.0 {
5050
token::NtExpr(e) | token::NtLiteral(e) => {
5151
let e = e.clone();
5252
$p.bump();
@@ -1952,7 +1952,7 @@ impl<'a> Parser<'a> {
19521952
mk_lit_char: impl FnOnce(Symbol, Span) -> L,
19531953
) -> PResult<'a, L> {
19541954
if let token::Interpolated(nt) = &self.token.kind
1955-
&& let token::NtExpr(e) | token::NtLiteral(e) = &**nt
1955+
&& let token::NtExpr(e) | token::NtLiteral(e) = &nt.0
19561956
&& matches!(e.kind, ExprKind::Err)
19571957
{
19581958
let mut err = errors::InvalidInterpolatedExpression { span: self.token.span }

0 commit comments

Comments
 (0)