about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_ast/src/util/literal.rs2
-rw-r--r--compiler/rustc_expand/src/lib.rs1
-rw-r--r--compiler/rustc_expand/src/mbe.rs16
-rw-r--r--compiler/rustc_expand/src/mbe/macro_check.rs4
-rw-r--r--compiler/rustc_expand/src/mbe/macro_parser.rs13
-rw-r--r--compiler/rustc_expand/src/mbe/macro_rules.rs20
-rw-r--r--compiler/rustc_expand/src/mbe/metavar_expr.rs157
-rw-r--r--compiler/rustc_expand/src/mbe/quoted.rs123
-rw-r--r--compiler/rustc_expand/src/mbe/transcribe.rs27
-rw-r--r--compiler/rustc_feature/src/active.rs2
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--src/test/ui/macros/rfc-3086-metavar-expr/dollar-dollar-has-correct-behavior.rs28
-rw-r--r--src/test/ui/macros/rfc-3086-metavar-expr/feature-gate-macro_metavar_expr.rs14
-rw-r--r--src/test/ui/macros/rfc-3086-metavar-expr/required-feature.rs9
-rw-r--r--src/test/ui/macros/rfc-3086-metavar-expr/required-feature.stderr12
-rw-r--r--src/test/ui/macros/rfc-3086-metavar-expr/syntax-errors.rs148
-rw-r--r--src/test/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr367
17 files changed, 900 insertions, 44 deletions
diff --git a/compiler/rustc_ast/src/util/literal.rs b/compiler/rustc_ast/src/util/literal.rs
index 231dd72af2c..9c18f55c03b 100644
--- a/compiler/rustc_ast/src/util/literal.rs
+++ b/compiler/rustc_ast/src/util/literal.rs
@@ -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);
diff --git a/compiler/rustc_expand/src/lib.rs b/compiler/rustc_expand/src/lib.rs
index b024524aa29..8a9efe01368 100644
--- a/compiler/rustc_expand/src/lib.rs
+++ b/compiler/rustc_expand/src/lib.rs
@@ -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)]
diff --git a/compiler/rustc_expand/src/mbe.rs b/compiler/rustc_expand/src/mbe.rs
index 5244ac36bba..3d4c77aba73 100644
--- a/compiler/rustc_expand/src/mbe.rs
+++ b/compiler/rustc_expand/src/mbe.rs
@@ -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(),
         }
     }
 
diff --git a/compiler/rustc_expand/src/mbe/macro_check.rs b/compiler/rustc_expand/src/mbe/macro_check.rs
index 3497e5ad543..88e11693220 100644
--- a/compiler/rustc_expand/src/mbe/macro_check.rs
+++ b/compiler/rustc_expand/src/mbe/macro_check.rs
@@ -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);
         }
diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs
index bb36dfd793d..04137086088 100644
--- a/compiler/rustc_expand/src/mbe/macro_parser.rs
+++ b/compiler/rustc_expand/src/mbe/macro_parser.rs
@@ -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(..) => {}
             }
         }
     }
diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs
index 62ea3aa76a4..c3b1b34aa29 100644
--- a/compiler/rustc_expand/src/mbe/macro_rules.rs
+++ b/compiler/rustc_expand/src/mbe/macro_rules.rs
@@ -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();
diff --git a/compiler/rustc_expand/src/mbe/metavar_expr.rs b/compiler/rustc_expand/src/mbe/metavar_expr.rs
new file mode 100644
index 00000000000..6c5a755da6f
--- /dev/null
+++ b/compiler/rustc_expand/src/mbe/metavar_expr.rs
@@ -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
+}
diff --git a/compiler/rustc_expand/src/mbe/quoted.rs b/compiler/rustc_expand/src/mbe/quoted.rs
index dedc6c618b9..12c5dac9e0b 100644
--- a/compiler/rustc_expand/src/mbe/quoted.rs
+++ b/compiler/rustc_expand/src/mbe/quoted.rs
@@ -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",
+    );
+}
diff --git a/compiler/rustc_expand/src/mbe/transcribe.rs b/compiler/rustc_expand/src/mbe/transcribe.rs
index 760dea77f9c..b8d83947541 100644
--- a/compiler/rustc_expand/src/mbe/transcribe.rs
+++ b/compiler/rustc_expand/src/mbe/transcribe.rs
@@ -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(())
+}
diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs
index a69d28b184a..a91f05587b2 100644
--- a/compiler/rustc_feature/src/active.rs
+++ b/compiler/rustc_feature/src/active.rs
@@ -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
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 3f44292e034..0d87233d9d7 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -845,6 +845,7 @@ symbols! {
         macro_export,
         macro_lifetime_matcher,
         macro_literal_matcher,
+        macro_metavar_expr,
         macro_reexport,
         macro_use,
         macro_vis_matcher,
diff --git a/src/test/ui/macros/rfc-3086-metavar-expr/dollar-dollar-has-correct-behavior.rs b/src/test/ui/macros/rfc-3086-metavar-expr/dollar-dollar-has-correct-behavior.rs
new file mode 100644
index 00000000000..ed94c27cf05
--- /dev/null
+++ b/src/test/ui/macros/rfc-3086-metavar-expr/dollar-dollar-has-correct-behavior.rs
@@ -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");
+}
diff --git a/src/test/ui/macros/rfc-3086-metavar-expr/feature-gate-macro_metavar_expr.rs b/src/test/ui/macros/rfc-3086-metavar-expr/feature-gate-macro_metavar_expr.rs
new file mode 100644
index 00000000000..6434ecc7e09
--- /dev/null
+++ b/src/test/ui/macros/rfc-3086-metavar-expr/feature-gate-macro_metavar_expr.rs
@@ -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), []);
+}
diff --git a/src/test/ui/macros/rfc-3086-metavar-expr/required-feature.rs b/src/test/ui/macros/rfc-3086-metavar-expr/required-feature.rs
new file mode 100644
index 00000000000..cff6f29a153
--- /dev/null
+++ b/src/test/ui/macros/rfc-3086-metavar-expr/required-feature.rs
@@ -0,0 +1,9 @@
+macro_rules! count {
+    ( $( $e:stmt ),* ) => {
+        ${ count(e) }
+        //~^ ERROR meta-variable expressions are unstable
+    };
+}
+
+fn main() {
+}
diff --git a/src/test/ui/macros/rfc-3086-metavar-expr/required-feature.stderr b/src/test/ui/macros/rfc-3086-metavar-expr/required-feature.stderr
new file mode 100644
index 00000000000..f5731944793
--- /dev/null
+++ b/src/test/ui/macros/rfc-3086-metavar-expr/required-feature.stderr
@@ -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`.
diff --git a/src/test/ui/macros/rfc-3086-metavar-expr/syntax-errors.rs b/src/test/ui/macros/rfc-3086-metavar-expr/syntax-errors.rs
new file mode 100644
index 00000000000..ea73fd0813c
--- /dev/null
+++ b/src/test/ui/macros/rfc-3086-metavar-expr/syntax-errors.rs
@@ -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)
+}
diff --git a/src/test/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr b/src/test/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr
new file mode 100644
index 00000000000..dc8b7a668c4
--- /dev/null
+++ b/src/test/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr
@@ -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`.