Rollup merge of #94368 - c410-f3r:metaaaaaaaaaaaaaaaaaaaaaaaaaaa, r=petrochenkov

[1/2] Implement macro meta-variable expressions

See https://github.com/rust-lang/rust/pull/93545#issuecomment-1050963295

The logic behind `length`, `index` and `count` was removed but the parsing code is still present, i.e., everything is simply ignored like `ignored`.

r? ``@petrochenkov``
This commit is contained in:
Dylan DPC 2022-03-10 23:12:58 +01:00 committed by GitHub
commit 634a6b0d25
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 900 additions and 44 deletions

View file

@ -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);

View file

@ -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)]

View file

@ -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(),
}
}

View file

@ -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);
}

View file

@ -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(..) => {}
}
}
}

View file

@ -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();

View file

@ -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
}

View file

@ -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",
);
}

View file

@ -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(())
}

View file

@ -430,6 +430,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

View file

@ -846,6 +846,7 @@ symbols! {
macro_export,
macro_lifetime_matcher,
macro_literal_matcher,
macro_metavar_expr,
macro_reexport,
macro_use,
macro_vis_matcher,

View file

@ -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");
}

View file

@ -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), []);
}

View file

@ -0,0 +1,9 @@
macro_rules! count {
( $( $e:stmt ),* ) => {
${ count(e) }
//~^ ERROR meta-variable expressions are unstable
};
}
fn main() {
}

View file

@ -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`.

View file

@ -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)
}

View file

@ -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`.