Skip to content

Commit 7695bdf

Browse files
committed
[RFC-3086] Add a new concat metavar expr
1 parent deace71 commit 7695bdf

File tree

7 files changed

+287
-6
lines changed

7 files changed

+287
-6
lines changed

compiler/rustc_expand/src/mbe/metavar_expr.rs

+51-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ use rustc_span::symbol::Ident;
88
use rustc_span::Span;
99

1010
/// A meta-variable expression, for expansions based on properties of meta-variables.
11-
#[derive(Debug, Clone, PartialEq, Encodable, Decodable)]
11+
#[derive(Debug, Decodable, Encodable, PartialEq)]
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(Box<[MetaVarExprConcatElem]>),
16+
1317
/// The number of repetitions of an identifier.
1418
Count(Ident, usize),
1519

@@ -41,6 +45,34 @@ 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+
fn element<'sess>(
50+
iter: &mut RefTokenTreeCursor<'_>,
51+
sess: &'sess ParseSess,
52+
span: Span,
53+
) -> PResult<'sess, MetaVarExprConcatElem> {
54+
let is_var = try_eat_dollar(iter);
55+
let ident = parse_ident(iter, sess, span)?;
56+
Ok(MetaVarExprConcatElem { ident, is_var })
57+
}
58+
59+
let mut rslt = Vec::new();
60+
rslt.push(element(&mut iter, sess, ident.span)?);
61+
if !try_eat_comma(&mut iter) {
62+
return Err(sess.dcx.struct_span_err(ident.span, "expected comma"));
63+
}
64+
rslt.push(element(&mut iter, sess, ident.span)?);
65+
loop {
66+
if iter.look_ahead(0).is_none() {
67+
break;
68+
}
69+
if !try_eat_comma(&mut iter) {
70+
return Err(sess.dcx.struct_span_err(ident.span, "expected comma"));
71+
}
72+
rslt.push(element(&mut iter, sess, ident.span)?);
73+
}
74+
MetaVarExpr::Concat(rslt.into())
75+
}
4476
"count" => parse_count(&mut iter, sess, ident.span)?,
4577
"ignore" => {
4678
eat_dollar(&mut iter, sess, ident.span)?;
@@ -67,11 +99,17 @@ impl MetaVarExpr {
6799
pub(crate) fn ident(&self) -> Option<Ident> {
68100
match *self {
69101
MetaVarExpr::Count(ident, _) | MetaVarExpr::Ignore(ident) => Some(ident),
70-
MetaVarExpr::Index(..) | MetaVarExpr::Length(..) => None,
102+
MetaVarExpr::Concat { .. } | MetaVarExpr::Index(..) | MetaVarExpr::Length(..) => None,
71103
}
72104
}
73105
}
74106

107+
#[derive(Debug, Decodable, Encodable, PartialEq)]
108+
pub(crate) struct MetaVarExprConcatElem {
109+
pub(crate) ident: Ident,
110+
pub(crate) is_var: bool,
111+
}
112+
75113
// Checks if there are any remaining tokens. For example, `${ignore(ident ... a b c ...)}`
76114
fn check_trailing_token<'sess>(
77115
iter: &mut RefTokenTreeCursor<'_>,
@@ -169,6 +207,17 @@ fn try_eat_comma(iter: &mut RefTokenTreeCursor<'_>) -> bool {
169207
false
170208
}
171209

210+
/// Tries to move the iterator forward returning `true` if there is a dollar sign. If not, then the
211+
/// iterator is not modified and the result is `false`.
212+
fn try_eat_dollar(iter: &mut RefTokenTreeCursor<'_>) -> bool {
213+
if let Some(TokenTree::Token(token::Token { kind: token::Dollar, .. }, _)) = iter.look_ahead(0)
214+
{
215+
let _ = iter.next();
216+
return true;
217+
}
218+
false
219+
}
220+
172221
/// Expects that the next item is a dollar sign.
173222
fn eat_dollar<'sess>(
174223
iter: &mut RefTokenTreeCursor<'_>,

compiler/rustc_expand/src/mbe/transcribe.rs

+33-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::DiagnosticBuilder;
1313
use rustc_errors::{pluralize, PResult};
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;
@@ -564,6 +564,37 @@ fn transcribe_metavar_expr<'a>(
564564
span
565565
};
566566
match *expr {
567+
MetaVarExpr::Concat(ref elements) => {
568+
let mut concatenated = String::new();
569+
for element in elements.into_iter() {
570+
let s = 'string: {
571+
if !element.is_var {
572+
break 'string element.ident.to_string();
573+
}
574+
let span = element.ident.span;
575+
let mrni = MacroRulesNormalizedIdent::new(element.ident);
576+
if let Some(nm) = lookup_cur_matched(mrni, interp, &repeats)
577+
&& let MatchedNonterminal(nt) = nm
578+
{
579+
if let Nonterminal::NtIdent(nt_ident, _) = &nt.0 {
580+
nt_ident.to_string()
581+
} else {
582+
return Err(cx.struct_span_err(
583+
span,
584+
"`${concat(..)}` currently only accepts identifiers as parameters",
585+
));
586+
}
587+
} else {
588+
element.ident.to_string()
589+
}
590+
};
591+
concatenated.push_str(&s);
592+
}
593+
result.push(TokenTree::Token(
594+
Token::from_ast_ident(Ident::new(Symbol::intern(&concatenated), visited_span())),
595+
Spacing::Alone,
596+
));
597+
}
567598
MetaVarExpr::Count(original_ident, depth) => {
568599
let matched = matched_from_ident(cx, original_ident, interp)?;
569600
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,49 @@
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! many_idents {
22+
($a:ident, $c:ident) => {
23+
const ${concat($a, B, $c, D)}: i32 = 1;
24+
};
25+
}
26+
27+
macro_rules! without_dollar_sign_is_an_ident {
28+
($ident:ident) => {
29+
const ${concat(VAR, ident)}: i32 = 1;
30+
const ${concat(VAR, $ident)}: i32 = 2;
31+
};
32+
}
33+
34+
fn main() {
35+
create_things!(behold);
36+
behold_separated_idents_in_a_fn();
37+
let _ = behold_separated_idents_in_a_module::FOO;
38+
let _ = behold_separated_idents_in_a_struct {
39+
foo: 1,
40+
behold_separated_idents_in_a_field: 2,
41+
};
42+
43+
many_idents!(A, C);
44+
assert_eq!(ABCD, 1);
45+
46+
without_dollar_sign_is_an_ident!(_123);
47+
assert_eq!(VARident, 1);
48+
assert_eq!(VAR_123, 2);
49+
}

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

+48
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,50 @@ 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+
${concat($ex, aaaa 123)}
166+
//~^ ERROR expected comma
167+
168+
${concat($ex, aaaa,)}
169+
//~^ ERROR expected identifier
170+
171+
${concat($ex, aaaa, 123)}
172+
//~^ ERROR expected identifier
173+
};
174+
}
175+
176+
macro_rules! tt_that_is_dollar_sign_with_concat {
177+
($sign:tt, $name:ident) => {
178+
const ${concat($sign name, _123)}: () = ();
179+
//~^ expected comma
180+
//~| expected identifier, found `$`
181+
}
182+
}
183+
140184
fn main() {
141185
curly__no_rhs_dollar__round!(a, b, c);
142186
curly__no_rhs_dollar__no_round!(a);
@@ -156,4 +200,8 @@ fn main() {
156200
unknown_count_ident!(a);
157201
unknown_ignore_ident!(a);
158202
unknown_metavar!(a);
203+
204+
wrong_concat_declarations!(1);
205+
206+
tt_that_is_dollar_sign_with_concat!($, FOO);
159207
}

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

+79-2
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,66 @@ 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:165:11
237+
|
238+
LL | ${concat($ex, aaaa 123)}
239+
| ^^^^^^
240+
241+
error: expected identifier
242+
--> $DIR/syntax-errors.rs:168:11
243+
|
244+
LL | ${concat($ex, aaaa,)}
245+
| ^^^^^^
246+
247+
error: expected identifier, found `123`
248+
--> $DIR/syntax-errors.rs:171:11
249+
|
250+
LL | ${concat($ex, aaaa, 123)}
251+
| ^^^^^^ --- help: try removing `123`
252+
253+
error: expected comma
254+
--> $DIR/syntax-errors.rs:178:17
255+
|
256+
LL | const ${concat($sign name, _123)}: () = ();
257+
| ^^^^^^
258+
199259
error: `count` can not be placed inside the inner-most repetition
200260
--> $DIR/syntax-errors.rs:12:24
201261
|
@@ -313,6 +373,23 @@ LL | unknown_metavar!(a);
313373
|
314374
= note: this error originates in the macro `unknown_metavar` (in Nightly builds, run with -Z macro-backtrace for more info)
315375

376+
error: `${concat(..)}` currently only accepts identifiers as parameters
377+
--> $DIR/syntax-errors.rs:162:19
378+
|
379+
LL | ${concat($ex, aaaa)}
380+
| ^^
381+
382+
error: expected identifier, found `$`
383+
--> $DIR/syntax-errors.rs:178:15
384+
|
385+
LL | const ${concat($sign name, _123)}: () = ();
386+
| ^ expected identifier
387+
...
388+
LL | tt_that_is_dollar_sign_with_concat!($, FOO);
389+
| ------------------------------------------- in this macro invocation
390+
|
391+
= 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)
392+
316393
error[E0425]: cannot find value `i` in this scope
317394
--> $DIR/syntax-errors.rs:22:36
318395
|
@@ -336,7 +413,7 @@ LL | no_curly__no_rhs_dollar__no_round!(a);
336413
= 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)
337414

338415
error[E0425]: cannot find value `a` in this scope
339-
--> $DIR/syntax-errors.rs:147:37
416+
--> $DIR/syntax-errors.rs:191:37
340417
|
341418
LL | no_curly__rhs_dollar__no_round!(a);
342419
| ^ not found in this scope
@@ -374,6 +451,6 @@ LL | no_curly__rhs_dollar__no_round!(a);
374451
|
375452
= note: this error originates in the macro `no_curly__rhs_dollar__no_round` (in Nightly builds, run with -Z macro-backtrace for more info)
376453

377-
error: aborting due to 39 previous errors
454+
error: aborting due to 51 previous errors
378455

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

0 commit comments

Comments
 (0)