More detail when expecting expression but encountering bad macro argument

Partially address #71039.
This commit is contained in:
Esteban Küber 2023-07-31 14:55:47 +00:00
parent 1be1e84872
commit 4e418805da
27 changed files with 200 additions and 67 deletions

View file

@ -249,7 +249,7 @@ impl<'a> Parser<'a> {
/// The delimiters or `=` are still put into the resulting token stream.
pub fn parse_attr_item(&mut self, capture_tokens: bool) -> PResult<'a, ast::AttrItem> {
let item = match &self.token.kind {
token::Interpolated(nt) => match &**nt {
token::Interpolated(nt) => match &nt.0 {
Nonterminal::NtMeta(item) => Some(item.clone().into_inner()),
_ => None,
},
@ -369,7 +369,7 @@ impl<'a> Parser<'a> {
/// ```
pub fn parse_meta_item(&mut self) -> PResult<'a, ast::MetaItem> {
let nt_meta = match &self.token.kind {
token::Interpolated(nt) => match &**nt {
token::Interpolated(nt) => match &nt.0 {
token::NtMeta(e) => Some(e.clone()),
_ => None,
},

View file

@ -24,11 +24,12 @@ use crate::parser;
use rustc_ast as ast;
use rustc_ast::ptr::P;
use rustc_ast::token::{self, Delimiter, Lit, LitKind, TokenKind};
use rustc_ast::tokenstream::AttrTokenTree;
use rustc_ast::util::parser::AssocOp;
use rustc_ast::{
AngleBracketedArg, AngleBracketedArgs, AnonConst, AttrVec, BinOpKind, BindingAnnotation, Block,
BlockCheckMode, Expr, ExprKind, GenericArg, Generics, Item, ItemKind, Param, Pat, PatKind,
Path, PathSegment, QSelf, Ty, TyKind,
BlockCheckMode, Expr, ExprKind, GenericArg, Generics, HasTokens, Item, ItemKind, Param, Pat,
PatKind, Path, PathSegment, QSelf, Ty, TyKind,
};
use rustc_ast_pretty::pprust;
use rustc_data_structures::fx::FxHashSet;
@ -2252,6 +2253,59 @@ impl<'a> Parser<'a> {
err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp));
}
err.span_label(span, "expected expression");
// Walk the chain of macro expansions for the current token to point at how the original
// code was interpreted. This helps the user realize when a macro argument of one type is
// later reinterpreted as a different type, like `$x:expr` being reinterpreted as `$x:pat`
// in a subsequent macro invocation (#71039).
let mut tok = self.token.clone();
let mut labels = vec![];
while let TokenKind::Interpolated(node) = &tok.kind {
let tokens = node.0.tokens();
labels.push(node.clone());
if let Some(tokens) = tokens
&& let tokens = tokens.to_attr_token_stream()
&& let tokens = tokens.0.deref()
&& let [AttrTokenTree::Token(token, _)] = &tokens[..]
{
tok = token.clone();
} else {
break;
}
}
let mut iter = labels.into_iter().peekable();
let mut show_link = false;
while let Some(node) = iter.next() {
let descr = node.0.descr();
if let Some(next) = iter.peek() {
let next_descr = next.0.descr();
if next_descr != descr {
err.span_label(next.1, format!("this macro fragment matcher is {next_descr}"));
err.span_label(node.1, format!("this macro fragment matcher is {descr}"));
err.span_label(
next.0.use_span(),
format!("this is expected to be {next_descr}"),
);
err.span_label(
node.0.use_span(),
format!(
"this is interpreted as {}, but it is expected to be {}",
next_descr, descr,
),
);
show_link = true;
} else {
err.span_label(node.1, "");
}
}
}
if show_link {
err.note(
"when forwarding a matched fragment to another macro-by-example, matchers in the \
second macro will see an opaque AST of the fragment type, not the underlying \
tokens",
);
}
err
}

View file

@ -46,7 +46,7 @@ use thin_vec::{thin_vec, ThinVec};
macro_rules! maybe_whole_expr {
($p:expr) => {
if let token::Interpolated(nt) = &$p.token.kind {
match &**nt {
match &nt.0 {
token::NtExpr(e) | token::NtLiteral(e) => {
let e = e.clone();
$p.bump();
@ -1952,7 +1952,7 @@ impl<'a> Parser<'a> {
mk_lit_char: impl FnOnce(Symbol, Span) -> L,
) -> PResult<'a, L> {
if let token::Interpolated(nt) = &self.token.kind
&& let token::NtExpr(e) | token::NtLiteral(e) = &**nt
&& let token::NtExpr(e) | token::NtLiteral(e) = &nt.0
&& matches!(e.kind, ExprKind::Err)
{
let mut err = errors::InvalidInterpolatedExpression { span: self.token.span }

View file

@ -123,7 +123,7 @@ impl<'a> Parser<'a> {
// Don't use `maybe_whole` so that we have precise control
// over when we bump the parser
if let token::Interpolated(nt) = &self.token.kind
&& let token::NtItem(item) = &**nt
&& let token::NtItem(item) = &nt.0
{
let mut item = item.clone();
self.bump();
@ -2750,7 +2750,7 @@ impl<'a> Parser<'a> {
fn is_named_param(&self) -> bool {
let offset = match &self.token.kind {
token::Interpolated(nt) => match **nt {
token::Interpolated(nt) => match &nt.0 {
token::NtPat(..) => return self.look_ahead(1, |t| t == &token::Colon),
_ => 0,
},

View file

@ -93,7 +93,7 @@ pub enum TrailingToken {
macro_rules! maybe_whole {
($p:expr, $constructor:ident, |$x:ident| $e:expr) => {
if let token::Interpolated(nt) = &$p.token.kind {
if let token::$constructor(x) = &**nt {
if let token::$constructor(x) = &nt.0 {
let $x = x.clone();
$p.bump();
return Ok($e);
@ -110,7 +110,7 @@ macro_rules! maybe_recover_from_interpolated_ty_qpath {
&& $self.may_recover()
&& $self.look_ahead(1, |t| t == &token::ModSep)
&& let token::Interpolated(nt) = &$self.token.kind
&& let token::NtTy(ty) = &**nt
&& let token::NtTy(ty) = &nt.0
{
let ty = ty.clone();
$self.bump();
@ -367,12 +367,14 @@ impl TokenDescription {
pub(super) fn token_descr(token: &Token) -> String {
let name = pprust::token_to_string(token).to_string();
let kind = TokenDescription::from_token(token).map(|kind| match kind {
TokenDescription::ReservedIdentifier => "reserved identifier",
TokenDescription::Keyword => "keyword",
TokenDescription::ReservedKeyword => "reserved keyword",
TokenDescription::DocComment => "doc comment",
});
let kind = match (TokenDescription::from_token(token), &token.kind) {
(Some(TokenDescription::ReservedIdentifier), _) => Some("reserved identifier"),
(Some(TokenDescription::Keyword), _) => Some("keyword"),
(Some(TokenDescription::ReservedKeyword), _) => Some("reserved keyword"),
(Some(TokenDescription::DocComment), _) => Some("doc comment"),
(None, TokenKind::Interpolated(node)) => Some(node.0.descr()),
(None, _) => None,
};
if let Some(kind) = kind { format!("{kind} `{name}`") } else { format!("`{name}`") }
}
@ -662,7 +664,7 @@ impl<'a> Parser<'a> {
fn check_inline_const(&self, dist: usize) -> bool {
self.is_keyword_ahead(dist, &[kw::Const])
&& self.look_ahead(dist + 1, |t| match &t.kind {
token::Interpolated(nt) => matches!(**nt, token::NtBlock(..)),
token::Interpolated(nt) => matches!(&nt.0, token::NtBlock(..)),
token::OpenDelim(Delimiter::Brace) => true,
_ => false,
})

View file

@ -50,12 +50,12 @@ impl<'a> Parser<'a> {
NonterminalKind::Literal => token.can_begin_literal_maybe_minus(),
NonterminalKind::Vis => match token.kind {
// The follow-set of :vis + "priv" keyword + interpolated
token::Comma | token::Ident(..) | token::Interpolated(..) => true,
token::Comma | token::Ident(..) | token::Interpolated(_) => true,
_ => token.can_begin_type(),
},
NonterminalKind::Block => match &token.kind {
token::OpenDelim(Delimiter::Brace) => true,
token::Interpolated(nt) => match **nt {
token::Interpolated(nt) => match &nt.0 {
NtBlock(_) | NtLifetime(_) | NtStmt(_) | NtExpr(_) | NtLiteral(_) => true,
NtItem(_) | NtPat(_) | NtTy(_) | NtIdent(..) | NtMeta(_) | NtPath(_)
| NtVis(_) => false,
@ -64,7 +64,7 @@ impl<'a> Parser<'a> {
},
NonterminalKind::Path | NonterminalKind::Meta => match &token.kind {
token::ModSep | token::Ident(..) => true,
token::Interpolated(nt) => may_be_ident(nt),
token::Interpolated(nt) => may_be_ident(&nt.0),
_ => false,
},
NonterminalKind::PatParam { .. } | NonterminalKind::PatWithOr => {
@ -75,7 +75,7 @@ impl<'a> Parser<'a> {
token::BinOp(token::And) | // reference
token::BinOp(token::Minus) | // negative literal
token::AndAnd | // double reference
token::Literal(..) | // literal
token::Literal(_) | // literal
token::DotDot | // range pattern (future compat)
token::DotDotDot | // range pattern (future compat)
token::ModSep | // path
@ -83,14 +83,14 @@ impl<'a> Parser<'a> {
token::BinOp(token::Shl) => true, // path (double UFCS)
// leading vert `|` or-pattern
token::BinOp(token::Or) => matches!(kind, NonterminalKind::PatWithOr),
token::Interpolated(nt) => may_be_ident(nt),
token::Interpolated(nt) => may_be_ident(&nt.0),
_ => false,
}
}
NonterminalKind::Lifetime => match &token.kind {
token::Lifetime(_) => true,
token::Interpolated(nt) => {
matches!(**nt, NtLifetime(_))
matches!(&nt.0, NtLifetime(_))
}
_ => false,
},
@ -191,7 +191,7 @@ impl<'a> Parser<'a> {
panic!(
"Missing tokens for nt {:?} at {:?}: {:?}",
nt,
nt.span(),
nt.use_span(),
pprust::nonterminal_to_string(&nt)
);
}

View file

@ -592,7 +592,7 @@ impl<'a> Parser<'a> {
// Make sure we don't allow e.g. `let mut $p;` where `$p:pat`.
if let token::Interpolated(nt) = &self.token.kind {
if let token::NtPat(_) = **nt {
if let token::NtPat(..) = &nt.0 {
self.expected_ident_found_err().emit();
}
}

View file

@ -185,7 +185,7 @@ impl<'a> Parser<'a> {
});
if let token::Interpolated(nt) = &self.token.kind {
if let token::NtTy(ty) = &**nt {
if let token::NtTy(ty) = &nt.0 {
if let ast::TyKind::Path(None, path) = &ty.kind {
let path = path.clone();
self.bump();

View file

@ -53,7 +53,7 @@ impl<'a> Parser<'a> {
// Don't use `maybe_whole` so that we have precise control
// over when we bump the parser
if let token::Interpolated(nt) = &self.token.kind
&& let token::NtStmt(stmt) = &**nt
&& let token::NtStmt(stmt) = &nt.0
{
let mut stmt = stmt.clone();
self.bump();