diff --git a/compiler/rustc_ast/src/util/literal.rs b/compiler/rustc_ast/src/util/literal.rs index 231dd72af2c7..9c18f55c03b4 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 { + pub fn from_lit_token(lit: token::Lit) -> Result { 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 b024524aa296..8a9efe01368e 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 5244ac36bba5..3d4c77aba733 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), + /// 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 3497e5ad543a..88e116932209 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 bb36dfd793d4..04137086088d 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>( } 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 62ea3aa76a46..c3b1b34aa29b 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 000000000000..6c5a755da6f4 --- /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), + + /// 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 dedc6c618b9a..12c5dac9e0bf 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, - 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 760dea77f9c2..b8d839475413 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, + _repeats: &[(usize, usize)], + _result: &mut Vec, + _sp: &DelimSpan, +) -> PResult<'a, ()> { + Ok(()) +} diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs index a69d28b184ae..a91f05587b27 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 3f44292e0342..0d87233d9d79 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 000000000000..ed94c27cf05c --- /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 000000000000..6434ecc7e092 --- /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 000000000000..cff6f29a1538 --- /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 000000000000..f57319447931 --- /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 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 000000000000..ea73fd0813c5 --- /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 000000000000..dc8b7a668c4e --- /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`.