From 5242d59533b6f10be913fc6c8d478f5131491709 Mon Sep 17 00:00:00 2001 From: Caio Date: Tue, 26 Dec 2023 19:51:38 -0300 Subject: [PATCH] [RFC-3086] Add a new concat metavar expr --- compiler/rustc_expand/src/mbe/metavar_expr.rs | 53 +++++++++++- compiler/rustc_expand/src/mbe/transcribe.rs | 35 +++++++- .../rfc-3086-metavar-expr/concat-hygiene.rs | 13 +++ .../concat-hygiene.stderr | 14 ++++ .../ui/macros/rfc-3086-metavar-expr/concat.rs | 49 +++++++++++ .../rfc-3086-metavar-expr/syntax-errors.rs | 48 +++++++++++ .../syntax-errors.stderr | 81 ++++++++++++++++++- 7 files changed, 287 insertions(+), 6 deletions(-) create mode 100644 tests/ui/macros/rfc-3086-metavar-expr/concat-hygiene.rs create mode 100644 tests/ui/macros/rfc-3086-metavar-expr/concat-hygiene.stderr create mode 100644 tests/ui/macros/rfc-3086-metavar-expr/concat.rs diff --git a/compiler/rustc_expand/src/mbe/metavar_expr.rs b/compiler/rustc_expand/src/mbe/metavar_expr.rs index e3dc73d0d851f..9addf20e2d6ff 100644 --- a/compiler/rustc_expand/src/mbe/metavar_expr.rs +++ b/compiler/rustc_expand/src/mbe/metavar_expr.rs @@ -8,8 +8,12 @@ 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)] +#[derive(Debug, Decodable, Encodable, PartialEq)] pub(crate) enum MetaVarExpr { + /// Unification of two identifiers. The `bool` of each element indicates if there is a + /// preceding dollar sign. + Concat(Box<[MetaVarExprConcatElem]>), + /// The number of repetitions of an identifier. Count(Ident, usize), @@ -41,6 +45,34 @@ impl MetaVarExpr { check_trailing_token(&mut tts, sess)?; let mut iter = args.trees(); let rslt = match ident.as_str() { + "concat" => { + fn element<'sess>( + iter: &mut RefTokenTreeCursor<'_>, + sess: &'sess ParseSess, + span: Span, + ) -> PResult<'sess, MetaVarExprConcatElem> { + let is_var = try_eat_dollar(iter); + let ident = parse_ident(iter, sess, span)?; + Ok(MetaVarExprConcatElem { ident, is_var }) + } + + let mut rslt = Vec::new(); + rslt.push(element(&mut iter, sess, ident.span)?); + if !try_eat_comma(&mut iter) { + return Err(sess.dcx.struct_span_err(ident.span, "expected comma")); + } + rslt.push(element(&mut iter, sess, ident.span)?); + loop { + if iter.look_ahead(0).is_none() { + break; + } + if !try_eat_comma(&mut iter) { + return Err(sess.dcx.struct_span_err(ident.span, "expected comma")); + } + rslt.push(element(&mut iter, sess, ident.span)?); + } + MetaVarExpr::Concat(rslt.into()) + } "count" => parse_count(&mut iter, sess, ident.span)?, "ignore" => { eat_dollar(&mut iter, sess, ident.span)?; @@ -67,11 +99,17 @@ impl MetaVarExpr { pub(crate) fn ident(&self) -> Option { match *self { MetaVarExpr::Count(ident, _) | MetaVarExpr::Ignore(ident) => Some(ident), - MetaVarExpr::Index(..) | MetaVarExpr::Length(..) => None, + MetaVarExpr::Concat { .. } | MetaVarExpr::Index(..) | MetaVarExpr::Length(..) => None, } } } +#[derive(Debug, Decodable, Encodable, PartialEq)] +pub(crate) struct MetaVarExprConcatElem { + pub(crate) ident: Ident, + pub(crate) is_var: bool, +} + // Checks if there are any remaining tokens. For example, `${ignore(ident ... a b c ...)}` fn check_trailing_token<'sess>( iter: &mut RefTokenTreeCursor<'_>, @@ -169,6 +207,17 @@ fn try_eat_comma(iter: &mut RefTokenTreeCursor<'_>) -> bool { false } +/// Tries to move the iterator forward returning `true` if there is a dollar sign. If not, then the +/// iterator is not modified and the result is `false`. +fn try_eat_dollar(iter: &mut RefTokenTreeCursor<'_>) -> bool { + if let Some(TokenTree::Token(token::Token { kind: token::Dollar, .. }, _)) = iter.look_ahead(0) + { + let _ = iter.next(); + return true; + } + false +} + /// Expects that the next item is a dollar sign. fn eat_dollar<'sess>( iter: &mut RefTokenTreeCursor<'_>, diff --git a/compiler/rustc_expand/src/mbe/transcribe.rs b/compiler/rustc_expand/src/mbe/transcribe.rs index f2a9875ffd28a..93d8bc9148c07 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::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::DiagnosticBuilder; use rustc_errors::{pluralize, PResult}; 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; @@ -564,6 +564,37 @@ fn transcribe_metavar_expr<'a>( span }; match *expr { + MetaVarExpr::Concat(ref elements) => { + let mut concatenated = String::new(); + for element in elements.into_iter() { + let s = 'string: { + if !element.is_var { + break 'string element.ident.to_string(); + } + let span = element.ident.span; + let mrni = MacroRulesNormalizedIdent::new(element.ident); + if let Some(nm) = lookup_cur_matched(mrni, interp, &repeats) + && let MatchedNonterminal(nt) = nm + { + if let Nonterminal::NtIdent(nt_ident, _) = &nt.0 { + nt_ident.to_string() + } else { + return Err(cx.dcx().struct_span_err( + span, + "`${concat(..)}` currently only accepts identifiers as parameters", + )); + } + } else { + element.ident.to_string() + } + }; + concatenated.push_str(&s); + } + result.push(TokenTree::Token( + Token::from_ast_ident(Ident::new(Symbol::intern(&concatenated), visited_span())), + Spacing::Alone, + )); + } MetaVarExpr::Count(original_ident, depth) => { let matched = matched_from_ident(cx, original_ident, interp)?; let count = count_repetitions(cx, depth, matched, repeats, sp)?; diff --git a/tests/ui/macros/rfc-3086-metavar-expr/concat-hygiene.rs b/tests/ui/macros/rfc-3086-metavar-expr/concat-hygiene.rs new file mode 100644 index 0000000000000..77faa32d164c8 --- /dev/null +++ b/tests/ui/macros/rfc-3086-metavar-expr/concat-hygiene.rs @@ -0,0 +1,13 @@ +#![feature(macro_metavar_expr)] + +macro_rules! join { + ($lhs:ident, $rhs:ident) => { + ${concat($lhs, $rhs)} + //~^ cannot find value `abcdef` in this scope + }; +} + +fn main() { + let abcdef = 1; + let _another = join!(abc, def); +} diff --git a/tests/ui/macros/rfc-3086-metavar-expr/concat-hygiene.stderr b/tests/ui/macros/rfc-3086-metavar-expr/concat-hygiene.stderr new file mode 100644 index 0000000000000..f3150d385ee70 --- /dev/null +++ b/tests/ui/macros/rfc-3086-metavar-expr/concat-hygiene.stderr @@ -0,0 +1,14 @@ +error[E0425]: cannot find value `abcdef` in this scope + --> $DIR/concat-hygiene.rs:5:10 + | +LL | ${concat($lhs, $rhs)} + | ^^^^^^^^^^^^^^^^^^^^ not found in this scope +... +LL | let _another = join!(abc, def); + | --------------- in this macro invocation + | + = note: this error originates in the macro `join` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0425`. 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..0bddd9479cafc --- /dev/null +++ b/tests/ui/macros/rfc-3086-metavar-expr/concat.rs @@ -0,0 +1,49 @@ +// run-pass + +#![allow(dead_code, non_camel_case_types, non_upper_case_globals)] +#![feature(macro_metavar_expr)] + +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)}() {} + }; +} + +macro_rules! many_idents { + ($a:ident, $c:ident) => { + const ${concat($a, B, $c, D)}: i32 = 1; + }; +} + +macro_rules! without_dollar_sign_is_an_ident { + ($ident:ident) => { + const ${concat(VAR, ident)}: i32 = 1; + const ${concat(VAR, $ident)}: i32 = 2; + }; +} + +fn main() { + create_things!(behold); + behold_separated_idents_in_a_fn(); + let _ = behold_separated_idents_in_a_module::FOO; + let _ = behold_separated_idents_in_a_struct { + foo: 1, + behold_separated_idents_in_a_field: 2, + }; + + many_idents!(A, C); + assert_eq!(ABCD, 1); + + without_dollar_sign_is_an_ident!(_123); + assert_eq!(VARident, 1); + assert_eq!(VAR_123, 2); +} diff --git a/tests/ui/macros/rfc-3086-metavar-expr/syntax-errors.rs b/tests/ui/macros/rfc-3086-metavar-expr/syntax-errors.rs index 05c65fe869073..8002e689299ef 100644 --- a/tests/ui/macros/rfc-3086-metavar-expr/syntax-errors.rs +++ b/tests/ui/macros/rfc-3086-metavar-expr/syntax-errors.rs @@ -137,6 +137,50 @@ macro_rules! unknown_metavar { //~| ERROR expected expression } +macro_rules! wrong_concat_declarations { + ($ex:expr) => { + ${concat()} + //~^ ERROR expected identifier + + ${concat(aaaa)} + //~^ ERROR expected comma + + ${concat(aaaa,)} + //~^ ERROR expected identifier + + ${concat(aaaa, 1)} + //~^ ERROR expected identifier + + ${concat(_, aaaa)} + + ${concat(aaaa aaaa)} + //~^ ERROR expected comma + + ${concat($ex)} + //~^ ERROR expected comma + + ${concat($ex, aaaa)} + //~^ `${concat(..)}` currently only accepts identifiers as + + ${concat($ex, aaaa 123)} + //~^ ERROR expected comma + + ${concat($ex, aaaa,)} + //~^ ERROR expected identifier + + ${concat($ex, aaaa, 123)} + //~^ ERROR expected identifier + }; +} + +macro_rules! tt_that_is_dollar_sign_with_concat { + ($sign:tt, $name:ident) => { + const ${concat($sign name, _123)}: () = (); + //~^ expected comma + //~| expected identifier, found `$` + } +} + fn main() { curly__no_rhs_dollar__round!(a, b, c); curly__no_rhs_dollar__no_round!(a); @@ -156,4 +200,8 @@ fn main() { unknown_count_ident!(a); unknown_ignore_ident!(a); unknown_metavar!(a); + + wrong_concat_declarations!(1); + + tt_that_is_dollar_sign_with_concat!($, FOO); } diff --git a/tests/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr b/tests/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr index 0dda38290ab6b..40ecc12164af2 100644 --- a/tests/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr +++ b/tests/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr @@ -196,6 +196,66 @@ error: unrecognized meta-variable expression LL | ( $( $i:ident ),* ) => { ${ aaaaaaaaaaaaaa(i) } }; | ^^^^^^^^^^^^^^ help: supported expressions are count, ignore, index and length +error: expected identifier + --> $DIR/syntax-errors.rs:142:11 + | +LL | ${concat()} + | ^^^^^^ + +error: expected comma + --> $DIR/syntax-errors.rs:145:11 + | +LL | ${concat(aaaa)} + | ^^^^^^ + +error: expected identifier + --> $DIR/syntax-errors.rs:148:11 + | +LL | ${concat(aaaa,)} + | ^^^^^^ + +error: expected identifier, found `1` + --> $DIR/syntax-errors.rs:151:11 + | +LL | ${concat(aaaa, 1)} + | ^^^^^^ - help: try removing `1` + +error: expected comma + --> $DIR/syntax-errors.rs:156:11 + | +LL | ${concat(aaaa aaaa)} + | ^^^^^^ + +error: expected comma + --> $DIR/syntax-errors.rs:159:11 + | +LL | ${concat($ex)} + | ^^^^^^ + +error: expected comma + --> $DIR/syntax-errors.rs:165:11 + | +LL | ${concat($ex, aaaa 123)} + | ^^^^^^ + +error: expected identifier + --> $DIR/syntax-errors.rs:168:11 + | +LL | ${concat($ex, aaaa,)} + | ^^^^^^ + +error: expected identifier, found `123` + --> $DIR/syntax-errors.rs:171:11 + | +LL | ${concat($ex, aaaa, 123)} + | ^^^^^^ --- help: try removing `123` + +error: expected comma + --> $DIR/syntax-errors.rs:178:17 + | +LL | const ${concat($sign name, _123)}: () = (); + | ^^^^^^ + error: `count` can not be placed inside the inner-most repetition --> $DIR/syntax-errors.rs:12:24 | @@ -313,6 +373,23 @@ LL | unknown_metavar!(a); | = note: this error originates in the macro `unknown_metavar` (in Nightly builds, run with -Z macro-backtrace for more info) +error: `${concat(..)}` currently only accepts identifiers as parameters + --> $DIR/syntax-errors.rs:162:19 + | +LL | ${concat($ex, aaaa)} + | ^^ + +error: expected identifier, found `$` + --> $DIR/syntax-errors.rs:178:15 + | +LL | const ${concat($sign name, _123)}: () = (); + | ^ expected identifier +... +LL | tt_that_is_dollar_sign_with_concat!($, FOO); + | ------------------------------------------- in this macro invocation + | + = note: this error originates in the macro `tt_that_is_dollar_sign_with_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + error[E0425]: cannot find value `i` in this scope --> $DIR/syntax-errors.rs:22:36 | @@ -336,7 +413,7 @@ LL | no_curly__no_rhs_dollar__no_round!(a); = 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 `a` in this scope - --> $DIR/syntax-errors.rs:147:37 + --> $DIR/syntax-errors.rs:191:37 | LL | no_curly__rhs_dollar__no_round!(a); | ^ not found in this scope @@ -374,6 +451,6 @@ LL | no_curly__rhs_dollar__no_round!(a); | = 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: aborting due to 39 previous errors +error: aborting due to 51 previous errors For more information about this error, try `rustc --explain E0425`.