Skip to content

Commit d680dc5

Browse files
committed
Auto merge of rust-lang#114292 - estebank:issue-71039, r=<try>
More detail when expecting expression but encountering bad macro argument Partially address rust-lang#71039. ``` error: expected expression, found `1 + 1` --> $DIR/trace_faulty_macros.rs:49:37 | LL | (let $p:pat = $e:expr) => {test!(($p,$e))}; | -- this is interpreted as pattern `1 + 1` (in expansion #2) ... LL | (($p:pat, $e:pat)) => {let $p = $e;}; | ^^ expected expression ... LL | test!(let x = 1+1); | ------------------ | | | | | this is interpreted as expression `1 + 1` (in expansion #1) | in this macro invocation | = note: this error originates in the macro `test` (in Nightly builds, run with -Z macro-backtrace for more info) ```
2 parents e20cb77 + 33d9f1e commit d680dc5

27 files changed

+208
-80
lines changed

compiler/rustc_ast/src/attr/mod.rs

Lines changed: 1 addition & 1 deletion
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

Lines changed: 4 additions & 1 deletion
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

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ impl Lit {
112112
}
113113
Literal(token_lit) => Some(token_lit),
114114
Interpolated(ref nt)
115-
if let NtExpr(expr) | NtLiteral(expr) = &**nt
115+
if let NtExpr(expr) | NtLiteral(expr) = &nt.0
116116
&& let ast::ExprKind::Lit(token_lit) = expr.kind =>
117117
{
118118
Some(token_lit)
@@ -289,7 +289,7 @@ pub enum TokenKind {
289289
/// - It prevents `Token` from implementing `Copy`.
290290
/// It adds complexity and likely slows things down. Please don't add new
291291
/// occurrences of this token kind!
292-
Interpolated(Lrc<Nonterminal>),
292+
Interpolated(Lrc<(Nonterminal, Span)>),
293293

294294
/// A doc comment token.
295295
/// `Symbol` is the doc comment's data excluding its "quotes" (`///`, `/**`, etc)
@@ -395,7 +395,7 @@ impl Token {
395395
/// if they keep spans or perform edition checks.
396396
pub fn uninterpolated_span(&self) -> Span {
397397
match &self.kind {
398-
Interpolated(nt) => nt.span(),
398+
Interpolated(nt) => nt.0.use_span(),
399399
_ => self.span,
400400
}
401401
}
@@ -438,7 +438,7 @@ impl Token {
438438
ModSep | // global path
439439
Lifetime(..) | // labeled loop
440440
Pound => true, // expression attributes
441-
Interpolated(ref nt) => matches!(**nt, NtLiteral(..) |
441+
Interpolated(ref nt) => matches!(&nt.0, NtLiteral(..) |
442442
NtExpr(..) |
443443
NtBlock(..) |
444444
NtPath(..)),
@@ -462,7 +462,7 @@ impl Token {
462462
| DotDot | DotDotDot | DotDotEq // ranges
463463
| Lt | BinOp(Shl) // associated path
464464
| ModSep => true, // global path
465-
Interpolated(ref nt) => matches!(**nt, NtLiteral(..) |
465+
Interpolated(ref nt) => matches!(&nt.0, NtLiteral(..) |
466466
NtPat(..) |
467467
NtBlock(..) |
468468
NtPath(..)),
@@ -485,7 +485,7 @@ impl Token {
485485
Lifetime(..) | // lifetime bound in trait object
486486
Lt | BinOp(Shl) | // associated path
487487
ModSep => true, // global path
488-
Interpolated(ref nt) => matches!(**nt, NtTy(..) | NtPath(..)),
488+
Interpolated(ref nt) => matches!(&nt.0, NtTy(..) | NtPath(..)),
489489
// For anonymous structs or unions, which only appear in specific positions
490490
// (type of struct fields or union fields), we don't consider them as regular types
491491
_ => false,
@@ -496,7 +496,7 @@ impl Token {
496496
pub fn can_begin_const_arg(&self) -> bool {
497497
match self.kind {
498498
OpenDelim(Delimiter::Brace) => true,
499-
Interpolated(ref nt) => matches!(**nt, NtExpr(..) | NtBlock(..) | NtLiteral(..)),
499+
Interpolated(ref nt) => matches!(&nt.0, NtExpr(..) | NtBlock(..) | NtLiteral(..)),
500500
_ => self.can_begin_literal_maybe_minus(),
501501
}
502502
}
@@ -550,8 +550,8 @@ impl Token {
550550
match self.uninterpolate().kind {
551551
Literal(..) | BinOp(Minus) => true,
552552
Ident(name, false) if name.is_bool_lit() => true,
553-
Interpolated(ref nt) => match &**nt {
554-
NtLiteral(_) => true,
553+
Interpolated(ref nt) => match &nt.0 {
554+
NtLiteral(..) => true,
555555
NtExpr(e) => match &e.kind {
556556
ast::ExprKind::Lit(_) => true,
557557
ast::ExprKind::Unary(ast::UnOp::Neg, e) => {
@@ -571,9 +571,9 @@ impl Token {
571571
/// otherwise returns the original token.
572572
pub fn uninterpolate(&self) -> Cow<'_, Token> {
573573
match &self.kind {
574-
Interpolated(nt) => match **nt {
574+
Interpolated(nt) => match &nt.0 {
575575
NtIdent(ident, is_raw) => {
576-
Cow::Owned(Token::new(Ident(ident.name, is_raw), ident.span))
576+
Cow::Owned(Token::new(Ident(ident.name, *is_raw), ident.span))
577577
}
578578
NtLifetime(ident) => Cow::Owned(Token::new(Lifetime(ident.name), ident.span)),
579579
_ => Cow::Borrowed(self),
@@ -588,8 +588,8 @@ impl Token {
588588
// We avoid using `Token::uninterpolate` here because it's slow.
589589
match &self.kind {
590590
&Ident(name, is_raw) => Some((Ident::new(name, self.span), is_raw)),
591-
Interpolated(nt) => match **nt {
592-
NtIdent(ident, is_raw) => Some((ident, is_raw)),
591+
Interpolated(nt) => match &nt.0 {
592+
NtIdent(ident, is_raw) => Some((*ident, *is_raw)),
593593
_ => None,
594594
},
595595
_ => None,
@@ -602,8 +602,8 @@ impl Token {
602602
// We avoid using `Token::uninterpolate` here because it's slow.
603603
match &self.kind {
604604
&Lifetime(name) => Some(Ident::new(name, self.span)),
605-
Interpolated(nt) => match **nt {
606-
NtLifetime(ident) => Some(ident),
605+
Interpolated(nt) => match &nt.0 {
606+
NtLifetime(ident) => Some(*ident),
607607
_ => None,
608608
},
609609
_ => None,
@@ -628,7 +628,7 @@ impl Token {
628628

629629
/// Returns `true` if the token is an interpolated path.
630630
fn is_path(&self) -> bool {
631-
if let Interpolated(nt) = &self.kind && let NtPath(..) = **nt {
631+
if let Interpolated(nt) = &self.kind && let NtPath(..) = &nt.0 {
632632
return true;
633633
}
634634

@@ -640,7 +640,7 @@ impl Token {
640640
/// (which happens while parsing the result of macro expansion)?
641641
pub fn is_whole_expr(&self) -> bool {
642642
if let Interpolated(nt) = &self.kind
643-
&& let NtExpr(_) | NtLiteral(_) | NtPath(_) | NtBlock(_) = **nt
643+
&& let NtExpr(..) | NtLiteral(..) | NtPath(..) | NtBlock(..) = &nt.0
644644
{
645645
return true;
646646
}
@@ -650,7 +650,7 @@ impl Token {
650650

651651
/// Is the token an interpolated block (`$b:block`)?
652652
pub fn is_whole_block(&self) -> bool {
653-
if let Interpolated(nt) = &self.kind && let NtBlock(..) = **nt {
653+
if let Interpolated(nt) = &self.kind && let NtBlock(..) = &nt.0 {
654654
return true;
655655
}
656656

@@ -897,7 +897,7 @@ impl fmt::Display for NonterminalKind {
897897
}
898898

899899
impl Nonterminal {
900-
pub fn span(&self) -> Span {
900+
pub fn use_span(&self) -> Span {
901901
match self {
902902
NtItem(item) => item.span,
903903
NtBlock(block) => block.span,
@@ -911,6 +911,23 @@ impl Nonterminal {
911911
NtVis(vis) => vis.span,
912912
}
913913
}
914+
915+
pub fn descr(&self) -> &'static str {
916+
match self {
917+
NtItem(..) => "item",
918+
NtBlock(..) => "block",
919+
NtStmt(..) => "statement",
920+
NtPat(..) => "pattern",
921+
NtExpr(..) => "expression",
922+
NtLiteral(..) => "literal",
923+
NtTy(..) => "type",
924+
NtIdent(..) => "identifier",
925+
NtLifetime(..) => "lifetime",
926+
NtMeta(..) => "attribute",
927+
NtPath(..) => "path",
928+
NtVis(..) => "visibility",
929+
}
930+
}
914931
}
915932

916933
impl PartialEq for Nonterminal {

compiler/rustc_ast/src/tokenstream.rs

Lines changed: 2 additions & 2 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 6 additions & 0 deletions
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -226,19 +226,19 @@ impl FromInternal<(TokenStream, &mut Rustc<'_, '_>)> for Vec<TokenTree<TokenStre
226226
}));
227227
}
228228

229-
Interpolated(nt) if let NtIdent(ident, is_raw) = *nt => {
230-
trees.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 }))
231231
}
232232

233233
Interpolated(nt) => {
234-
let stream = TokenStream::from_nonterminal_ast(&nt);
234+
let stream = TokenStream::from_nonterminal_ast(&nt.0);
235235
// A hack used to pass AST fragments to attribute and derive
236236
// macros as a single nonterminal token instead of a token
237237
// stream. Such token needs to be "unwrapped" and not
238238
// represented as a delimited group.
239239
// FIXME: It needs to be removed, but there are some
240240
// compatibility issues (see #73345).
241-
if crate::base::nt_pretty_printing_compatibility_hack(&nt, rustc.sess()) {
241+
if crate::base::nt_pretty_printing_compatibility_hack(&nt.0, rustc.sess()) {
242242
trees.extend(Self::from_internal((stream, rustc)));
243243
} else {
244244
trees.push(TokenTree::Group(Group {

compiler/rustc_parse/src/parser/attr.rs

Lines changed: 2 additions & 2 deletions
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

Lines changed: 56 additions & 2 deletions
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;
@@ -2163,6 +2164,59 @@ impl<'a> Parser<'a> {
21632164
err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp));
21642165
}
21652166
err.span_label(span, "expected expression");
2167+
2168+
// Walk the chain of macro expansions for the current token to point at how the original
2169+
// code was interpreted. This helps the user realize when a macro argument of one type is
2170+
// later reinterpreted as a different type, like `$x:expr` being reinterpreted as `$x:pat`
2171+
// in a subsequent macro invocation (#71039).
2172+
let mut tok = self.token.clone();
2173+
let mut labels = vec![];
2174+
while let TokenKind::Interpolated(node) = &tok.kind {
2175+
let tokens = node.0.tokens();
2176+
labels.push(node.clone());
2177+
if let Some(tokens) = tokens
2178+
&& let tokens = tokens.to_attr_token_stream()
2179+
&& let tokens = tokens.0.deref()
2180+
&& let [AttrTokenTree::Token(token, _)] = &tokens[..]
2181+
{
2182+
tok = token.clone();
2183+
} else {
2184+
break;
2185+
}
2186+
}
2187+
let mut iter = labels.into_iter().peekable();
2188+
let mut show_link = false;
2189+
while let Some(node) = iter.next() {
2190+
let descr = node.0.descr();
2191+
if let Some(next) = iter.peek() {
2192+
let next_descr = next.0.descr();
2193+
if next_descr != descr {
2194+
err.span_label(next.1, format!("this macro fragment matcher is {next_descr}"));
2195+
err.span_label(node.1, format!("this macro fragment matcher is {descr}"));
2196+
err.span_label(
2197+
next.0.use_span(),
2198+
format!("this is expected to be {next_descr}"),
2199+
);
2200+
err.span_label(
2201+
node.0.use_span(),
2202+
format!(
2203+
"this is interpreted as {}, but it is expected to be {}",
2204+
next_descr, descr,
2205+
),
2206+
);
2207+
show_link = true;
2208+
} else {
2209+
err.span_label(node.1, "");
2210+
}
2211+
}
2212+
}
2213+
if show_link {
2214+
err.note(
2215+
"when forwarding a matched fragment to another macro-by-example, matchers in the \
2216+
second macro will see an opaque AST of the fragment type, not the underlying \
2217+
tokens",
2218+
);
2219+
}
21662220
err
21672221
}
21682222

compiler/rustc_parse/src/parser/expr.rs

Lines changed: 2 additions & 2 deletions
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)