Skip to content

Commit 17f5a09

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 2183597 commit 17f5a09

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
@@ -141,6 +141,25 @@ expand_mve_expected_ident =
141141
.ignore_expr_note = `ignore` takes a metavariable argument
142142
.count_expr_note = `count` takes a metavariable argument
143143

144+
expand_mve_extra_tokens_in_braces =
145+
unexpected trailing tokens in metavariable expression braces
146+
.suggestion = try removing these tokens
147+
148+
expand_mve_extra_tokens_in_expr =
149+
unexpected trailing tokens in metavariable expression
150+
.label = for this metavariable expression
151+
.note= the `{$name}` metavariable expression takes up to {$max} arguments
152+
.suggestion = try removing {$count ->
153+
[one] this token
154+
*[other] these tokens
155+
}
156+
157+
expand_mve_missing_paren =
158+
expected `(`
159+
.label = for this this metavariable expression
160+
.note = metavariable expressions use function-like parentheses syntax
161+
.suggestion = try adding parentheses
162+
144163
expand_mve_unrecognized_var =
145164
variable `{$key}` is not recognized in meta-variable expression
146165

compiler/rustc_expand/src/errors.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,38 @@ mod metavar_exprs {
521521
Count,
522522
}
523523

524+
#[derive(Diagnostic)]
525+
#[diag(expand_mve_extra_tokens_in_braces)]
526+
pub(crate) struct MveExtraTokensInBraces {
527+
#[primary_span]
528+
#[suggestion(code = "", applicability = "machine-applicable")]
529+
pub span: Span,
530+
}
531+
532+
#[derive(Diagnostic)]
533+
#[note]
534+
#[diag(expand_mve_extra_tokens_in_expr)]
535+
pub(crate) struct MveExtraTokensInExpr {
536+
#[primary_span]
537+
#[suggestion(code = "", applicability = "machine-applicable")]
538+
pub span: Span,
539+
#[label]
540+
pub ident_span: Span,
541+
pub count: usize,
542+
pub max: usize,
543+
pub name: &'static str,
544+
}
545+
546+
#[derive(Diagnostic)]
547+
#[note]
548+
#[diag(expand_mve_missing_paren)]
549+
pub(crate) struct MveMissingParen {
550+
#[primary_span]
551+
pub span: Span,
552+
#[suggestion(code = "( /* ... */ )", applicability = "has-placeholders")]
553+
pub insert_span: Option<Span>,
554+
}
555+
524556
#[derive(Diagnostic)]
525557
#[diag(expand_mve_unrecognized_var)]
526558
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
@@ -12,8 +12,18 @@ use crate::errors::{self, MveExpectedIdentContext};
1212
pub(crate) const RAW_IDENT_ERR: &str = "`${concat(..)}` currently does not support raw identifiers";
1313
pub(crate) const UNSUPPORTED_CONCAT_ELEM_ERR: &str = "expected identifier or string literal";
1414

15+
/// List of the below list for diagnostics.
1516
const VALID_METAVAR_EXPR_NAMES: &str = "`count`, `ignore`, `index`, `len`, and `concat`";
1617

18+
/// Map from expression names to the maximum arg count.
19+
const EXPR_NAME_ARG_MAP: &[(&str, Option<usize>)] = &[
20+
("concat", None),
21+
("count", Some(2)),
22+
("ignore", Some(1)),
23+
("index", Some(2)),
24+
("len", Some(2)),
25+
];
26+
1727
/// A meta-variable expression, for expansions based on properties of meta-variables.
1828
#[derive(Debug, PartialEq, Encodable, Decodable)]
1929
pub(crate) enum MetaVarExpr {
@@ -49,11 +59,26 @@ impl MetaVarExpr {
4959
outer_span,
5060
MveExpectedIdentContext::ExprName { valid_expr_list: VALID_METAVAR_EXPR_NAMES },
5161
)?;
52-
let Some(TokenTree::Delimited(.., Delimiter::Parenthesis, args)) = iter.next() else {
53-
let msg = "meta-variable expression parameter must be wrapped in parentheses";
54-
return Err(psess.dcx().struct_span_err(ident.span, msg));
62+
63+
let next = iter.next();
64+
let Some(TokenTree::Delimited(.., Delimiter::Parenthesis, args)) = next else {
65+
// No `()`; wrong or no delimiters
66+
let (span, insert_span) = match next {
67+
Some(TokenTree::Delimited(delim, ..)) => (delim.open, None),
68+
Some(tt) => (tt.span(), Some(ident.span.shrink_to_hi())),
69+
None => (ident.span.shrink_to_hi(), Some(ident.span.shrink_to_hi())),
70+
};
71+
let err = errors::MveMissingParen { span, insert_span };
72+
return Err(psess.dcx().create_err(err));
5573
};
56-
check_trailing_token(&mut iter, psess)?;
74+
75+
// Ensure there are no other tokens in the
76+
if iter.peek().is_some() {
77+
let span = iter_span(&iter).expect("checked is_some above");
78+
let err = errors::MveExtraTokensInBraces { span };
79+
return Err(psess.dcx().create_err(err));
80+
}
81+
5782
let mut iter = args.iter();
5883
let rslt = match ident.as_str() {
5984
"concat" => parse_concat(&mut iter, psess, outer_span, ident.span)?,
@@ -78,7 +103,7 @@ impl MetaVarExpr {
78103
return Err(err);
79104
}
80105
};
81-
check_trailing_token(&mut iter, psess)?;
106+
check_trailing_tokens(&mut iter, psess, ident)?;
82107
Ok(rslt)
83108
}
84109

@@ -98,20 +123,44 @@ impl MetaVarExpr {
98123
}
99124
}
100125

101-
// Checks if there are any remaining tokens. For example, `${ignore(ident ... a b c ...)}`
102-
fn check_trailing_token<'psess>(
126+
/// Checks if there are any remaining tokens. For example, `${ignore(ident ... a b c ...)}`
127+
fn check_trailing_tokens<'psess>(
103128
iter: &mut TokenStreamIter<'_>,
104129
psess: &'psess ParseSess,
130+
ident: Ident,
105131
) -> PResult<'psess, ()> {
106-
if let Some(tt) = iter.next() {
107-
let mut diag = psess
108-
.dcx()
109-
.struct_span_err(tt.span(), format!("unexpected token: {}", pprust::tt_to_string(tt)));
110-
diag.span_note(tt.span(), "meta-variable expression must not have trailing tokens");
111-
Err(diag)
112-
} else {
113-
Ok(())
132+
if iter.peek().is_none() {
133+
// All tokens used, no problem
134+
return Ok(());
114135
}
136+
137+
let (name, max) = EXPR_NAME_ARG_MAP
138+
.iter()
139+
.find(|(name, _)| *name == ident.as_str())
140+
.expect("called with an invalid name");
141+
142+
let Some(max) = *max else {
143+
// For expressions like `concat`, all tokens should be consumed already
144+
panic!("{name} takes unlimited tokens but didn't eat them all");
145+
};
146+
147+
let err = errors::MveExtraTokensInExpr {
148+
span: iter_span(iter).expect("checked is_none above"),
149+
ident_span: ident.span,
150+
count: iter.count(),
151+
max,
152+
name,
153+
};
154+
Err(psess.dcx().create_err(err))
155+
}
156+
157+
/// Returns a span encompassing all tokens in the iterator if there is at least one item.
158+
fn iter_span(iter: &TokenStreamIter<'_>) -> Option<Span> {
159+
let mut iter = iter.clone(); // cloning is cheap
160+
let first_sp = iter.next()?.span();
161+
let last_sp = iter.last().map(TokenTree::span).unwrap_or(first_sp);
162+
let span = first_sp.with_hi(last_sp.hi());
163+
Some(span)
115164
}
116165

117166
/// 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)