Skip to content

Commit 03fde33

Browse files
committed
More detail when expecting expression but encountering bad macro argument
Partially address #71039.
1 parent f70779b commit 03fde33

27 files changed

+196
-75
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
@@ -760,7 +760,10 @@ pub fn visit_token<T: MutVisitor>(t: &mut Token, vis: &mut T) {
760760
return; // Avoid visiting the span for the second time.
761761
}
762762
token::Interpolated(nt) => {
763-
visit_nonterminal(Lrc::make_mut(nt), vis);
763+
let nt = Lrc::make_mut(nt);
764+
let (nt, sp) = (&mut nt.0, &mut nt.1);
765+
vis.visit_span(sp);
766+
visit_nonterminal(nt, vis);
764767
}
765768
_ => {}
766769
}

compiler/rustc_ast/src/token.rs

+35-22
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)
@@ -313,7 +313,7 @@ pub enum TokenKind {
313313
/// - It prevents `Token` from implementing `Copy`.
314314
/// It adds complexity and likely slows things down. Please don't add new
315315
/// occurrences of this token kind!
316-
Interpolated(Lrc<Nonterminal>),
316+
Interpolated(Lrc<(Nonterminal, Span)>),
317317

318318
/// A doc comment token.
319319
/// `Symbol` is the doc comment's data excluding its "quotes" (`///`, `/**`, etc)
@@ -420,7 +420,7 @@ impl Token {
420420
/// if they keep spans or perform edition checks.
421421
pub fn uninterpolated_span(&self) -> Span {
422422
match &self.kind {
423-
Interpolated(nt) => nt.span(),
423+
Interpolated(nt) => nt.0.use_span(),
424424
_ => self.span,
425425
}
426426
}
@@ -463,7 +463,7 @@ impl Token {
463463
ModSep | // global path
464464
Lifetime(..) | // labeled loop
465465
Pound => true, // expression attributes
466-
Interpolated(ref nt) => matches!(**nt, NtLiteral(..) |
466+
Interpolated(ref nt) => matches!(&nt.0, NtLiteral(..) |
467467
NtExpr(..) |
468468
NtBlock(..) |
469469
NtPath(..)),
@@ -487,7 +487,7 @@ impl Token {
487487
| DotDot | DotDotDot | DotDotEq // ranges
488488
| Lt | BinOp(Shl) // associated path
489489
| ModSep => true, // global path
490-
Interpolated(ref nt) => matches!(**nt, NtLiteral(..) |
490+
Interpolated(ref nt) => matches!(&nt.0, NtLiteral(..) |
491491
NtPat(..) |
492492
NtBlock(..) |
493493
NtPath(..)),
@@ -510,7 +510,7 @@ impl Token {
510510
Lifetime(..) | // lifetime bound in trait object
511511
Lt | BinOp(Shl) | // associated path
512512
ModSep => true, // global path
513-
Interpolated(ref nt) => matches!(**nt, NtTy(..) | NtPath(..)),
513+
Interpolated(ref nt) => matches!(&nt.0, NtTy(..) | NtPath(..)),
514514
// For anonymous structs or unions, which only appear in specific positions
515515
// (type of struct fields or union fields), we don't consider them as regular types
516516
_ => false,
@@ -521,7 +521,7 @@ impl Token {
521521
pub fn can_begin_const_arg(&self) -> bool {
522522
match self.kind {
523523
OpenDelim(Delimiter::Brace) => true,
524-
Interpolated(ref nt) => matches!(**nt, NtExpr(..) | NtBlock(..) | NtLiteral(..)),
524+
Interpolated(ref nt) => matches!(&nt.0, NtExpr(..) | NtBlock(..) | NtLiteral(..)),
525525
_ => self.can_begin_literal_maybe_minus(),
526526
}
527527
}
@@ -575,7 +575,7 @@ impl Token {
575575
match self.uninterpolate().kind {
576576
Literal(..) | BinOp(Minus) => true,
577577
Ident(name, false) if name.is_bool_lit() => true,
578-
Interpolated(ref nt) => match &**nt {
578+
Interpolated(ref nt) => match &nt.0 {
579579
NtLiteral(_) => true,
580580
NtExpr(e) => match &e.kind {
581581
ast::ExprKind::Lit(_) => true,
@@ -596,9 +596,9 @@ impl Token {
596596
/// otherwise returns the original token.
597597
pub fn uninterpolate(&self) -> Cow<'_, Token> {
598598
match &self.kind {
599-
Interpolated(nt) => match **nt {
599+
Interpolated(nt) => match &nt.0 {
600600
NtIdent(ident, is_raw) => {
601-
Cow::Owned(Token::new(Ident(ident.name, is_raw), ident.span))
601+
Cow::Owned(Token::new(Ident(ident.name, *is_raw), ident.span))
602602
}
603603
NtLifetime(ident) => Cow::Owned(Token::new(Lifetime(ident.name), ident.span)),
604604
_ => Cow::Borrowed(self),
@@ -613,8 +613,8 @@ impl Token {
613613
// We avoid using `Token::uninterpolate` here because it's slow.
614614
match &self.kind {
615615
&Ident(name, is_raw) => Some((Ident::new(name, self.span), is_raw)),
616-
Interpolated(nt) => match **nt {
617-
NtIdent(ident, is_raw) => Some((ident, is_raw)),
616+
Interpolated(nt) => match &nt.0 {
617+
NtIdent(ident, is_raw) => Some((*ident, *is_raw)),
618618
_ => None,
619619
},
620620
_ => None,
@@ -627,8 +627,8 @@ impl Token {
627627
// We avoid using `Token::uninterpolate` here because it's slow.
628628
match &self.kind {
629629
&Lifetime(name) => Some(Ident::new(name, self.span)),
630-
Interpolated(nt) => match **nt {
631-
NtLifetime(ident) => Some(ident),
630+
Interpolated(nt) => match &nt.0 {
631+
NtLifetime(ident) => Some(*ident),
632632
_ => None,
633633
},
634634
_ => None,
@@ -653,9 +653,7 @@ impl Token {
653653

654654
/// Returns `true` if the token is an interpolated path.
655655
fn is_path(&self) -> bool {
656-
if let Interpolated(nt) = &self.kind
657-
&& let NtPath(..) = **nt
658-
{
656+
if let Interpolated(nt) = &self.kind && let NtPath(..) = &nt.0 {
659657
return true;
660658
}
661659

@@ -667,7 +665,7 @@ impl Token {
667665
/// (which happens while parsing the result of macro expansion)?
668666
pub fn is_whole_expr(&self) -> bool {
669667
if let Interpolated(nt) = &self.kind
670-
&& let NtExpr(_) | NtLiteral(_) | NtPath(_) | NtBlock(_) = **nt
668+
&& let NtExpr(_) | NtLiteral(_) | NtPath(_) | NtBlock(_) = &nt.0
671669
{
672670
return true;
673671
}
@@ -677,9 +675,7 @@ impl Token {
677675

678676
/// Is the token an interpolated block (`$b:block`)?
679677
pub fn is_whole_block(&self) -> bool {
680-
if let Interpolated(nt) = &self.kind
681-
&& let NtBlock(..) = **nt
682-
{
678+
if let Interpolated(nt) = &self.kind && let NtBlock(..) = &nt.0 {
683679
return true;
684680
}
685681

@@ -926,7 +922,7 @@ impl fmt::Display for NonterminalKind {
926922
}
927923

928924
impl Nonterminal {
929-
pub fn span(&self) -> Span {
925+
pub fn use_span(&self) -> Span {
930926
match self {
931927
NtItem(item) => item.span,
932928
NtBlock(block) => block.span,
@@ -940,6 +936,23 @@ impl Nonterminal {
940936
NtVis(vis) => vis.span,
941937
}
942938
}
939+
940+
pub fn descr(&self) -> &'static str {
941+
match self {
942+
NtItem(..) => "item",
943+
NtBlock(..) => "block",
944+
NtStmt(..) => "statement",
945+
NtPat(..) => "pattern",
946+
NtExpr(..) => "expression",
947+
NtLiteral(..) => "literal",
948+
NtTy(..) => "type",
949+
NtIdent(..) => "identifier",
950+
NtLifetime(..) => "lifetime",
951+
NtMeta(..) => "attribute",
952+
NtPath(..) => "path",
953+
NtVis(..) => "visibility",
954+
}
955+
}
943956
}
944957

945958
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
@@ -813,7 +813,7 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
813813
}
814814
token::Eof => "<eof>".into(),
815815

816-
token::Interpolated(ref nt) => self.nonterminal_to_string(nt).into(),
816+
token::Interpolated(ref nt) => self.nonterminal_to_string(&nt.0).into(),
817817
}
818818
}
819819

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

+5-4
Original file line numberDiff line numberDiff line change
@@ -226,18 +226,19 @@ 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 { sym: ident.name, is_raw: *is_raw, span: ident.span }))
231+
}
231232

232233
Interpolated(nt) => {
233-
let stream = TokenStream::from_nonterminal_ast(&nt);
234+
let stream = TokenStream::from_nonterminal_ast(&nt.0);
234235
// A hack used to pass AST fragments to attribute and derive
235236
// macros as a single nonterminal token instead of a token
236237
// stream. Such token needs to be "unwrapped" and not
237238
// represented as a delimited group.
238239
// FIXME: It needs to be removed, but there are some
239240
// compatibility issues (see #73345).
240-
if crate::base::nt_pretty_printing_compatibility_hack(&nt, rustc.sess()) {
241+
if crate::base::nt_pretty_printing_compatibility_hack(&nt.0, rustc.sess()) {
241242
trees.extend(Self::from_internal((stream, rustc)));
242243
} else {
243244
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;
@@ -2230,6 +2231,59 @@ impl<'a> Parser<'a> {
22302231
err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp));
22312232
}
22322233
err.span_label(span, "expected expression");
2234+
2235+
// Walk the chain of macro expansions for the current token to point at how the original
2236+
// code was interpreted. This helps the user realize when a macro argument of one type is
2237+
// later reinterpreted as a different type, like `$x:expr` being reinterpreted as `$x:pat`
2238+
// in a subsequent macro invocation (#71039).
2239+
let mut tok = self.token.clone();
2240+
let mut labels = vec![];
2241+
while let TokenKind::Interpolated(node) = &tok.kind {
2242+
let tokens = node.0.tokens();
2243+
labels.push(node.clone());
2244+
if let Some(tokens) = tokens
2245+
&& let tokens = tokens.to_attr_token_stream()
2246+
&& let tokens = tokens.0.deref()
2247+
&& let [AttrTokenTree::Token(token, _)] = &tokens[..]
2248+
{
2249+
tok = token.clone();
2250+
} else {
2251+
break;
2252+
}
2253+
}
2254+
let mut iter = labels.into_iter().peekable();
2255+
let mut show_link = false;
2256+
while let Some(node) = iter.next() {
2257+
let descr = node.0.descr();
2258+
if let Some(next) = iter.peek() {
2259+
let next_descr = next.0.descr();
2260+
if next_descr != descr {
2261+
err.span_label(next.1, format!("this macro fragment matcher is {next_descr}"));
2262+
err.span_label(node.1, format!("this macro fragment matcher is {descr}"));
2263+
err.span_label(
2264+
next.0.use_span(),
2265+
format!("this is expected to be {next_descr}"),
2266+
);
2267+
err.span_label(
2268+
node.0.use_span(),
2269+
format!(
2270+
"this is interpreted as {}, but it is expected to be {}",
2271+
next_descr, descr,
2272+
),
2273+
);
2274+
show_link = true;
2275+
} else {
2276+
err.span_label(node.1, "");
2277+
}
2278+
}
2279+
}
2280+
if show_link {
2281+
err.note(
2282+
"when forwarding a matched fragment to another macro-by-example, matchers in the \
2283+
second macro will see an opaque AST of the fragment type, not the underlying \
2284+
tokens",
2285+
);
2286+
}
22332287
err
22342288
}
22352289

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();
@@ -1946,7 +1946,7 @@ impl<'a> Parser<'a> {
19461946
mk_lit_char: impl FnOnce(Symbol, Span) -> L,
19471947
) -> PResult<'a, L> {
19481948
if let token::Interpolated(nt) = &self.token.kind
1949-
&& let token::NtExpr(e) | token::NtLiteral(e) = &**nt
1949+
&& let token::NtExpr(e) | token::NtLiteral(e) = &nt.0
19501950
&& matches!(e.kind, ExprKind::Err)
19511951
{
19521952
let mut err = errors::InvalidInterpolatedExpression { span: self.token.span }

0 commit comments

Comments
 (0)