Skip to content

[1/2] Implement macro meta-variable expressions #94368

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

Merged
merged 1 commit into from
Mar 11, 2022
Merged
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
2 changes: 1 addition & 1 deletion compiler/rustc_ast/src/util/literal.rs
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ pub enum LitError {

impl LitKind {
/// Converts literal token into a semantic literal.
fn from_lit_token(lit: token::Lit) -> Result<LitKind, LitError> {
pub fn from_lit_token(lit: token::Lit) -> Result<LitKind, LitError> {
let token::Lit { kind, symbol, suffix } = lit;
if suffix.is_some() && !kind.may_have_suffix() {
return Err(LitError::InvalidSuffix);
1 change: 1 addition & 0 deletions compiler/rustc_expand/src/lib.rs
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
#![feature(crate_visibility_modifier)]
#![feature(decl_macro)]
#![feature(if_let_guard)]
#![feature(let_chains)]
#![feature(let_else)]
#![feature(proc_macro_diagnostic)]
#![feature(proc_macro_internals)]
16 changes: 10 additions & 6 deletions compiler/rustc_expand/src/mbe.rs
Original file line number Diff line number Diff line change
@@ -6,17 +6,17 @@
crate mod macro_check;
crate mod macro_parser;
crate mod macro_rules;
crate mod metavar_expr;
crate mod quoted;
crate mod transcribe;

use metavar_expr::MetaVarExpr;
use rustc_ast::token::{self, NonterminalKind, Token, TokenKind};
use rustc_ast::tokenstream::DelimSpan;

use rustc_data_structures::sync::Lrc;
use rustc_span::symbol::Ident;
use rustc_span::Span;

use rustc_data_structures::sync::Lrc;

/// Contains the sub-token-trees of a "delimited" token tree, such as the contents of `(`. Note
/// that the delimiter itself might be `NoDelim`.
#[derive(Clone, PartialEq, Encodable, Decodable, Debug)]
@@ -73,8 +73,8 @@ enum KleeneOp {
ZeroOrOne,
}

/// Similar to `tokenstream::TokenTree`, except that `$i`, `$i:ident`, and `$(...)`
/// are "first-class" token trees. Useful for parsing macros.
/// Similar to `tokenstream::TokenTree`, except that `$i`, `$i:ident`, `$(...)`,
/// and `${...}` are "first-class" token trees. Useful for parsing macros.
#[derive(Debug, Clone, PartialEq, Encodable, Decodable)]
enum TokenTree {
Token(Token),
@@ -85,6 +85,8 @@ enum TokenTree {
MetaVar(Span, Ident),
/// e.g., `$var:expr`. This is only used in the left hand side of MBE macros.
MetaVarDecl(Span, Ident /* name to bind */, Option<NonterminalKind>),
/// A meta-variable expression inside `${...}`
MetaVarExpr(DelimSpan, MetaVarExpr),
}

impl TokenTree {
@@ -139,7 +141,9 @@ impl TokenTree {
TokenTree::Token(Token { span, .. })
| TokenTree::MetaVar(span, _)
| TokenTree::MetaVarDecl(span, _, _) => span,
TokenTree::Delimited(span, _) | TokenTree::Sequence(span, _) => span.entire(),
TokenTree::Delimited(span, _)
| TokenTree::MetaVarExpr(span, _)
| TokenTree::Sequence(span, _) => span.entire(),
}
}

4 changes: 4 additions & 0 deletions compiler/rustc_expand/src/mbe/macro_check.rs
Original file line number Diff line number Diff line change
@@ -278,6 +278,8 @@ fn check_binders(
binders.insert(name, BinderInfo { span, ops: ops.into() });
}
}
// `MetaVarExpr` can not appear in the LHS of a macro arm
TokenTree::MetaVarExpr(..) => {}
TokenTree::Delimited(_, ref del) => {
for tt in &del.tts {
check_binders(sess, node_id, tt, macros, binders, ops, valid);
@@ -335,6 +337,8 @@ fn check_occurrences(
let name = MacroRulesNormalizedIdent::new(name);
check_ops_is_prefix(sess, node_id, macros, binders, ops, span, name);
}
// FIXME(c410-f3r) Check token (https://github.com/rust-lang/rust/issues/93902)
TokenTree::MetaVarExpr(..) => {}
TokenTree::Delimited(_, ref del) => {
check_nested_occurrences(sess, node_id, &del.tts, macros, binders, ops, valid);
}
13 changes: 9 additions & 4 deletions compiler/rustc_expand/src/mbe/macro_parser.rs
Original file line number Diff line number Diff line change
@@ -200,7 +200,7 @@ struct MatcherPos<'root, 'tt> {

// This type is used a lot. Make sure it doesn't unintentionally get bigger.
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
rustc_data_structures::static_assert_size!(MatcherPos<'_, '_>, 192);
rustc_data_structures::static_assert_size!(MatcherPos<'_, '_>, 240);

impl<'root, 'tt> MatcherPos<'root, 'tt> {
/// Generates the top-level matcher position in which the "dot" is before the first token of
@@ -321,10 +321,13 @@ pub(super) fn count_names(ms: &[TokenTree]) -> usize {
ms.iter().fold(0, |count, elt| {
count
+ match *elt {
TokenTree::Sequence(_, ref seq) => seq.num_captures,
TokenTree::Delimited(_, ref delim) => count_names(&delim.tts),
TokenTree::MetaVar(..) => 0,
TokenTree::MetaVarDecl(..) => 1,
// FIXME(c410-f3r) MetaVarExpr should be handled instead of being ignored
// https://github.com/rust-lang/rust/issues/9390
TokenTree::MetaVarExpr(..) => 0,
TokenTree::Sequence(_, ref seq) => seq.num_captures,
TokenTree::Token(..) => 0,
}
})
@@ -436,7 +439,9 @@ fn nameize<I: Iterator<Item = NamedMatch>>(
}
Occupied(..) => return Err((sp, format!("duplicated bind name: {}", bind_name))),
},
TokenTree::MetaVar(..) | TokenTree::Token(..) => (),
// FIXME(c410-f3r) MetaVar and MetaVarExpr should be handled instead of being ignored
// https://github.com/rust-lang/rust/issues/9390
TokenTree::MetaVar(..) | TokenTree::MetaVarExpr(..) | TokenTree::Token(..) => {}
}

Ok(())
@@ -650,7 +655,7 @@ fn inner_parse_loop<'root, 'tt>(
// rules. NOTE that this is not necessarily an error unless _all_ items in
// `cur_items` end up doing this. There may still be some other matchers that do
// end up working out.
TokenTree::Token(..) | TokenTree::MetaVar(..) => {}
TokenTree::Token(..) | TokenTree::MetaVar(..) | TokenTree::MetaVarExpr(..) => {}
}
}
}
20 changes: 16 additions & 4 deletions compiler/rustc_expand/src/mbe/macro_rules.rs
Original file line number Diff line number Diff line change
@@ -580,7 +580,10 @@ fn check_lhs_no_empty_seq(sess: &ParseSess, tts: &[mbe::TokenTree]) -> bool {
use mbe::TokenTree;
for tt in tts {
match *tt {
TokenTree::Token(..) | TokenTree::MetaVar(..) | TokenTree::MetaVarDecl(..) => (),
TokenTree::Token(..)
| TokenTree::MetaVar(..)
| TokenTree::MetaVarDecl(..)
| TokenTree::MetaVarExpr(..) => (),
TokenTree::Delimited(_, ref del) => {
if !check_lhs_no_empty_seq(sess, &del.tts) {
return false;
@@ -669,7 +672,10 @@ impl FirstSets {
let mut first = TokenSet::empty();
for tt in tts.iter().rev() {
match *tt {
TokenTree::Token(..) | TokenTree::MetaVar(..) | TokenTree::MetaVarDecl(..) => {
TokenTree::Token(..)
| TokenTree::MetaVar(..)
| TokenTree::MetaVarDecl(..)
| TokenTree::MetaVarExpr(..) => {
first.replace_with(tt.clone());
}
TokenTree::Delimited(span, ref delimited) => {
@@ -731,7 +737,10 @@ impl FirstSets {
for tt in tts.iter() {
assert!(first.maybe_empty);
match *tt {
TokenTree::Token(..) | TokenTree::MetaVar(..) | TokenTree::MetaVarDecl(..) => {
TokenTree::Token(..)
| TokenTree::MetaVar(..)
| TokenTree::MetaVarDecl(..)
| TokenTree::MetaVarExpr(..) => {
first.add_one(tt.clone());
return first;
}
@@ -907,7 +916,10 @@ fn check_matcher_core(
// First, update `last` so that it corresponds to the set
// of NT tokens that might end the sequence `... token`.
match *token {
TokenTree::Token(..) | TokenTree::MetaVar(..) | TokenTree::MetaVarDecl(..) => {
TokenTree::Token(..)
| TokenTree::MetaVar(..)
| TokenTree::MetaVarDecl(..)
| TokenTree::MetaVarExpr(..) => {
if token_can_be_followed_by_any(token) {
// don't need to track tokens that work with any,
last.replace_with_irrelevant();
157 changes: 157 additions & 0 deletions compiler/rustc_expand/src/mbe/metavar_expr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
use rustc_ast::token;
use rustc_ast::tokenstream::{Cursor, TokenStream, TokenTree};
use rustc_ast::{LitIntType, LitKind};
use rustc_ast_pretty::pprust;
use rustc_errors::{Applicability, PResult};
use rustc_session::parse::ParseSess;
use rustc_span::symbol::Ident;
use rustc_span::Span;

/// A meta-variable expression, for expansions based on properties of meta-variables.
#[derive(Debug, Clone, PartialEq, Encodable, Decodable)]
crate enum MetaVarExpr {
/// The number of repetitions of an identifier, optionally limited to a number
/// of outer-most repetition depths. If the depth limit is `None` then the depth is unlimited.
Count(Ident, Option<usize>),

/// Ignore a meta-variable for repetition without expansion.
Ignore(Ident),

/// The index of the repetition at a particular depth, where 0 is the inner-most
/// repetition. The `usize` is the depth.
Index(usize),

/// The length of the repetition at a particular depth, where 0 is the inner-most
/// repetition. The `usize` is the depth.
Length(usize),
}

impl MetaVarExpr {
/// Attempt to parse a meta-variable expression from a token stream.
crate fn parse<'sess>(
input: &TokenStream,
outer_span: Span,
sess: &'sess ParseSess,
) -> PResult<'sess, MetaVarExpr> {
let mut tts = input.trees();
let ident = parse_ident(&mut tts, sess, outer_span)?;
let Some(TokenTree::Delimited(_, token::Paren, args)) = tts.next() else {
let msg = "meta-variable expression parameter must be wrapped in parentheses";
return Err(sess.span_diagnostic.struct_span_err(ident.span, msg));
};
check_trailing_token(&mut tts, sess)?;
let mut iter = args.trees();
let rslt = match &*ident.as_str() {
"count" => parse_count(&mut iter, sess, ident.span)?,
"ignore" => MetaVarExpr::Ignore(parse_ident(&mut iter, sess, ident.span)?),
"index" => MetaVarExpr::Index(parse_depth(&mut iter, sess, ident.span)?),
"length" => MetaVarExpr::Length(parse_depth(&mut iter, sess, ident.span)?),
_ => {
let err_msg = "unrecognized meta-variable expression";
let mut err = sess.span_diagnostic.struct_span_err(ident.span, err_msg);
err.span_suggestion(
ident.span,
"supported expressions are count, ignore, index and length",
String::new(),
Applicability::MachineApplicable,
);
return Err(err);
}
};
check_trailing_token(&mut iter, sess)?;
Ok(rslt)
}

crate fn ident(&self) -> Option<&Ident> {
match self {
MetaVarExpr::Count(ident, _) | MetaVarExpr::Ignore(ident) => Some(&ident),
MetaVarExpr::Index(..) | MetaVarExpr::Length(..) => None,
}
}
}

// Checks if there are any remaining tokens. For example, `${ignore(ident ... a b c ...)}`
fn check_trailing_token<'sess>(iter: &mut Cursor, sess: &'sess ParseSess) -> PResult<'sess, ()> {
if let Some(tt) = iter.next() {
let mut diag = sess.span_diagnostic.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(())
}
}

/// Parse a meta-variable `count` expression: `count(ident[, depth])`
fn parse_count<'sess>(
iter: &mut Cursor,
sess: &'sess ParseSess,
span: Span,
) -> PResult<'sess, MetaVarExpr> {
let ident = parse_ident(iter, sess, span)?;
let depth = if try_eat_comma(iter) { Some(parse_depth(iter, sess, span)?) } else { None };
Ok(MetaVarExpr::Count(ident, depth))
}

/// Parses the depth used by index(depth) and length(depth).
fn parse_depth<'sess>(
iter: &mut Cursor,
sess: &'sess ParseSess,
span: Span,
) -> PResult<'sess, usize> {
let Some(tt) = iter.next() else { return Ok(0) };
let TokenTree::Token(token::Token {
kind: token::TokenKind::Literal(lit), ..
}) = tt else {
return Err(sess.span_diagnostic.struct_span_err(
span,
"meta-variable expression depth must be a literal"
));
};
if let Ok(lit_kind) = LitKind::from_lit_token(lit)
&& let LitKind::Int(n_u128, LitIntType::Unsuffixed) = lit_kind
&& let Ok(n_usize) = usize::try_from(n_u128)
{
Ok(n_usize)
}
else {
let msg = "only unsuffixes integer literals are supported in meta-variable expressions";
Err(sess.span_diagnostic.struct_span_err(span, msg))
}
}

/// Parses an generic ident
fn parse_ident<'sess>(
iter: &mut Cursor,
sess: &'sess ParseSess,
span: Span,
) -> PResult<'sess, Ident> {
let err_fn = |msg| sess.span_diagnostic.struct_span_err(span, msg);
if let Some(tt) = iter.next() && let TokenTree::Token(token) = tt {
if let Some((elem, false)) = token.ident() {
return Ok(elem);
}
let token_str = pprust::token_to_string(&token);
let mut err = err_fn(&format!("expected identifier, found `{}`", &token_str));
err.span_suggestion(
token.span,
&format!("try removing `{}`", &token_str),
String::new(),
Applicability::MaybeIncorrect,
);
return Err(err);
}
Err(err_fn("expected identifier"))
}

/// Tries to move the iterator forward returning `true` if there is a comma. If not, then the
/// iterator is not modified and the result is `false`.
fn try_eat_comma(iter: &mut Cursor) -> bool {
if let Some(TokenTree::Token(token::Token { kind: token::Comma, .. })) = iter.look_ahead(0) {
let _ = iter.next();
return true;
}
false
}
123 changes: 94 additions & 29 deletions compiler/rustc_expand/src/mbe/quoted.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use crate::mbe::macro_parser;
use crate::mbe::{Delimited, KleeneOp, KleeneToken, SequenceRepetition, TokenTree};
use crate::mbe::{Delimited, KleeneOp, KleeneToken, MetaVarExpr, SequenceRepetition, TokenTree};

use rustc_ast::token::{self, Token};
use rustc_ast::tokenstream;
use rustc_ast::{NodeId, DUMMY_NODE_ID};
use rustc_ast_pretty::pprust;
use rustc_feature::Features;
use rustc_session::parse::ParseSess;
use rustc_span::symbol::{kw, Ident};
use rustc_session::parse::{feature_err, ParseSess};
use rustc_span::symbol::{kw, sym, Ident};

use rustc_span::edition::Edition;
use rustc_span::{Span, SyntaxContext};
@@ -25,22 +25,22 @@ const VALID_FRAGMENT_NAMES_MSG: &str = "valid fragment specifiers are \
/// # Parameters
///
/// - `input`: a token stream to read from, the contents of which we are parsing.
/// - `expect_matchers`: `parse` can be used to parse either the "patterns" or the "body" of a
/// macro. Both take roughly the same form _except_ that in a pattern, metavars are declared with
/// their "matcher" type. For example `$var:expr` or `$id:ident`. In this example, `expr` and
/// `ident` are "matchers". They are not present in the body of a macro rule -- just in the
/// pattern, so we pass a parameter to indicate whether to expect them or not.
/// - `parsing_patterns`: `parse` can be used to parse either the "patterns" or the "body" of a
/// macro. Both take roughly the same form _except_ that:
/// - In a pattern, metavars are declared with their "matcher" type. For example `$var:expr` or
/// `$id:ident`. In this example, `expr` and `ident` are "matchers". They are not present in the
/// body of a macro rule -- just in the pattern.
/// - Metavariable expressions are only valid in the "body", not the "pattern".
/// - `sess`: the parsing session. Any errors will be emitted to this session.
/// - `node_id`: the NodeId of the macro we are parsing.
/// - `features`: language features so we can do feature gating.
/// - `edition`: the edition of the crate defining the macro
///
/// # Returns
///
/// A collection of `self::TokenTree`. There may also be some errors emitted to `sess`.
pub(super) fn parse(
input: tokenstream::TokenStream,
expect_matchers: bool,
parsing_patterns: bool,
sess: &ParseSess,
node_id: NodeId,
features: &Features,
@@ -55,9 +55,9 @@ pub(super) fn parse(
while let Some(tree) = trees.next() {
// Given the parsed tree, if there is a metavar and we are expecting matchers, actually
// parse out the matcher (i.e., in `$id:ident` this would parse the `:` and `ident`).
let tree = parse_tree(tree, &mut trees, expect_matchers, sess, node_id, features, edition);
let tree = parse_tree(tree, &mut trees, parsing_patterns, sess, node_id, features, edition);
match tree {
TokenTree::MetaVar(start_sp, ident) if expect_matchers => {
TokenTree::MetaVar(start_sp, ident) if parsing_patterns => {
let span = match trees.next() {
Some(tokenstream::TokenTree::Token(Token { kind: token::Colon, span })) => {
match trees.next() {
@@ -118,6 +118,14 @@ pub(super) fn parse(
result
}

/// Asks for the `macro_metavar_expr` feature if it is not already declared
fn maybe_emit_macro_metavar_expr_feature(features: &Features, sess: &ParseSess, span: Span) {
if !features.macro_metavar_expr {
let msg = "meta-variable expressions are unstable";
feature_err(&sess, sym::macro_metavar_expr, span, msg).emit();
}
}

/// Takes a `tokenstream::TokenTree` and returns a `self::TokenTree`. Specifically, this takes a
/// generic `TokenTree`, such as is used in the rest of the compiler, and returns a `TokenTree`
/// for use in parsing a macro.
@@ -129,14 +137,13 @@ pub(super) fn parse(
/// - `tree`: the tree we wish to convert.
/// - `outer_trees`: an iterator over trees. We may need to read more tokens from it in order to finish
/// converting `tree`
/// - `expect_matchers`: same as for `parse` (see above).
/// - `parsing_patterns`: same as [parse].
/// - `sess`: the parsing session. Any errors will be emitted to this session.
/// - `features`: language features so we can do feature gating.
/// - `edition` - the edition of the crate defining the macro
fn parse_tree(
tree: tokenstream::TokenTree,
outer_trees: &mut impl Iterator<Item = tokenstream::TokenTree>,
expect_matchers: bool,
parsing_patterns: bool,
sess: &ParseSess,
node_id: NodeId,
features: &Features,
@@ -158,24 +165,57 @@ fn parse_tree(
}

match next {
// `tree` is followed by a delimited set of token trees. This indicates the beginning
// of a repetition sequence in the macro (e.g. `$(pat)*`).
Some(tokenstream::TokenTree::Delimited(span, delim, tts)) => {
// Must have `(` not `{` or `[`
if delim != token::Paren {
let tok = pprust::token_kind_to_string(&token::OpenDelim(delim));
let msg = format!("expected `(`, found `{}`", tok);
sess.span_diagnostic.span_err(span.entire(), &msg);
// `tree` is followed by a delimited set of token trees.
Some(tokenstream::TokenTree::Delimited(delim_span, delim, tts)) => {
if parsing_patterns {
if delim != token::Paren {
span_dollar_dollar_or_metavar_in_the_lhs_err(
sess,
&Token { kind: token::OpenDelim(delim), span: delim_span.entire() },
);
}
} else {
match delim {
token::Brace => {
// The delimiter is `{`. This indicates the beginning
// of a meta-variable expression (e.g. `${count(ident)}`).
// Try to parse the meta-variable expression.
match MetaVarExpr::parse(&tts, delim_span.entire(), sess) {
Err(mut err) => {
err.emit();
// Returns early the same read `$` to avoid spanning
// unrelated diagnostics that could be performed afterwards
return TokenTree::token(token::Dollar, span);
}
Ok(elem) => {
maybe_emit_macro_metavar_expr_feature(
features,
sess,
delim_span.entire(),
);
return TokenTree::MetaVarExpr(delim_span, elem);
}
}
}
token::Paren => {}
_ => {
let tok = pprust::token_kind_to_string(&token::OpenDelim(delim));
let msg = format!("expected `(` or `{{`, found `{}`", tok);
sess.span_diagnostic.span_err(delim_span.entire(), &msg);
}
}
}
// Parse the contents of the sequence itself
let sequence = parse(tts, expect_matchers, sess, node_id, features, edition);
// If we didn't find a metavar expression above, then we must have a
// repetition sequence in the macro (e.g. `$(pat)*`). Parse the
// contents of the sequence itself
let sequence = parse(tts, parsing_patterns, sess, node_id, features, edition);
// Get the Kleene operator and optional separator
let (separator, kleene) =
parse_sep_and_kleene_op(&mut trees, span.entire(), sess);
parse_sep_and_kleene_op(&mut trees, delim_span.entire(), sess);
// Count the number of captured "names" (i.e., named metavars)
let name_captures = macro_parser::count_names(&sequence);
TokenTree::Sequence(
span,
delim_span,
Lrc::new(SequenceRepetition {
tts: sequence,
separator,
@@ -197,7 +237,20 @@ fn parse_tree(
}
}

// `tree` is followed by a random token. This is an error.
// `tree` is followed by another `$`. This is an escaped `$`.
Some(tokenstream::TokenTree::Token(Token { kind: token::Dollar, span })) => {
if parsing_patterns {
span_dollar_dollar_or_metavar_in_the_lhs_err(
sess,
&Token { kind: token::Dollar, span },
);
} else {
maybe_emit_macro_metavar_expr_feature(features, sess, span);
}
TokenTree::token(token::Dollar, span)
}

// `tree` is followed by some other token. This is an error.
Some(tokenstream::TokenTree::Token(token)) => {
let msg = format!(
"expected identifier, found `{}`",
@@ -221,7 +274,7 @@ fn parse_tree(
span,
Lrc::new(Delimited {
delim,
tts: parse(tts, expect_matchers, sess, node_id, features, edition),
tts: parse(tts, parsing_patterns, sess, node_id, features, edition),
}),
),
}
@@ -309,3 +362,15 @@ fn parse_sep_and_kleene_op(
// Return a dummy
(None, KleeneToken::new(KleeneOp::ZeroOrMore, span))
}

// `$$` or a meta-variable is the lhs of a macro but shouldn't.
//
// For example, `macro_rules! foo { ( ${length()} ) => {} }`
fn span_dollar_dollar_or_metavar_in_the_lhs_err<'sess>(sess: &'sess ParseSess, token: &Token) {
sess.span_diagnostic
.span_err(token.span, &format!("unexpected token: {}", pprust::token_to_string(token)));
sess.span_diagnostic.span_note_without_error(
token.span,
"`$$` and meta-variable expressions are not allowed inside macro parameter definitions",
);
}
27 changes: 27 additions & 0 deletions compiler/rustc_expand/src/mbe/transcribe.rs
Original file line number Diff line number Diff line change
@@ -255,6 +255,11 @@ pub(super) fn transcribe<'a>(
}
}

// Replace meta-variable expressions with the result of their expansion.
mbe::TokenTree::MetaVarExpr(sp, expr) => {
transcribe_metavar_expr(cx, expr, interp, &repeats, &mut result, &sp)?;
}

// If we are entering a new delimiter, we push its contents to the `stack` to be
// processed, and we push all of the currently produced results to the `result_stack`.
// We will produce all of the results of the inside of the `Delimited` and then we will
@@ -391,6 +396,28 @@ fn lockstep_iter_size(
_ => LockstepIterSize::Unconstrained,
}
}
TokenTree::MetaVarExpr(_, ref expr) => {
let default_rslt = LockstepIterSize::Unconstrained;
let Some(ident) = expr.ident() else { return default_rslt; };
let name = MacroRulesNormalizedIdent::new(ident.clone());
match lookup_cur_matched(name, interpolations, repeats) {
Some(MatchedSeq(ref ads)) => {
default_rslt.with(LockstepIterSize::Constraint(ads.len(), name))
}
_ => default_rslt,
}
}
TokenTree::Token(..) => LockstepIterSize::Unconstrained,
}
}

fn transcribe_metavar_expr<'a>(
_cx: &ExtCtxt<'a>,
_expr: mbe::MetaVarExpr,
_interp: &FxHashMap<MacroRulesNormalizedIdent, NamedMatch>,
_repeats: &[(usize, usize)],
_result: &mut Vec<TreeAndSpacing>,
_sp: &DelimSpan,
) -> PResult<'a, ()> {
Ok(())
}
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/active.rs
Original file line number Diff line number Diff line change
@@ -426,6 +426,8 @@ declare_features! (
(active, link_cfg, "1.14.0", Some(37406), None),
/// Allows using `reason` in lint attributes and the `#[expect(lint)]` lint check.
(active, lint_reasons, "1.31.0", Some(54503), None),
/// Give access to additional metadata about declarative macro meta-variables.
(active, macro_metavar_expr, "1.61.0", Some(83527), None),
/// Allows `#[marker]` on certain traits allowing overlapping implementations.
(active, marker_trait_attr, "1.30.0", Some(29864), None),
/// A minimal, sound subset of specialization intended to be used by the
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
@@ -845,6 +845,7 @@ symbols! {
macro_export,
macro_lifetime_matcher,
macro_literal_matcher,
macro_metavar_expr,
macro_reexport,
macro_use,
macro_vis_matcher,
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// run-pass

#![feature(macro_metavar_expr)]

macro_rules! nested {
( $a:ident ) => {
macro_rules! $a {
( $$( $b:ident ),* ) => {
$$(
macro_rules! $b {
( $$$$( $c:ident ),* ) => {
$$$$(
fn $c() -> &'static str { stringify!($c) }
),*
};
}
)*
};
}
};
}

fn main() {
nested!(a);
a!(b);
b!(c);
assert_eq!(c(), "c");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// run-pass

#![feature(macro_metavar_expr)]

macro_rules! ignore {
( $( $i:ident ),* ) => {{
let array: [i32; 0] = [$( ${ignore(i)} )*];
array
}};
}

fn main() {
assert_eq!(ignore!(a, b, c), []);
}
9 changes: 9 additions & 0 deletions src/test/ui/macros/rfc-3086-metavar-expr/required-feature.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
macro_rules! count {
( $( $e:stmt ),* ) => {
${ count(e) }
//~^ ERROR meta-variable expressions are unstable
};
}

fn main() {
}
12 changes: 12 additions & 0 deletions src/test/ui/macros/rfc-3086-metavar-expr/required-feature.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
error[E0658]: meta-variable expressions are unstable
--> $DIR/required-feature.rs:3:10
|
LL | ${ count(e) }
| ^^^^^^^^^^^^
|
= note: see issue #83527 <https://github.com/rust-lang/rust/issues/83527> for more information
= help: add `#![feature(macro_metavar_expr)]` to the crate attributes to enable

error: aborting due to previous error

For more information about this error, try `rustc --explain E0658`.
148 changes: 148 additions & 0 deletions src/test/ui/macros/rfc-3086-metavar-expr/syntax-errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#![feature(macro_metavar_expr)]

// `curly` = Right hand side curly brackets
// `no_rhs_dollar` = No dollar sign at the right hand side meta variable "function"
// `round` = Left hand side round brackets

macro_rules! curly__no_rhs_dollar__round {
( $( $i:ident ),* ) => { ${ count(i) } };
}

macro_rules! curly__no_rhs_dollar__no_round {
( $i:ident ) => { ${ count(i) } };
}

macro_rules! curly__rhs_dollar__round {
( $( $i:ident ),* ) => { ${ count($i) } };
//~^ ERROR expected identifier, found `$`
//~| ERROR expected expression, found `$`
}

macro_rules! curly__rhs_dollar__no_round {
( $i:ident ) => { ${ count($i) } };
//~^ ERROR expected identifier, found `$`
//~| ERROR expected expression, found `$`
}

macro_rules! no_curly__no_rhs_dollar__round {
( $( $i:ident ),* ) => { count(i) };
//~^ ERROR cannot find function `count` in this scope
//~| ERROR cannot find value `i` in this scope
}

macro_rules! no_curly__no_rhs_dollar__no_round {
( $i:ident ) => { count(i) };
//~^ ERROR cannot find function `count` in this scope
//~| ERROR cannot find value `i` in this scope
}

macro_rules! no_curly__rhs_dollar__round {
( $( $i:ident ),* ) => { count($i) };
//~^ ERROR variable 'i' is still repeating at this depth
}

macro_rules! no_curly__rhs_dollar__no_round {
( $i:ident ) => { count($i) };
//~^ ERROR cannot find function `count` in this scope
}

// Other scenarios

macro_rules! dollar_dollar_in_the_lhs {
( $$ $a:ident ) => {
//~^ ERROR unexpected token: $
};
}

macro_rules! extra_garbage_after_metavar {
( $( $i:ident ),* ) => {
${count() a b c}
//~^ ERROR unexpected token: a
//~| ERROR expected expression, found `$`
${count(i a b c)}
//~^ ERROR unexpected token: a
${count(i, 1 a b c)}
//~^ ERROR unexpected token: a
${count(i) a b c}
//~^ ERROR unexpected token: a

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

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

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

const IDX: usize = 1;
macro_rules! metavar_depth_is_not_literal {
( $( $i:ident ),* ) => { ${ index(IDX) } };
//~^ ERROR meta-variable expression depth must be a literal
//~| ERROR expected expression, found `$`
}

macro_rules! metavar_in_the_lhs {
( ${ length() } ) => {
//~^ ERROR unexpected token: {
//~| ERROR expected one of: `*`, `+`, or `?`
};
}

macro_rules! metavar_token_without_ident {
( $( $i:ident ),* ) => { ${ ignore() } };
//~^ ERROR expected identifier
//~| ERROR expected expression, found `$`
}

macro_rules! metavar_with_literal_suffix {
( $( $i:ident ),* ) => { ${ index(1u32) } };
//~^ ERROR only unsuffixes integer literals are supported in meta-variable expressions
//~| ERROR expected expression, found `$`
}

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

macro_rules! open_brackets_without_tokens {
( $( $i:ident ),* ) => { ${ {} } };
//~^ ERROR expected expression, found `$`
//~| ERROR expected identifier
}

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

fn main() {
curly__no_rhs_dollar__round!(a, b, c);
curly__no_rhs_dollar__no_round!(a);
curly__rhs_dollar__round!(a, b, c);
curly__rhs_dollar__no_round!(a);
no_curly__no_rhs_dollar__round!(a, b, c);
no_curly__no_rhs_dollar__no_round!(a);
no_curly__rhs_dollar__round!(a, b, c);
no_curly__rhs_dollar__no_round!(a);
//~^ ERROR cannot find value `a` in this scope

extra_garbage_after_metavar!(a);
unknown_metavar!(a);
metavar_without_parens!(a);
metavar_token_without_ident!(a);
metavar_depth_is_not_literal!(a);
metavar_with_literal_suffix!(a);
open_brackets_without_tokens!(a)
}
367 changes: 367 additions & 0 deletions src/test/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,367 @@
error: expected identifier, found `$`
--> $DIR/syntax-errors.rs:16:33
|
LL | ( $( $i:ident ),* ) => { ${ count($i) } };
| ^^^^^ - help: try removing `$`

error: expected identifier, found `$`
--> $DIR/syntax-errors.rs:22:26
|
LL | ( $i:ident ) => { ${ count($i) } };
| ^^^^^ - help: try removing `$`

error: unexpected token: $
--> $DIR/syntax-errors.rs:52:8
|
LL | ( $$ $a:ident ) => {
| ^

note: `$$` and meta-variable expressions are not allowed inside macro parameter definitions
--> $DIR/syntax-errors.rs:52:8
|
LL | ( $$ $a:ident ) => {
| ^

error: unexpected token: a
--> $DIR/syntax-errors.rs:59:19
|
LL | ${count() a b c}
| ^
|
note: meta-variable expression must not have trailing tokens
--> $DIR/syntax-errors.rs:59:19
|
LL | ${count() a b c}
| ^

error: unexpected token: a
--> $DIR/syntax-errors.rs:62:19
|
LL | ${count(i a b c)}
| ^
|
note: meta-variable expression must not have trailing tokens
--> $DIR/syntax-errors.rs:62:19
|
LL | ${count(i a b c)}
| ^

error: unexpected token: a
--> $DIR/syntax-errors.rs:64:22
|
LL | ${count(i, 1 a b c)}
| ^
|
note: meta-variable expression must not have trailing tokens
--> $DIR/syntax-errors.rs:64:22
|
LL | ${count(i, 1 a b c)}
| ^

error: unexpected token: a
--> $DIR/syntax-errors.rs:66:20
|
LL | ${count(i) a b c}
| ^
|
note: meta-variable expression must not have trailing tokens
--> $DIR/syntax-errors.rs:66:20
|
LL | ${count(i) a b c}
| ^

error: unexpected token: a
--> $DIR/syntax-errors.rs:69:21
|
LL | ${ignore(i) a b c}
| ^
|
note: meta-variable expression must not have trailing tokens
--> $DIR/syntax-errors.rs:69:21
|
LL | ${ignore(i) a b c}
| ^

error: unexpected token: a
--> $DIR/syntax-errors.rs:71:20
|
LL | ${ignore(i a b c)}
| ^
|
note: meta-variable expression must not have trailing tokens
--> $DIR/syntax-errors.rs:71:20
|
LL | ${ignore(i a b c)}
| ^

error: unexpected token: a
--> $DIR/syntax-errors.rs:74:19
|
LL | ${index() a b c}
| ^
|
note: meta-variable expression must not have trailing tokens
--> $DIR/syntax-errors.rs:74:19
|
LL | ${index() a b c}
| ^

error: unexpected token: a
--> $DIR/syntax-errors.rs:76:19
|
LL | ${index(1 a b c)}
| ^
|
note: meta-variable expression must not have trailing tokens
--> $DIR/syntax-errors.rs:76:19
|
LL | ${index(1 a b c)}
| ^

error: unexpected token: a
--> $DIR/syntax-errors.rs:79:19
|
LL | ${index() a b c}
| ^
|
note: meta-variable expression must not have trailing tokens
--> $DIR/syntax-errors.rs:79:19
|
LL | ${index() a b c}
| ^

error: unexpected token: a
--> $DIR/syntax-errors.rs:81:19
|
LL | ${index(1 a b c)}
| ^
|
note: meta-variable expression must not have trailing tokens
--> $DIR/syntax-errors.rs:81:19
|
LL | ${index(1 a b c)}
| ^

error: meta-variable expression depth must be a literal
--> $DIR/syntax-errors.rs:88:33
|
LL | ( $( $i:ident ),* ) => { ${ index(IDX) } };
| ^^^^^

error: unexpected token: {
--> $DIR/syntax-errors.rs:94:8
|
LL | ( ${ length() } ) => {
| ^^^^^^^^^^^^

note: `$$` and meta-variable expressions are not allowed inside macro parameter definitions
--> $DIR/syntax-errors.rs:94:8
|
LL | ( ${ length() } ) => {
| ^^^^^^^^^^^^

error: expected one of: `*`, `+`, or `?`
--> $DIR/syntax-errors.rs:94:8
|
LL | ( ${ length() } ) => {
| ^^^^^^^^^^^^

error: expected identifier
--> $DIR/syntax-errors.rs:101:33
|
LL | ( $( $i:ident ),* ) => { ${ ignore() } };
| ^^^^^^

error: only unsuffixes integer literals are supported in meta-variable expressions
--> $DIR/syntax-errors.rs:107:33
|
LL | ( $( $i:ident ),* ) => { ${ index(1u32) } };
| ^^^^^

error: meta-variable expression parameter must be wrapped in parentheses
--> $DIR/syntax-errors.rs:113:33
|
LL | ( $( $i:ident ),* ) => { ${ count{i} } };
| ^^^^^

error: expected identifier
--> $DIR/syntax-errors.rs:119:31
|
LL | ( $( $i:ident ),* ) => { ${ {} } };
| ^^^^^^

error: unrecognized meta-variable expression
--> $DIR/syntax-errors.rs:125:33
|
LL | ( $( $i:ident ),* ) => { ${ aaaaaaaaaaaaaa(i) } };
| ^^^^^^^^^^^^^^ help: supported expressions are count, ignore, index and length

error: expected expression, found `$`
--> $DIR/syntax-errors.rs:16:30
|
LL | ( $( $i:ident ),* ) => { ${ count($i) } };
| ^ expected expression
...
LL | curly__rhs_dollar__round!(a, b, c);
| ---------------------------------- in this macro invocation
|
= note: this error originates in the macro `curly__rhs_dollar__round` (in Nightly builds, run with -Z macro-backtrace for more info)

error: expected expression, found `$`
--> $DIR/syntax-errors.rs:22:23
|
LL | ( $i:ident ) => { ${ count($i) } };
| ^ expected expression
...
LL | curly__rhs_dollar__no_round!(a);
| ------------------------------- in this macro invocation
|
= note: this error originates in the macro `curly__rhs_dollar__no_round` (in Nightly builds, run with -Z macro-backtrace for more info)

error: variable 'i' is still repeating at this depth
--> $DIR/syntax-errors.rs:40:36
|
LL | ( $( $i:ident ),* ) => { count($i) };
| ^^

error: expected expression, found `$`
--> $DIR/syntax-errors.rs:59:9
|
LL | ${count() a b c}
| ^ expected expression
...
LL | extra_garbage_after_metavar!(a);
| ------------------------------- in this macro invocation
|
= note: this error originates in the macro `extra_garbage_after_metavar` (in Nightly builds, run with -Z macro-backtrace for more info)

error: expected expression, found `$`
--> $DIR/syntax-errors.rs:125:30
|
LL | ( $( $i:ident ),* ) => { ${ aaaaaaaaaaaaaa(i) } };
| ^ expected expression
...
LL | unknown_metavar!(a);
| ------------------- in this macro invocation
|
= note: this error originates in the macro `unknown_metavar` (in Nightly builds, run with -Z macro-backtrace for more info)

error: expected expression, found `$`
--> $DIR/syntax-errors.rs:113:30
|
LL | ( $( $i:ident ),* ) => { ${ count{i} } };
| ^ expected expression
...
LL | metavar_without_parens!(a);
| -------------------------- in this macro invocation
|
= note: this error originates in the macro `metavar_without_parens` (in Nightly builds, run with -Z macro-backtrace for more info)

error: expected expression, found `$`
--> $DIR/syntax-errors.rs:101:30
|
LL | ( $( $i:ident ),* ) => { ${ ignore() } };
| ^ expected expression
...
LL | metavar_token_without_ident!(a);
| ------------------------------- in this macro invocation
|
= note: this error originates in the macro `metavar_token_without_ident` (in Nightly builds, run with -Z macro-backtrace for more info)

error: expected expression, found `$`
--> $DIR/syntax-errors.rs:88:30
|
LL | ( $( $i:ident ),* ) => { ${ index(IDX) } };
| ^ expected expression
...
LL | metavar_depth_is_not_literal!(a);
| -------------------------------- in this macro invocation
|
= note: this error originates in the macro `metavar_depth_is_not_literal` (in Nightly builds, run with -Z macro-backtrace for more info)

error: expected expression, found `$`
--> $DIR/syntax-errors.rs:107:30
|
LL | ( $( $i:ident ),* ) => { ${ index(1u32) } };
| ^ expected expression
...
LL | metavar_with_literal_suffix!(a);
| ------------------------------- in this macro invocation
|
= note: this error originates in the macro `metavar_with_literal_suffix` (in Nightly builds, run with -Z macro-backtrace for more info)

error: expected expression, found `$`
--> $DIR/syntax-errors.rs:119:30
|
LL | ( $( $i:ident ),* ) => { ${ {} } };
| ^ expected expression
...
LL | open_brackets_without_tokens!(a)
| -------------------------------- in this macro invocation
|
= note: this error originates in the macro `open_brackets_without_tokens` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0425]: cannot find function `count` in this scope
--> $DIR/syntax-errors.rs:28:30
|
LL | ( $( $i:ident ),* ) => { count(i) };
| ^^^^^ not found in this scope
...
LL | no_curly__no_rhs_dollar__round!(a, b, c);
| ---------------------------------------- in this macro invocation
|
= note: this error originates in the macro `no_curly__no_rhs_dollar__round` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0425]: cannot find value `i` in this scope
--> $DIR/syntax-errors.rs:28:36
|
LL | ( $( $i:ident ),* ) => { count(i) };
| ^ not found in this scope
...
LL | no_curly__no_rhs_dollar__round!(a, b, c);
| ---------------------------------------- in this macro invocation
|
= note: this error originates in the macro `no_curly__no_rhs_dollar__round` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0425]: cannot find function `count` in this scope
--> $DIR/syntax-errors.rs:34:23
|
LL | ( $i:ident ) => { count(i) };
| ^^^^^ not found in this scope
...
LL | no_curly__no_rhs_dollar__no_round!(a);
| ------------------------------------- in this macro invocation
|
= note: this error originates in the macro `no_curly__no_rhs_dollar__no_round` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0425]: cannot find value `i` in this scope
--> $DIR/syntax-errors.rs:34:29
|
LL | ( $i:ident ) => { count(i) };
| ^ not found in this scope
...
LL | no_curly__no_rhs_dollar__no_round!(a);
| ------------------------------------- in this macro invocation
|
= note: this error originates in the macro `no_curly__no_rhs_dollar__no_round` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0425]: cannot find function `count` in this scope
--> $DIR/syntax-errors.rs:45:23
|
LL | ( $i:ident ) => { count($i) };
| ^^^^^ not found in this scope
...
LL | no_curly__rhs_dollar__no_round!(a);
| ---------------------------------- in this macro invocation
|
= note: this error originates in the macro `no_curly__rhs_dollar__no_round` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0425]: cannot find value `a` in this scope
--> $DIR/syntax-errors.rs:138:37
|
LL | no_curly__rhs_dollar__no_round!(a);
| ^ not found in this scope

error: aborting due to 37 previous errors

For more information about this error, try `rustc --explain E0425`.