Skip to content

Commit 173d3dc

Browse files
committed
mbe: Refactor diagnostics for invalid metavar expression syntax
Give a more user-friendly diagnostic about the following: * Invalid syntax within the `${...}` braces, including missing parentheses or trailing tokens. * Incorrect number of arguments passed to specific metavariable expressions.
1 parent 837c5dd commit 173d3dc

File tree

5 files changed

+172
-108
lines changed

5 files changed

+172
-108
lines changed

compiler/rustc_expand/messages.ftl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,25 @@ expand_module_multiple_candidates =
133133
expand_must_repeat_once =
134134
this must repeat at least once
135135
136+
expand_mve_extra_tokens_in_braces =
137+
unexpected trailing tokens in metavariable expression braces
138+
.suggestion = try removing these tokens
139+
140+
expand_mve_extra_tokens_in_expr =
141+
unexpected trailing tokens in metavariable expression
142+
.label = for this metavariable expression
143+
.note= the `{$name}` metavariable expression takes up to {$max} arguments
144+
.suggestion = try removing {$count ->
145+
[one] this token
146+
*[other] these tokens
147+
}
148+
149+
expand_mve_missing_paren =
150+
expected `(`
151+
.label = for this this metavariable expression
152+
.note = metavariable expressions use function-like parentheses syntax
153+
.suggestion = try adding parentheses
154+
136155
expand_mve_unrecognized_var =
137156
variable `{$key}` is not recognized in meta-variable expression
138157

compiler/rustc_expand/src/errors.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,38 @@ pub(crate) use metavar_exprs::*;
496496
mod metavar_exprs {
497497
use super::*;
498498

499+
#[derive(Diagnostic)]
500+
#[diag(expand_mve_extra_tokens_in_braces)]
501+
pub(crate) struct MveExtraTokensInBraces {
502+
#[primary_span]
503+
#[suggestion(code = "", applicability = "machine-applicable")]
504+
pub span: Span,
505+
}
506+
507+
#[derive(Diagnostic)]
508+
#[note]
509+
#[diag(expand_mve_extra_tokens_in_expr)]
510+
pub(crate) struct MveExtraTokensInExpr {
511+
#[primary_span]
512+
#[suggestion(code = "", applicability = "machine-applicable")]
513+
pub span: Span,
514+
#[label]
515+
pub ident_span: Span,
516+
pub count: usize,
517+
pub max: usize,
518+
pub name: &'static str,
519+
}
520+
521+
#[derive(Diagnostic)]
522+
#[note]
523+
#[diag(expand_mve_missing_paren)]
524+
pub(crate) struct MveMissingParen {
525+
#[primary_span]
526+
pub span: Span,
527+
#[suggestion(code = "( /* ... */ )", applicability = "has-placeholders")]
528+
pub insert_span: Option<Span>,
529+
}
530+
499531
#[derive(Diagnostic)]
500532
#[diag(expand_mve_unrecognized_var)]
501533
pub(crate) struct MveUnrecognizedVar {

compiler/rustc_expand/src/mbe/metavar_expr.rs

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,20 @@ use rustc_macros::{Decodable, Encodable};
77
use rustc_session::parse::ParseSess;
88
use rustc_span::{Ident, Span, Symbol};
99

10+
use crate::errors;
11+
1012
pub(crate) const RAW_IDENT_ERR: &str = "`${concat(..)}` currently does not support raw identifiers";
1113
pub(crate) const UNSUPPORTED_CONCAT_ELEM_ERR: &str = "expected identifier or string literal";
1214

15+
/// Map from expression names to the maximum arg count.
16+
const EXPR_NAME_ARG_MAP: &[(&str, Option<usize>)] = &[
17+
("concat", None),
18+
("count", Some(2)),
19+
("ignore", Some(1)),
20+
("index", Some(2)),
21+
("len", Some(2)),
22+
];
23+
1324
/// A meta-variable expression, for expansions based on properties of meta-variables.
1425
#[derive(Debug, PartialEq, Encodable, Decodable)]
1526
pub(crate) enum MetaVarExpr {
@@ -40,11 +51,25 @@ impl MetaVarExpr {
4051
) -> PResult<'psess, MetaVarExpr> {
4152
let mut iter = input.iter();
4253
let ident = parse_ident(&mut iter, psess, outer_span)?;
43-
let Some(TokenTree::Delimited(.., Delimiter::Parenthesis, args)) = iter.next() else {
44-
let msg = "meta-variable expression parameter must be wrapped in parentheses";
45-
return Err(psess.dcx().struct_span_err(ident.span, msg));
54+
let next = iter.next();
55+
let Some(TokenTree::Delimited(.., Delimiter::Parenthesis, args)) = next else {
56+
// No `()`; wrong or no delimiters
57+
let (span, insert_span) = match next {
58+
Some(TokenTree::Delimited(delim, ..)) => (delim.open, None),
59+
Some(tt) => (tt.span(), Some(ident.span.shrink_to_hi())),
60+
None => (ident.span.shrink_to_hi(), Some(ident.span.shrink_to_hi())),
61+
};
62+
let err = errors::MveMissingParen { span, insert_span };
63+
return Err(psess.dcx().create_err(err));
4664
};
47-
check_trailing_token(&mut iter, psess)?;
65+
66+
// Ensure there are no other tokens in the
67+
if iter.peek().is_some() {
68+
let span = iter_span(&iter).expect("checked is_some above");
69+
let err = errors::MveExtraTokensInBraces { span };
70+
return Err(psess.dcx().create_err(err));
71+
}
72+
4873
let mut iter = args.iter();
4974
let rslt = match ident.as_str() {
5075
"concat" => parse_concat(&mut iter, psess, outer_span, ident.span)?,
@@ -67,7 +92,7 @@ impl MetaVarExpr {
6792
return Err(err);
6893
}
6994
};
70-
check_trailing_token(&mut iter, psess)?;
95+
check_trailing_tokens(&mut iter, psess, ident)?;
7196
Ok(rslt)
7297
}
7398

@@ -87,20 +112,44 @@ impl MetaVarExpr {
87112
}
88113
}
89114

90-
// Checks if there are any remaining tokens. For example, `${ignore(ident ... a b c ...)}`
91-
fn check_trailing_token<'psess>(
115+
/// Checks if there are any remaining tokens. For example, `${ignore(ident ... a b c ...)}`
116+
fn check_trailing_tokens<'psess>(
92117
iter: &mut TokenStreamIter<'_>,
93118
psess: &'psess ParseSess,
119+
ident: Ident,
94120
) -> PResult<'psess, ()> {
95-
if let Some(tt) = iter.next() {
96-
let mut diag = psess
97-
.dcx()
98-
.struct_span_err(tt.span(), format!("unexpected token: {}", pprust::tt_to_string(tt)));
99-
diag.span_note(tt.span(), "meta-variable expression must not have trailing tokens");
100-
Err(diag)
101-
} else {
102-
Ok(())
121+
if iter.peek().is_none() {
122+
// All tokens used, no problem
123+
return Ok(());
103124
}
125+
126+
let (name, max) = EXPR_NAME_ARG_MAP
127+
.iter()
128+
.find(|(name, _)| *name == ident.as_str())
129+
.expect("called with an invalid name");
130+
131+
let Some(max) = *max else {
132+
// For expressions like `concat`, all tokens should be consumed already
133+
panic!("{name} takes unlimited tokens but didn't eat them all");
134+
};
135+
136+
let err = errors::MveExtraTokensInExpr {
137+
span: iter_span(iter).expect("checked is_none above"),
138+
ident_span: ident.span,
139+
count: iter.count(),
140+
max,
141+
name,
142+
};
143+
Err(psess.dcx().create_err(err))
144+
}
145+
146+
/// Returns a span encompassing all tokens in the iterator if there is at least one item.
147+
fn iter_span(iter: &TokenStreamIter<'_>) -> Option<Span> {
148+
let mut iter = iter.clone(); // cloning is cheap
149+
let first_sp = iter.next()?.span();
150+
let last_sp = iter.last().map(TokenTree::span).unwrap_or(first_sp);
151+
let span = first_sp.with_hi(last_sp.hi());
152+
Some(span)
104153
}
105154

106155
/// Indicates what is placed in a `concat` parameter. For example, literals

tests/ui/macros/metavar-expressions/syntax-errors.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ macro_rules! metavar_with_literal_suffix {
3030

3131
macro_rules! mve_without_parens {
3232
( $( $i:ident ),* ) => { ${ count } };
33-
//~^ ERROR meta-variable expression parameter must be wrapped in parentheses
33+
//~^ ERROR expected `(`
3434
}
3535

3636
#[rustfmt::skip]
@@ -47,7 +47,7 @@ macro_rules! open_brackets_with_lit {
4747

4848
macro_rules! mve_wrong_delim {
4949
( $( $i:ident ),* ) => { ${ count{i} } };
50-
//~^ ERROR meta-variable expression parameter must be wrapped in parentheses
50+
//~^ ERROR expected `(`
5151
}
5252

5353
macro_rules! invalid_metavar {
@@ -64,28 +64,28 @@ macro_rules! open_brackets_with_group {
6464
macro_rules! extra_garbage_after_metavar {
6565
( $( $i:ident ),* ) => {
6666
${count() a b c}
67-
//~^ ERROR unexpected token: a
67+
//~^ ERROR unexpected trailing tokens
6868
${count($i a b c)}
69-
//~^ ERROR unexpected token: a
69+
//~^ ERROR unexpected trailing tokens
7070
${count($i, 1 a b c)}
71-
//~^ ERROR unexpected token: a
71+
//~^ ERROR unexpected trailing tokens
7272
${count($i) a b c}
73-
//~^ ERROR unexpected token: a
73+
//~^ ERROR unexpected trailing tokens
7474

7575
${ignore($i) a b c}
76-
//~^ ERROR unexpected token: a
76+
//~^ ERROR unexpected trailing tokens
7777
${ignore($i a b c)}
78-
//~^ ERROR unexpected token: a
78+
//~^ ERROR unexpected trailing tokens
7979

8080
${index() a b c}
81-
//~^ ERROR unexpected token: a
81+
//~^ ERROR unexpected trailing tokens
8282
${index(1 a b c)}
83-
//~^ ERROR unexpected token: a
83+
//~^ ERROR unexpected trailing tokens
8484

8585
${index() a b c}
86-
//~^ ERROR unexpected token: a
86+
//~^ ERROR unexpected trailing tokens
8787
${index(1 a b c)}
88-
//~^ ERROR unexpected token: a
88+
//~^ ERROR unexpected trailing tokens
8989
};
9090
}
9191

0 commit comments

Comments
 (0)