Skip to content

mbe: Rework diagnostics for metavariable expressions #142950

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions compiler/rustc_expand/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,32 @@ expand_module_multiple_candidates =
expand_must_repeat_once =
this must repeat at least once

expand_mve_extra_tokens =
unexpected trailing tokens
.label = for this metavariable expression
.range = the `{$name}` metavariable expression takes between {$min_or_exact_args} and {$max_args} arguments
.exact = the `{$name}` metavariable expression takes {$min_or_exact_args ->
[zero] no arguments
[one] a single argument
*[other] {$min_or_exact_args} arguments
}
.suggestion = try removing {$extra_count ->
[one] this token
*[other] these tokens
}

expand_mve_missing_paren =
expected `(`
.label = for this this metavariable expression
.unexpected = unexpected token
.note = metavariable expressions use function-like parentheses syntax
.suggestion = try adding parentheses

expand_mve_unrecognized_expr =
unrecognized metavariable expression
.label = not a valid metavariable expression
.note = valid metavariable expressions are {$valid_expr_list}

expand_mve_unrecognized_var =
variable `{$key}` is not recognized in meta-variable expression

Expand Down
44 changes: 44 additions & 0 deletions compiler/rustc_expand/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,50 @@ pub(crate) use metavar_exprs::*;
mod metavar_exprs {
use super::*;

#[derive(Diagnostic, Default)]
#[diag(expand_mve_extra_tokens)]
pub(crate) struct MveExtraTokens {
#[primary_span]
#[suggestion(code = "", applicability = "machine-applicable")]
pub span: Span,
#[label]
pub ident_span: Span,
pub extra_count: usize,

// The rest is only used for specific diagnostics and can be default if neither
// `note` is `Some`.
#[note(expand_exact)]
pub exact_args_note: Option<()>,
#[note(expand_range)]
pub range_args_note: Option<()>,
pub min_or_exact_args: usize,
pub max_args: usize,
pub name: &'static str,
}

#[derive(Diagnostic)]
#[note]
#[diag(expand_mve_missing_paren)]
pub(crate) struct MveMissingParen {
#[primary_span]
#[label]
pub ident_span: Span,
#[label(expand_unexpected)]
pub unexpected_span: Option<Span>,
#[suggestion(code = "( /* ... */ )", applicability = "has-placeholders")]
pub insert_span: Option<Span>,
}

#[derive(Diagnostic)]
#[note]
#[diag(expand_mve_unrecognized_expr)]
pub(crate) struct MveUnrecognizedExpr {
#[primary_span]
#[label]
pub span: Span,
pub valid_expr_list: &'static str,
}

#[derive(Diagnostic)]
#[diag(expand_mve_unrecognized_var)]
pub(crate) struct MveUnrecognizedVar {
Expand Down
121 changes: 97 additions & 24 deletions compiler/rustc_expand/src/mbe/metavar_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,34 @@ use rustc_macros::{Decodable, Encodable};
use rustc_session::parse::ParseSess;
use rustc_span::{Ident, Span, Symbol};

use crate::errors;

pub(crate) const RAW_IDENT_ERR: &str = "`${concat(..)}` currently does not support raw identifiers";
pub(crate) const UNSUPPORTED_CONCAT_ELEM_ERR: &str = "expected identifier or string literal";

/// Argument specification for a metavariable expression
#[derive(Clone, Copy)]
enum ArgSpec {
/// Any number of args
Any,
/// Between n and m args (inclusive)
Between(usize, usize),
/// Exactly n args
Exact(usize),
}

/// Map of `(name, max_arg_count, variable_count)`.
const EXPR_NAME_ARG_MAP: &[(&str, ArgSpec)] = &[
("concat", ArgSpec::Any),
("count", ArgSpec::Between(1, 2)),
("ignore", ArgSpec::Exact(1)),
("index", ArgSpec::Between(0, 1)),
("len", ArgSpec::Between(0, 1)),
];

/// List of the above for diagnostics
const VALID_METAVAR_EXPR_NAMES: &str = "`count`, `ignore`, `index`, `len`, and `concat`";

/// A meta-variable expression, for expansions based on properties of meta-variables.
#[derive(Debug, PartialEq, Encodable, Decodable)]
pub(crate) enum MetaVarExpr {
Expand Down Expand Up @@ -40,11 +65,32 @@ impl MetaVarExpr {
) -> PResult<'psess, MetaVarExpr> {
let mut iter = input.iter();
let ident = parse_ident(&mut iter, psess, outer_span)?;
let Some(TokenTree::Delimited(.., Delimiter::Parenthesis, args)) = iter.next() else {
let msg = "meta-variable expression parameter must be wrapped in parentheses";
return Err(psess.dcx().struct_span_err(ident.span, msg));
let next = iter.next();
let Some(TokenTree::Delimited(.., Delimiter::Parenthesis, args)) = next else {
// No `()`; wrong or no delimiters. Point at a problematic span or a place to
// add parens if it makes sense.
let (unexpected_span, insert_span) = match next {
Some(TokenTree::Delimited(..)) => (None, None),
Some(tt) => (Some(tt.span()), None),
None => (None, Some(ident.span.shrink_to_hi())),
};
let err =
errors::MveMissingParen { ident_span: ident.span, unexpected_span, insert_span };
return Err(psess.dcx().create_err(err));
};
check_trailing_token(&mut iter, psess)?;

// Ensure there are no trailing tokens in the braces, e.g. `${foo() extra}`
if iter.peek().is_some() {
let span = iter_span(&iter).expect("checked is_some above");
let err = errors::MveExtraTokens {
span,
ident_span: ident.span,
extra_count: iter.count(),
..Default::default()
};
return Err(psess.dcx().create_err(err));
}

let mut iter = args.iter();
let rslt = match ident.as_str() {
"concat" => parse_concat(&mut iter, psess, outer_span, ident.span)?,
Expand All @@ -56,18 +102,14 @@ impl MetaVarExpr {
"index" => MetaVarExpr::Index(parse_depth(&mut iter, psess, ident.span)?),
"len" => MetaVarExpr::Len(parse_depth(&mut iter, psess, ident.span)?),
_ => {
let err_msg = "unrecognized meta-variable expression";
let mut err = psess.dcx().struct_span_err(ident.span, err_msg);
err.span_suggestion(
ident.span,
"supported expressions are count, ignore, index and len",
"",
Applicability::MachineApplicable,
);
return Err(err);
let err = errors::MveUnrecognizedExpr {
span: ident.span,
valid_expr_list: VALID_METAVAR_EXPR_NAMES,
};
return Err(psess.dcx().create_err(err));
}
};
check_trailing_token(&mut iter, psess)?;
check_trailing_tokens(&mut iter, psess, ident)?;
Ok(rslt)
}

Expand All @@ -87,20 +129,51 @@ impl MetaVarExpr {
}
}

// Checks if there are any remaining tokens. For example, `${ignore(ident ... a b c ...)}`
fn check_trailing_token<'psess>(
/// Checks if there are any remaining tokens (for example, `${ignore($valid, extra)}`) and create
/// a diag with the correct arg count if so.
fn check_trailing_tokens<'psess>(
iter: &mut TokenStreamIter<'_>,
psess: &'psess ParseSess,
ident: Ident,
) -> PResult<'psess, ()> {
if let Some(tt) = iter.next() {
let mut diag = psess
.dcx()
.struct_span_err(tt.span(), format!("unexpected token: {}", pprust::tt_to_string(tt)));
diag.span_note(tt.span(), "meta-variable expression must not have trailing tokens");
Err(diag)
} else {
Ok(())
if iter.peek().is_none() {
// All tokens consumed, as expected
return Ok(());
}

let (name, spec) = EXPR_NAME_ARG_MAP
.iter()
.find(|(name, _)| *name == ident.as_str())
.expect("called with an invalid name");

let (min_or_exact_args, max_args) = match *spec {
// For expressions like `concat`, all tokens should be consumed already
ArgSpec::Any => panic!("{name} takes unlimited tokens but didn't eat them all"),
ArgSpec::Between(min, max) => (min, Some(max)),
ArgSpec::Exact(n) => (n, None),
};

let err = errors::MveExtraTokens {
span: iter_span(iter).expect("checked is_none above"),
ident_span: ident.span,
extra_count: iter.count(),

exact_args_note: if max_args.is_some() { None } else { Some(()) },
range_args_note: if max_args.is_some() { Some(()) } else { None },
min_or_exact_args,
max_args: max_args.unwrap_or_default(),
name,
};
Err(psess.dcx().create_err(err))
}

/// Returns a span encompassing all tokens in the iterator if there is at least one item.
fn iter_span(iter: &TokenStreamIter<'_>) -> Option<Span> {
let mut iter = iter.clone(); // cloning is cheap
let first_sp = iter.next()?.span();
let last_sp = iter.last().map(TokenTree::span).unwrap_or(first_sp);
let span = first_sp.with_hi(last_sp.hi());
Some(span)
}

/// Indicates what is placed in a `concat` parameter. For example, literals
Expand Down
33 changes: 20 additions & 13 deletions tests/ui/macros/metavar-expressions/syntax-errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ macro_rules! metavar_with_literal_suffix {

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

#[rustfmt::skip]
Expand All @@ -45,9 +45,14 @@ macro_rules! open_brackets_with_lit {
//~^ ERROR expected identifier
}

macro_rules! mvs_missing_paren {
( $( $i:ident ),* ) => { ${ count $i ($i) } };
//~^ ERROR expected `(`
}

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

macro_rules! invalid_metavar {
Expand All @@ -64,28 +69,30 @@ macro_rules! open_brackets_with_group {
macro_rules! extra_garbage_after_metavar {
( $( $i:ident ),* ) => {
${count() a b c}
//~^ ERROR unexpected token: a
//~^ ERROR unexpected trailing tokens
${count($i a b c)}
//~^ ERROR unexpected token: a
//~^ ERROR unexpected trailing tokens
${count($i, 1 a b c)}
//~^ ERROR unexpected token: a
//~^ ERROR unexpected trailing tokens
${count($i) a b c}
//~^ ERROR unexpected token: a
//~^ ERROR unexpected trailing tokens

${ignore($i) a b c}
//~^ ERROR unexpected token: a
//~^ ERROR unexpected trailing tokens
${ignore($i a b c)}
//~^ ERROR unexpected token: a
//~^ ERROR unexpected trailing tokens

${index() a b c}
//~^ ERROR unexpected token: a
//~^ ERROR unexpected trailing tokens
${index(1 a b c)}
//~^ ERROR unexpected token: a
//~^ ERROR unexpected trailing tokens

${index() a b c}
//~^ ERROR unexpected token: a
//~^ ERROR unexpected trailing tokens
${index(1 a b c)}
//~^ ERROR unexpected token: a
//~^ ERROR unexpected trailing tokens
${index(1, a b c)}
//~^ ERROR unexpected trailing tokens
};
}

Expand All @@ -111,7 +118,7 @@ macro_rules! unknown_ignore_ident {

macro_rules! unknown_metavar {
( $( $i:ident ),* ) => { ${ aaaaaaaaaaaaaa(i) } };
//~^ ERROR unrecognized meta-variable expression
//~^ ERROR unrecognized metavariable expression
}

fn main() {}
Loading
Loading