Skip to content

Commit 97b22d8

Browse files
committed
[RFC-3086] Add a new concat metavar expr
1 parent bf9229a commit 97b22d8

File tree

7 files changed

+232
-5
lines changed

7 files changed

+232
-5
lines changed

compiler/rustc_expand/src/mbe/metavar_expr.rs

+26-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ use rustc_span::Span;
1010
/// A meta-variable expression, for expansions based on properties of meta-variables.
1111
#[derive(Debug, Clone, PartialEq, Encodable, Decodable)]
1212
pub(crate) enum MetaVarExpr {
13+
/// Unification of two identifiers. The `bool` of each element indicates if there is a
14+
/// preceding dollar sign.
15+
Concat { lhs_ident: Ident, lhs_is_var: bool, rhs_ident: Ident, rhs_is_var: bool },
16+
1317
/// The number of repetitions of an identifier.
1418
Count(Ident, usize),
1519

@@ -41,6 +45,16 @@ impl MetaVarExpr {
4145
check_trailing_token(&mut tts, sess)?;
4246
let mut iter = args.trees();
4347
let rslt = match ident.as_str() {
48+
"concat" => {
49+
let lhs_is_var = try_eat_dollar(&mut iter);
50+
let lhs_ident = parse_ident(&mut iter, sess, ident.span)?;
51+
if !try_eat_comma(&mut iter) {
52+
return Err(sess.span_diagnostic.struct_span_err(ident.span, "expected comma"));
53+
}
54+
let rhs_is_var = try_eat_dollar(&mut iter);
55+
let rhs_ident = parse_ident(&mut iter, sess, ident.span)?;
56+
MetaVarExpr::Concat { lhs_ident, lhs_is_var, rhs_ident, rhs_is_var }
57+
}
4458
"count" => parse_count(&mut iter, sess, ident.span)?,
4559
"ignore" => {
4660
eat_dollar(&mut iter, sess, ident.span)?;
@@ -67,7 +81,7 @@ impl MetaVarExpr {
6781
pub(crate) fn ident(&self) -> Option<Ident> {
6882
match *self {
6983
MetaVarExpr::Count(ident, _) | MetaVarExpr::Ignore(ident) => Some(ident),
70-
MetaVarExpr::Index(..) | MetaVarExpr::Length(..) => None,
84+
MetaVarExpr::Concat { .. } | MetaVarExpr::Index(..) | MetaVarExpr::Length(..) => None,
7185
}
7286
}
7387
}
@@ -169,6 +183,17 @@ fn try_eat_comma(iter: &mut RefTokenTreeCursor<'_>) -> bool {
169183
false
170184
}
171185

186+
/// Tries to move the iterator forward returning `true` if there is a dollar sign. If not, then the
187+
/// iterator is not modified and the result is `false`.
188+
fn try_eat_dollar(iter: &mut RefTokenTreeCursor<'_>) -> bool {
189+
if let Some(TokenTree::Token(token::Token { kind: token::Dollar, .. }, _)) = iter.look_ahead(0)
190+
{
191+
let _ = iter.next();
192+
return true;
193+
}
194+
false
195+
}
196+
172197
/// Expects that the next item is a dollar sign.
173198
fn eat_dollar<'sess>(
174199
iter: &mut RefTokenTreeCursor<'_>,

compiler/rustc_expand/src/mbe/transcribe.rs

+39-2
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ use crate::errors::{
66
use crate::mbe::macro_parser::{MatchedNonterminal, MatchedSeq, MatchedTokenTree, NamedMatch};
77
use crate::mbe::{self, MetaVarExpr};
88
use rustc_ast::mut_visit::{self, MutVisitor};
9-
use rustc_ast::token::{self, Delimiter, Token, TokenKind};
9+
use rustc_ast::token::{self, Delimiter, Nonterminal, Token, TokenKind};
1010
use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree};
1111
use rustc_data_structures::fx::FxHashMap;
1212
use rustc_errors::{pluralize, PResult};
1313
use rustc_errors::{DiagnosticBuilder, ErrorGuaranteed};
1414
use rustc_span::hygiene::{LocalExpnId, Transparency};
1515
use rustc_span::symbol::{sym, Ident, MacroRulesNormalizedIdent};
16-
use rustc_span::Span;
16+
use rustc_span::{Span, Symbol};
1717

1818
use smallvec::{smallvec, SmallVec};
1919
use std::mem;
@@ -558,6 +558,43 @@ fn transcribe_metavar_expr<'a>(
558558
span
559559
};
560560
match *expr {
561+
MetaVarExpr::Concat { lhs_ident, lhs_is_var, rhs_ident, rhs_is_var } => {
562+
fn manage_element<'a>(
563+
cx: &ExtCtxt<'a>,
564+
ident: Ident,
565+
interp: &FxHashMap<MacroRulesNormalizedIdent, NamedMatch>,
566+
is_var: bool,
567+
repeats: &[(usize, usize)],
568+
) -> PResult<'a, String> {
569+
if !is_var {
570+
return Ok(ident.to_string());
571+
}
572+
let span = ident.span;
573+
let mrni = MacroRulesNormalizedIdent::new(ident);
574+
if let Some(nm) = lookup_cur_matched(mrni, interp, &repeats)
575+
&& let MatchedNonterminal(nt) = nm
576+
{
577+
if let Nonterminal::NtIdent(nt_ident, _) = &nt.0 {
578+
Ok(nt_ident.to_string())
579+
} else {
580+
Err(cx.struct_span_err(
581+
span,
582+
"`${concat(..)}` currently only accepts identifiers as parameters",
583+
))
584+
}
585+
} else {
586+
Ok(ident.to_string())
587+
}
588+
}
589+
590+
let lhs_elem = manage_element(cx, lhs_ident, interp, lhs_is_var, repeats)?;
591+
let rhs_elem = manage_element(cx, rhs_ident, interp, rhs_is_var, repeats)?;
592+
let symbol_string = lhs_elem + &rhs_elem;
593+
result.push(TokenTree::Token(
594+
Token::from_ast_ident(Ident::new(Symbol::intern(&symbol_string), visited_span())),
595+
Spacing::Alone,
596+
));
597+
}
561598
MetaVarExpr::Count(original_ident, depth) => {
562599
let matched = matched_from_ident(cx, original_ident, interp)?;
563600
let count = count_repetitions(cx, depth, matched, repeats, sp)?;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#![feature(macro_metavar_expr)]
2+
3+
macro_rules! join {
4+
($lhs:ident, $rhs:ident) => {
5+
${concat($lhs, $rhs)}
6+
//~^ cannot find value `abcdef` in this scope
7+
};
8+
}
9+
10+
fn main() {
11+
let abcdef = 1;
12+
let _another = join!(abc, def);
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error[E0425]: cannot find value `abcdef` in this scope
2+
--> $DIR/concat-hygiene.rs:5:10
3+
|
4+
LL | ${concat($lhs, $rhs)}
5+
| ^^^^^^^^^^^^^^^^^^^^ not found in this scope
6+
...
7+
LL | let _another = join!(abc, def);
8+
| --------------- in this macro invocation
9+
|
10+
= note: this error originates in the macro `join` (in Nightly builds, run with -Z macro-backtrace for more info)
11+
12+
error: aborting due to 1 previous error
13+
14+
For more information about this error, try `rustc --explain E0425`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// run-pass
2+
3+
#![allow(dead_code, non_camel_case_types, non_upper_case_globals)]
4+
#![feature(macro_metavar_expr)]
5+
6+
macro_rules! create_things {
7+
($lhs:ident) => {
8+
struct ${concat($lhs, _separated_idents_in_a_struct)} {
9+
foo: i32,
10+
${concat($lhs, _separated_idents_in_a_field)}: i32,
11+
}
12+
13+
mod ${concat($lhs, _separated_idents_in_a_module)} {
14+
pub const FOO: () = ();
15+
}
16+
17+
fn ${concat($lhs, _separated_idents_in_a_fn)}() {}
18+
};
19+
}
20+
21+
macro_rules! without_dollar_sign_is_an_ident {
22+
($ident:ident) => {
23+
const ${concat(VAR, ident)}: i32 = 1;
24+
const ${concat(VAR, $ident)}: i32 = 2;
25+
};
26+
}
27+
28+
fn main() {
29+
create_things!(behold);
30+
behold_separated_idents_in_a_fn();
31+
let _ = behold_separated_idents_in_a_module::FOO;
32+
let _ = behold_separated_idents_in_a_struct {
33+
foo: 1,
34+
behold_separated_idents_in_a_field: 2,
35+
};
36+
37+
without_dollar_sign_is_an_ident!(_123);
38+
assert_eq!(VARident, 1);
39+
assert_eq!(VAR_123, 2);
40+
}

tests/ui/macros/rfc-3086-metavar-expr/syntax-errors.rs

+39
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,41 @@ macro_rules! unknown_metavar {
137137
//~| ERROR expected expression
138138
}
139139

140+
macro_rules! wrong_concat_declarations {
141+
($ex:expr) => {
142+
${concat()}
143+
//~^ ERROR expected identifier
144+
145+
${concat(aaaa)}
146+
//~^ ERROR expected comma
147+
148+
${concat(aaaa,)}
149+
//~^ ERROR expected identifier
150+
151+
${concat(aaaa, 1)}
152+
//~^ ERROR expected identifier
153+
154+
${concat(_, aaaa)}
155+
156+
${concat(aaaa aaaa)}
157+
//~^ ERROR expected comma
158+
159+
${concat($ex)}
160+
//~^ ERROR expected comma
161+
162+
${concat($ex, aaaa)}
163+
//~^ `${concat(..)}` currently only accepts identifiers as
164+
};
165+
}
166+
167+
macro_rules! tt_that_is_dollar_sign_with_concat {
168+
($sign:tt, $name:ident) => {
169+
const ${concat($sign name, _123)}: () = ();
170+
//~^ expected comma
171+
//~| expected identifier, found `$`
172+
}
173+
}
174+
140175
fn main() {
141176
curly__no_rhs_dollar__round!(a, b, c);
142177
curly__no_rhs_dollar__no_round!(a);
@@ -156,4 +191,8 @@ fn main() {
156191
unknown_count_ident!(a);
157192
unknown_ignore_ident!(a);
158193
unknown_metavar!(a);
194+
195+
wrong_concat_declarations!(1);
196+
197+
tt_that_is_dollar_sign_with_concat!($, FOO);
159198
}

tests/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr

+61-2
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,48 @@ error: unrecognized meta-variable expression
196196
LL | ( $( $i:ident ),* ) => { ${ aaaaaaaaaaaaaa(i) } };
197197
| ^^^^^^^^^^^^^^ help: supported expressions are count, ignore, index and length
198198

199+
error: expected identifier
200+
--> $DIR/syntax-errors.rs:142:11
201+
|
202+
LL | ${concat()}
203+
| ^^^^^^
204+
205+
error: expected comma
206+
--> $DIR/syntax-errors.rs:145:11
207+
|
208+
LL | ${concat(aaaa)}
209+
| ^^^^^^
210+
211+
error: expected identifier
212+
--> $DIR/syntax-errors.rs:148:11
213+
|
214+
LL | ${concat(aaaa,)}
215+
| ^^^^^^
216+
217+
error: expected identifier, found `1`
218+
--> $DIR/syntax-errors.rs:151:11
219+
|
220+
LL | ${concat(aaaa, 1)}
221+
| ^^^^^^ - help: try removing `1`
222+
223+
error: expected comma
224+
--> $DIR/syntax-errors.rs:156:11
225+
|
226+
LL | ${concat(aaaa aaaa)}
227+
| ^^^^^^
228+
229+
error: expected comma
230+
--> $DIR/syntax-errors.rs:159:11
231+
|
232+
LL | ${concat($ex)}
233+
| ^^^^^^
234+
235+
error: expected comma
236+
--> $DIR/syntax-errors.rs:169:17
237+
|
238+
LL | const ${concat($sign name, _123)}: () = ();
239+
| ^^^^^^
240+
199241
error: `count` can not be placed inside the inner-most repetition
200242
--> $DIR/syntax-errors.rs:12:24
201243
|
@@ -313,6 +355,23 @@ LL | unknown_metavar!(a);
313355
|
314356
= note: this error originates in the macro `unknown_metavar` (in Nightly builds, run with -Z macro-backtrace for more info)
315357

358+
error: `${concat(..)}` currently only accepts identifiers as parameters
359+
--> $DIR/syntax-errors.rs:162:19
360+
|
361+
LL | ${concat($ex, aaaa)}
362+
| ^^
363+
364+
error: expected identifier, found `$`
365+
--> $DIR/syntax-errors.rs:169:15
366+
|
367+
LL | const ${concat($sign name, _123)}: () = ();
368+
| ^ expected identifier
369+
...
370+
LL | tt_that_is_dollar_sign_with_concat!($, FOO);
371+
| ------------------------------------------- in this macro invocation
372+
|
373+
= 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)
374+
316375
error[E0425]: cannot find value `i` in this scope
317376
--> $DIR/syntax-errors.rs:22:36
318377
|
@@ -336,7 +395,7 @@ LL | no_curly__no_rhs_dollar__no_round!(a);
336395
= 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)
337396

338397
error[E0425]: cannot find value `a` in this scope
339-
--> $DIR/syntax-errors.rs:147:37
398+
--> $DIR/syntax-errors.rs:182:37
340399
|
341400
LL | no_curly__rhs_dollar__no_round!(a);
342401
| ^ not found in this scope
@@ -374,6 +433,6 @@ LL | no_curly__rhs_dollar__no_round!(a);
374433
|
375434
= note: this error originates in the macro `no_curly__rhs_dollar__no_round` (in Nightly builds, run with -Z macro-backtrace for more info)
376435

377-
error: aborting due to 39 previous errors
436+
error: aborting due to 48 previous errors
378437

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

0 commit comments

Comments
 (0)