diff --git a/compiler/rustc_expand/src/mbe/metavar_expr.rs b/compiler/rustc_expand/src/mbe/metavar_expr.rs index 6e919615019fb..4f8b09fdf1a0d 100644 --- a/compiler/rustc_expand/src/mbe/metavar_expr.rs +++ b/compiler/rustc_expand/src/mbe/metavar_expr.rs @@ -1,4 +1,4 @@ -use rustc_ast::token::{self, Delimiter}; +use rustc_ast::token::{self, Delimiter, TokenKind}; use rustc_ast::tokenstream::{RefTokenTreeCursor, TokenStream, TokenTree}; use rustc_ast::{LitIntType, LitKind}; use rustc_ast_pretty::pprust; @@ -10,6 +10,8 @@ use rustc_span::Span; /// A meta-variable expression, for expansions based on properties of meta-variables. #[derive(Debug, Clone, PartialEq, Encodable, Decodable)] pub(crate) enum MetaVarExpr { + Concat(Ident, Ident), + /// 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>), @@ -42,6 +44,14 @@ impl MetaVarExpr { check_trailing_token(&mut tts, sess)?; let mut iter = args.trees(); let rslt = match ident.as_str() { + "concat" => { + let lhs = parse_ident_or_literal_as_ident(&mut iter, sess, ident.span)?; + if !try_eat_comma(&mut iter) { + return Err(sess.span_diagnostic.struct_span_err(ident.span, "expected comma")); + } + let rhs = parse_ident_or_literal_as_ident(&mut iter, sess, ident.span)?; + MetaVarExpr::Concat(lhs, rhs) + } "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)?), @@ -65,7 +75,7 @@ impl MetaVarExpr { pub(crate) fn ident(&self) -> Option<Ident> { match *self { MetaVarExpr::Count(ident, _) | MetaVarExpr::Ignore(ident) => Some(ident), - MetaVarExpr::Index(..) | MetaVarExpr::Length(..) => None, + MetaVarExpr::Concat(..) | MetaVarExpr::Index(..) | MetaVarExpr::Length(..) => None, } } } @@ -150,6 +160,27 @@ fn parse_ident<'sess>( Err(sess.span_diagnostic.struct_span_err(span, "expected identifier")) } +fn parse_ident_or_literal_as_ident<'sess>( + iter: &mut RefTokenTreeCursor<'_>, + sess: &'sess ParseSess, + span: Span, +) -> PResult<'sess, Ident> { + if let Some(tt) = iter.look_ahead(0) + && let TokenTree::Token(token, _) = tt + && let TokenKind::Literal(lit) = token.kind + { + let ident = Ident::new(lit.symbol, token.span); + let _ = iter.next(); + Ok(ident) + } + else if let Ok(ident) = parse_ident(iter, sess, span) { + Ok(ident) + } + else { + Err(sess.span_diagnostic.struct_span_err(span, "expected identifier or literal")) + } +} + /// 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 RefTokenTreeCursor<'_>) -> bool { diff --git a/compiler/rustc_expand/src/mbe/transcribe.rs b/compiler/rustc_expand/src/mbe/transcribe.rs index d523d3eacbeb9..7507973d2952c 100644 --- a/compiler/rustc_expand/src/mbe/transcribe.rs +++ b/compiler/rustc_expand/src/mbe/transcribe.rs @@ -6,14 +6,14 @@ use crate::errors::{ use crate::mbe::macro_parser::{MatchedNonterminal, MatchedSeq, MatchedTokenTree, NamedMatch}; use crate::mbe::{self, MetaVarExpr}; use rustc_ast::mut_visit::{self, MutVisitor}; -use rustc_ast::token::{self, Delimiter, Token, TokenKind}; +use rustc_ast::token::{self, Delimiter, Nonterminal, Token, TokenKind}; use rustc_ast::tokenstream::{DelimSpan, Spacing, TokenStream, TokenTree}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::{pluralize, PResult}; use rustc_errors::{DiagnosticBuilder, ErrorGuaranteed}; use rustc_span::hygiene::{LocalExpnId, Transparency}; use rustc_span::symbol::{sym, Ident, MacroRulesNormalizedIdent}; -use rustc_span::Span; +use rustc_span::{Span, Symbol}; use smallvec::{smallvec, SmallVec}; use std::mem; @@ -528,6 +528,26 @@ fn transcribe_metavar_expr<'a>( span }; match *expr { + MetaVarExpr::Concat(lhs, rhs) => { + let string = |ident| { + let mrni = MacroRulesNormalizedIdent::new(ident); + if let Some(nm) = lookup_cur_matched(mrni, interp, &repeats) + && let MatchedNonterminal(nt) = nm + && let Nonterminal::NtIdent(nt_ident, _) = &**nt + { + nt_ident.to_string() + } else { + ident.to_string() + } + }; + let symbol_span = lhs.span.to(rhs.span); + let mut symbol_string = string(lhs); + symbol_string.push_str(&string(rhs)); + result.push(TokenTree::Token( + Token::from_ast_ident(Ident::new(Symbol::intern(&symbol_string), symbol_span)), + Spacing::Alone, + )); + } MetaVarExpr::Count(original_ident, depth_opt) => { let matched = matched_from_ident(cx, original_ident, interp)?; let count = count_repetitions(cx, depth_opt, matched, &repeats, sp)?; diff --git a/tests/ui/macros/rfc-3086-metavar-expr/concat.rs b/tests/ui/macros/rfc-3086-metavar-expr/concat.rs new file mode 100644 index 0000000000000..ed5ed32335d6b --- /dev/null +++ b/tests/ui/macros/rfc-3086-metavar-expr/concat.rs @@ -0,0 +1,39 @@ +// run-pass + +#![allow(dead_code, non_camel_case_types)] +#![feature(macro_metavar_expr)] + +macro_rules! simple_ident { + ( $lhs:ident, $rhs:ident ) => { ${concat(lhs, rhs)} }; +} + +macro_rules! create_things { + ( $lhs:ident ) => { + struct ${concat(lhs, _separated_idents_in_a_struct)} { + foo: i32, + ${concat(lhs, _separated_idents_in_a_field)}: i32, + } + + mod ${concat(lhs, _separated_idents_in_a_module)} { + pub const FOO: () = (); + } + + fn ${concat(lhs, _separated_idents_in_a_fn)}() {} + }; +} + +create_things!(look_ma); + +fn main() { + let abcdef = 1; + let _another = simple_ident!(abc, def); + + look_ma_separated_idents_in_a_fn(); + + let _ = look_ma_separated_idents_in_a_module::FOO; + + let _ = look_ma_separated_idents_in_a_struct { + foo: 1, + look_ma_separated_idents_in_a_field: 2, + }; +}