Skip to content

Commit 7eb3204

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 7eb3204

File tree

5 files changed

+218
-123
lines changed

5 files changed

+218
-123
lines changed

compiler/rustc_expand/messages.ftl

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

compiler/rustc_expand/src/errors.rs

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

499+
#[derive(Diagnostic)]
500+
#[diag(expand_mve_extra_tokens)]
501+
pub(crate) struct MveExtraTokens {
502+
#[primary_span]
503+
#[suggestion(code = "", applicability = "machine-applicable")]
504+
pub span: Span,
505+
#[label]
506+
pub ident_span: Span,
507+
pub extra_count: usize,
508+
509+
// Only used for extra tokens in the arg position. These should be `Option`al but
510+
// `derive(Diagnostic)` doesn't allow that.
511+
#[note]
512+
pub args_note: Option<()>,
513+
pub max_args: usize,
514+
pub name: &'static str,
515+
}
516+
517+
#[derive(Diagnostic)]
518+
#[note]
519+
#[diag(expand_mve_missing_paren)]
520+
pub(crate) struct MveMissingParen {
521+
#[primary_span]
522+
#[label]
523+
pub ident_span: Span,
524+
#[label(expand_unexpected)]
525+
pub unexpected_span: Option<Span>,
526+
#[suggestion(code = "( /* ... */ )", applicability = "has-placeholders")]
527+
pub insert_span: Option<Span>,
528+
}
529+
499530
#[derive(Diagnostic)]
500531
#[diag(expand_mve_unrecognized_var)]
501532
pub(crate) struct MveUnrecognizedVar {

compiler/rustc_expand/src/mbe/metavar_expr.rs

Lines changed: 72 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,33 @@ 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 (unexpected_span, insert_span) = match next {
58+
Some(TokenTree::Delimited(..)) => (None, None),
59+
Some(tt) => (Some(tt.span()), None),
60+
None => (None, Some(ident.span.shrink_to_hi())),
61+
};
62+
let err =
63+
errors::MveMissingParen { ident_span: ident.span, unexpected_span, insert_span };
64+
return Err(psess.dcx().create_err(err));
4665
};
47-
check_trailing_token(&mut iter, psess)?;
66+
67+
// Ensure there are no other tokens in the
68+
if iter.peek().is_some() {
69+
let span = iter_span(&iter).expect("checked is_some above");
70+
let err = errors::MveExtraTokens {
71+
span,
72+
ident_span: ident.span,
73+
extra_count: iter.count(),
74+
args_note: None,
75+
max_args: 0,
76+
name: "",
77+
};
78+
return Err(psess.dcx().create_err(err));
79+
}
80+
4881
let mut iter = args.iter();
4982
let rslt = match ident.as_str() {
5083
"concat" => parse_concat(&mut iter, psess, outer_span, ident.span)?,
@@ -67,7 +100,7 @@ impl MetaVarExpr {
67100
return Err(err);
68101
}
69102
};
70-
check_trailing_token(&mut iter, psess)?;
103+
check_trailing_tokens(&mut iter, psess, ident)?;
71104
Ok(rslt)
72105
}
73106

@@ -87,20 +120,44 @@ impl MetaVarExpr {
87120
}
88121
}
89122

90-
// Checks if there are any remaining tokens. For example, `${ignore(ident ... a b c ...)}`
91-
fn check_trailing_token<'psess>(
123+
/// Checks if there are any remaining tokens. For example, `${ignore(ident ... a b c ...)}`
124+
fn check_trailing_tokens<'psess>(
92125
iter: &mut TokenStreamIter<'_>,
93126
psess: &'psess ParseSess,
127+
ident: Ident,
94128
) -> 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(())
129+
if iter.peek().is_none() {
130+
// All tokens used, no problem
131+
return Ok(());
103132
}
133+
134+
let (name, max) = EXPR_NAME_ARG_MAP
135+
.iter()
136+
.find(|(name, _)| *name == ident.as_str())
137+
.expect("called with an invalid name");
138+
139+
// For expressions like `concat`, all tokens should be consumed already
140+
let max =
141+
max.unwrap_or_else(|| panic!("{name} takes unlimited tokens but didn't eat them all"));
142+
143+
let err = errors::MveExtraTokens {
144+
span: iter_span(iter).expect("checked is_none above"),
145+
ident_span: ident.span,
146+
extra_count: iter.count(),
147+
args_note: Some(()),
148+
max_args: max,
149+
name,
150+
};
151+
Err(psess.dcx().create_err(err))
152+
}
153+
154+
/// Returns a span encompassing all tokens in the iterator if there is at least one item.
155+
fn iter_span(iter: &TokenStreamIter<'_>) -> Option<Span> {
156+
let mut iter = iter.clone(); // cloning is cheap
157+
let first_sp = iter.next()?.span();
158+
let last_sp = iter.last().map(TokenTree::span).unwrap_or(first_sp);
159+
let span = first_sp.with_hi(last_sp.hi());
160+
Some(span)
104161
}
105162

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

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

Lines changed: 17 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]
@@ -45,9 +45,14 @@ macro_rules! open_brackets_with_lit {
4545
//~^ ERROR expected identifier
4646
}
4747

48+
macro_rules! mvs_missing_paren {
49+
( $( $i:ident ),* ) => { ${ count $i ($i) } };
50+
//~^ ERROR expected `(`
51+
}
52+
4853
macro_rules! mve_wrong_delim {
4954
( $( $i:ident ),* ) => { ${ count{i} } };
50-
//~^ ERROR meta-variable expression parameter must be wrapped in parentheses
55+
//~^ ERROR expected `(`
5156
}
5257

5358
macro_rules! invalid_metavar {
@@ -64,28 +69,28 @@ macro_rules! open_brackets_with_group {
6469
macro_rules! extra_garbage_after_metavar {
6570
( $( $i:ident ),* ) => {
6671
${count() a b c}
67-
//~^ ERROR unexpected token: a
72+
//~^ ERROR unexpected trailing tokens
6873
${count($i a b c)}
69-
//~^ ERROR unexpected token: a
74+
//~^ ERROR unexpected trailing tokens
7075
${count($i, 1 a b c)}
71-
//~^ ERROR unexpected token: a
76+
//~^ ERROR unexpected trailing tokens
7277
${count($i) a b c}
73-
//~^ ERROR unexpected token: a
78+
//~^ ERROR unexpected trailing tokens
7479

7580
${ignore($i) a b c}
76-
//~^ ERROR unexpected token: a
81+
//~^ ERROR unexpected trailing tokens
7782
${ignore($i a b c)}
78-
//~^ ERROR unexpected token: a
83+
//~^ ERROR unexpected trailing tokens
7984

8085
${index() a b c}
81-
//~^ ERROR unexpected token: a
86+
//~^ ERROR unexpected trailing tokens
8287
${index(1 a b c)}
83-
//~^ ERROR unexpected token: a
88+
//~^ ERROR unexpected trailing tokens
8489

8590
${index() a b c}
86-
//~^ ERROR unexpected token: a
91+
//~^ ERROR unexpected trailing tokens
8792
${index(1 a b c)}
88-
//~^ ERROR unexpected token: a
93+
//~^ ERROR unexpected trailing tokens
8994
};
9095
}
9196

0 commit comments

Comments
 (0)