Skip to content

Commit

Permalink
[RFC-3086] Add a new concat metavar expr
Browse files Browse the repository at this point in the history
  • Loading branch information
c410-f3r committed Dec 18, 2023
1 parent 740cea8 commit b08d90b
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 5 deletions.
27 changes: 26 additions & 1 deletion compiler/rustc_expand/src/mbe/metavar_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ 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 {
/// Unification of two identifiers. The `bool` of each element indicates if there is a
/// preceding dollar sign.
Concat { lhs_ident: Ident, lhs_is_var: bool, rhs_ident: Ident, rhs_is_var: bool },

/// The number of repetitions of an identifier.
Count(Ident, usize),

Expand Down Expand Up @@ -41,6 +45,16 @@ impl MetaVarExpr {
check_trailing_token(&mut tts, sess)?;
let mut iter = args.trees();
let rslt = match ident.as_str() {
"concat" => {
let lhs_is_var = try_eat_dollar(&mut iter);
let lhs_ident = parse_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_is_var = try_eat_dollar(&mut iter);
let rhs_ident = parse_ident(&mut iter, sess, ident.span)?;
MetaVarExpr::Concat { lhs_ident, lhs_is_var, rhs_ident, rhs_is_var }
}
"count" => parse_count(&mut iter, sess, ident.span)?,
"ignore" => {
eat_dollar(&mut iter, sess, ident.span)?;
Expand All @@ -67,7 +81,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,
}
}
}
Expand Down Expand Up @@ -170,6 +184,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<'_>,
Expand Down
41 changes: 39 additions & 2 deletions compiler/rustc_expand/src/mbe/transcribe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{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;
Expand Down Expand Up @@ -558,6 +558,43 @@ fn transcribe_metavar_expr<'a>(
span
};
match *expr {
MetaVarExpr::Concat { lhs_ident, lhs_is_var, rhs_ident, rhs_is_var } => {
fn manage_element<'a>(
cx: &ExtCtxt<'a>,
ident: Ident,
interp: &FxHashMap<MacroRulesNormalizedIdent, NamedMatch>,
is_var: bool,
repeats: &[(usize, usize)],
) -> PResult<'a, String> {
if !is_var {
return Ok(ident.to_string());
}
let span = ident.span;
let mrni = MacroRulesNormalizedIdent::new(ident);
if let Some(nm) = lookup_cur_matched(mrni, interp, &repeats)
&& let MatchedNonterminal(nt) = nm
{
if let Nonterminal::NtIdent(nt_ident, _) = &nt.0 {
Ok(nt_ident.to_string())
} else {
Err(cx.struct_span_err(
span,
"`${concat(..)}` currently only accepts identifiers as parameters",
))
}
} else {
Ok(ident.to_string())
}
}

let lhs_elem = manage_element(cx, lhs_ident, interp, lhs_is_var, repeats)?;
let rhs_elem = manage_element(cx, rhs_ident, interp, rhs_is_var, repeats)?;
let symbol_string = lhs_elem + &rhs_elem;
result.push(TokenTree::Token(
Token::from_ast_ident(Ident::new(Symbol::intern(&symbol_string), 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)?;
Expand Down
13 changes: 13 additions & 0 deletions tests/ui/macros/rfc-3086-metavar-expr/concat-hygiene.rs
Original file line number Diff line number Diff line change
@@ -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);
}
14 changes: 14 additions & 0 deletions tests/ui/macros/rfc-3086-metavar-expr/concat-hygiene.stderr
Original file line number Diff line number Diff line change
@@ -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`.
40 changes: 40 additions & 0 deletions tests/ui/macros/rfc-3086-metavar-expr/concat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// 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! 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,
};

without_dollar_sign_is_an_ident!(_123);
assert_eq!(VARident, 1);
assert_eq!(VAR_123, 2);
}
39 changes: 39 additions & 0 deletions tests/ui/macros/rfc-3086-metavar-expr/syntax-errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,41 @@ 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
};
}

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);
Expand All @@ -156,4 +191,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);
}
63 changes: 61 additions & 2 deletions tests/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,48 @@ 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:169:17
|
LL | const ${concat($sign name, _123)}: () = ();
| ^^^^^^

error: `count` can not be placed inside the inner-most repetition
--> $DIR/syntax-errors.rs:12:24
|
Expand Down Expand Up @@ -313,6 +355,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:169: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
|
Expand All @@ -336,7 +395,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:182:37
|
LL | no_curly__rhs_dollar__no_round!(a);
| ^ not found in this scope
Expand Down Expand Up @@ -374,6 +433,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 48 previous errors

For more information about this error, try `rustc --explain E0425`.

0 comments on commit b08d90b

Please sign in to comment.