Skip to content

Commit 4a9dcc6

Browse files
committed
Add support for literals
1 parent a0f01c3 commit 4a9dcc6

File tree

6 files changed

+170
-53
lines changed

6 files changed

+170
-53
lines changed

compiler/rustc_expand/src/mbe/metavar_expr.rs

+44-21
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
use rustc_ast::token::{self, Delimiter, IdentIsRaw};
1+
use rustc_ast::token::{self, Delimiter, IdentIsRaw, Lit, Token, TokenKind};
22
use rustc_ast::tokenstream::{RefTokenTreeCursor, TokenStream, TokenTree};
33
use rustc_ast::{LitIntType, LitKind};
44
use rustc_ast_pretty::pprust;
55
use rustc_errors::{Applicability, PResult};
66
use rustc_macros::{Decodable, Encodable};
77
use rustc_session::parse::ParseSess;
88
use rustc_span::symbol::Ident;
9-
use rustc_span::Span;
9+
use rustc_span::{Span, Symbol};
1010

1111
pub(crate) const RAW_IDENT_ERR: &str = "`${concat(..)}` currently does not support raw identifiers";
1212

@@ -51,11 +51,18 @@ impl MetaVarExpr {
5151
let mut result = Vec::new();
5252
loop {
5353
let is_var = try_eat_dollar(&mut iter);
54-
let element_ident = parse_ident(&mut iter, psess, outer_span)?;
54+
let token = parse_token(&mut iter, psess, outer_span)?;
5555
let element = if is_var {
56-
MetaVarExprConcatElem::Var(element_ident)
56+
MetaVarExprConcatElem::Var(parse_ident_from_token(psess, token)?)
57+
} else if let TokenKind::Literal(Lit {
58+
kind: token::LitKind::Char | token::LitKind::Integer | token::LitKind::Str,
59+
symbol,
60+
suffix: None,
61+
}) = token.kind
62+
{
63+
MetaVarExprConcatElem::Literal(symbol)
5764
} else {
58-
MetaVarExprConcatElem::Ident(element_ident)
65+
MetaVarExprConcatElem::Ident(parse_ident_from_token(psess, token)?)
5966
};
6067
result.push(element);
6168
if iter.look_ahead(0).is_none() {
@@ -105,11 +112,13 @@ impl MetaVarExpr {
105112

106113
#[derive(Debug, Decodable, Encodable, PartialEq)]
107114
pub(crate) enum MetaVarExprConcatElem {
108-
/// There is NO preceding dollar sign, which means that this identifier should be interpreted
109-
/// as a literal.
115+
/// Identifier WITHOUT a preceding dollar sign, which means that this identifier should be
116+
/// interpreted as a literal.
110117
Ident(Ident),
111-
/// There is a preceding dollar sign, which means that this identifier should be expanded
112-
/// and interpreted as a variable.
118+
/// For example, a number or a string.
119+
Literal(Symbol),
120+
/// Identifier WITH a preceding dollar sign, which means that this identifier should be
121+
/// expanded and interpreted as a variable.
113122
Var(Ident),
114123
}
115124

@@ -158,7 +167,7 @@ fn parse_depth<'psess>(
158167
span: Span,
159168
) -> PResult<'psess, usize> {
160169
let Some(tt) = iter.next() else { return Ok(0) };
161-
let TokenTree::Token(token::Token { kind: token::TokenKind::Literal(lit), .. }, _) = tt else {
170+
let TokenTree::Token(Token { kind: TokenKind::Literal(lit), .. }, _) = tt else {
162171
return Err(psess
163172
.dcx()
164173
.struct_span_err(span, "meta-variable expression depth must be a literal"));
@@ -180,12 +189,14 @@ fn parse_ident<'psess>(
180189
psess: &'psess ParseSess,
181190
fallback_span: Span,
182191
) -> PResult<'psess, Ident> {
183-
let Some(tt) = iter.next() else {
184-
return Err(psess.dcx().struct_span_err(fallback_span, "expected identifier"));
185-
};
186-
let TokenTree::Token(token, _) = tt else {
187-
return Err(psess.dcx().struct_span_err(tt.span(), "expected identifier"));
188-
};
192+
let token = parse_token(iter, psess, fallback_span)?;
193+
parse_ident_from_token(psess, token)
194+
}
195+
196+
fn parse_ident_from_token<'psess>(
197+
psess: &'psess ParseSess,
198+
token: &Token,
199+
) -> PResult<'psess, Ident> {
189200
if let Some((elem, is_raw)) = token.ident() {
190201
if let IdentIsRaw::Yes = is_raw {
191202
return Err(psess.dcx().struct_span_err(elem.span, RAW_IDENT_ERR));
@@ -205,10 +216,24 @@ fn parse_ident<'psess>(
205216
Err(err)
206217
}
207218

219+
fn parse_token<'psess, 't>(
220+
iter: &mut RefTokenTreeCursor<'t>,
221+
psess: &'psess ParseSess,
222+
fallback_span: Span,
223+
) -> PResult<'psess, &'t Token> {
224+
let Some(tt) = iter.next() else {
225+
return Err(psess.dcx().struct_span_err(fallback_span, "expected identifier or literal"));
226+
};
227+
let TokenTree::Token(token, _) = tt else {
228+
return Err(psess.dcx().struct_span_err(tt.span(), "expected identifier or literal"));
229+
};
230+
Ok(token)
231+
}
232+
208233
/// Tries to move the iterator forward returning `true` if there is a comma. If not, then the
209234
/// iterator is not modified and the result is `false`.
210235
fn try_eat_comma(iter: &mut RefTokenTreeCursor<'_>) -> bool {
211-
if let Some(TokenTree::Token(token::Token { kind: token::Comma, .. }, _)) = iter.look_ahead(0) {
236+
if let Some(TokenTree::Token(Token { kind: token::Comma, .. }, _)) = iter.look_ahead(0) {
212237
let _ = iter.next();
213238
return true;
214239
}
@@ -218,8 +243,7 @@ fn try_eat_comma(iter: &mut RefTokenTreeCursor<'_>) -> bool {
218243
/// Tries to move the iterator forward returning `true` if there is a dollar sign. If not, then the
219244
/// iterator is not modified and the result is `false`.
220245
fn try_eat_dollar(iter: &mut RefTokenTreeCursor<'_>) -> bool {
221-
if let Some(TokenTree::Token(token::Token { kind: token::Dollar, .. }, _)) = iter.look_ahead(0)
222-
{
246+
if let Some(TokenTree::Token(Token { kind: token::Dollar, .. }, _)) = iter.look_ahead(0) {
223247
let _ = iter.next();
224248
return true;
225249
}
@@ -232,8 +256,7 @@ fn eat_dollar<'psess>(
232256
psess: &'psess ParseSess,
233257
span: Span,
234258
) -> PResult<'psess, ()> {
235-
if let Some(TokenTree::Token(token::Token { kind: token::Dollar, .. }, _)) = iter.look_ahead(0)
236-
{
259+
if let Some(TokenTree::Token(Token { kind: token::Dollar, .. }, _)) = iter.look_ahead(0) {
237260
let _ = iter.next();
238261
return Ok(());
239262
}

compiler/rustc_expand/src/mbe/transcribe.rs

+23-3
Original file line numberDiff line numberDiff line change
@@ -680,16 +680,36 @@ fn transcribe_metavar_expr<'a>(
680680
let mut concatenated = String::new();
681681
for element in elements.into_iter() {
682682
let string = match element {
683-
MetaVarExprConcatElem::Ident(ident) => ident.to_string(),
684-
MetaVarExprConcatElem::Var(ident) => extract_ident(dcx, *ident, interp)?,
683+
MetaVarExprConcatElem::Ident(elem) => elem.to_string(),
684+
MetaVarExprConcatElem::Literal(elem) => elem.as_str().into(),
685+
MetaVarExprConcatElem::Var(elem) => extract_ident(dcx, *elem, interp)?,
685686
};
686687
concatenated.push_str(&string);
687688
}
689+
let concatenated_span = visited_span();
690+
let has_valid_ascii_ident = if let [first, rest @ ..] = concatenated.as_bytes() {
691+
let first_is_valid = matches!(first, b'_' | b'a'..=b'z' | b'A'..=b'Z');
692+
let rest_is_valid = rest
693+
.iter()
694+
.all(|b| matches!(b, b'_' | b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9'));
695+
first_is_valid && rest_is_valid
696+
} else {
697+
return Err(dcx.struct_span_err(
698+
concatenated_span,
699+
"`${concat(..)}` is generating an empty identifier",
700+
));
701+
};
702+
if !has_valid_ascii_ident {
703+
return Err(dcx.struct_span_err(
704+
concatenated_span,
705+
"`${concat(..)}` is not generating a valid identifier",
706+
));
707+
}
688708
// The current implementation marks the span as coming from the macro regardless of
689709
// contexts of the concatenated identifiers but this behavior may change in the
690710
// future.
691711
result.push(TokenTree::Token(
692-
Token::from_ast_ident(Ident::new(Symbol::intern(&concatenated), visited_span())),
712+
Token::from_ast_ident(Ident::new(Symbol::intern(&concatenated), concatenated_span)),
693713
Spacing::Alone,
694714
));
695715
}

tests/ui/macros/macro-metavar-expr-concat/allowed-operations.rs

+14
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ macro_rules! without_dollar_sign_is_an_ident {
3737
};
3838
}
3939

40+
macro_rules! literals {
41+
($ident:ident) => {{
42+
let ${concat(_a, 'b')}: () = ();
43+
let ${concat(_a, 1)}: () = ();
44+
let ${concat(_a, "b")}: () = ();
45+
46+
let ${concat($ident, 'b')}: () = ();
47+
let ${concat($ident, 1)}: () = ();
48+
let ${concat($ident, "b")}: () = ();
49+
}};
50+
}
51+
4052
fn main() {
4153
create_things!(behold);
4254
behold_separated_idents_in_a_fn();
@@ -55,4 +67,6 @@ fn main() {
5567
without_dollar_sign_is_an_ident!(_123);
5668
assert_eq!(VARident, 1);
5769
assert_eq!(VAR_123, 2);
70+
71+
literals!(_hello);
5872
}

tests/ui/macros/macro-metavar-expr-concat/syntax-errors.rs

+34-6
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@ macro_rules! wrong_concat_declarations {
1111
${concat(aaaa,)}
1212
//~^ ERROR expected identifier
1313

14-
${concat(aaaa, 1)}
15-
//~^ ERROR expected identifier
16-
1714
${concat(_, aaaa)}
1815

1916
${concat(aaaa aaaa)}
@@ -30,9 +27,6 @@ macro_rules! wrong_concat_declarations {
3027

3128
${concat($ex, aaaa,)}
3229
//~^ ERROR expected identifier
33-
34-
${concat($ex, aaaa, 123)}
35-
//~^ ERROR expected identifier
3630
};
3731
}
3832

@@ -43,8 +37,42 @@ macro_rules! dollar_sign_without_referenced_ident {
4337
};
4438
}
4539

40+
macro_rules! starting_number {
41+
($ident:ident) => {{
42+
let ${concat(1, $ident)}: () = ();
43+
//~^ ERROR `${concat(..)}` is not generating a valid identifier
44+
}};
45+
}
46+
47+
macro_rules! starting_unicode {
48+
($ident:ident) => {{
49+
let ${concat("\u{342}", $ident)}: () = ();
50+
//~^ ERROR `${concat(..)}` is not generating a valid identifier
51+
}};
52+
}
53+
54+
macro_rules! ending_unicode {
55+
($ident:ident) => {{
56+
let ${concat($ident, "\u{342}")}: () = ();
57+
//~^ ERROR `${concat(..)}` is not generating a valid identifier
58+
}};
59+
}
60+
61+
macro_rules! empty {
62+
() => {{
63+
let ${concat("", "")}: () = ();
64+
//~^ ERROR `${concat(..)}` is generating an empty identifier
65+
}};
66+
}
67+
68+
4669
fn main() {
4770
wrong_concat_declarations!(1);
4871

4972
dollar_sign_without_referenced_ident!(VAR);
73+
74+
starting_number!(_abc);
75+
starting_unicode!(_abc);
76+
ending_unicode!(_abc);
77+
empty!();
5078
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: expected identifier
1+
error: expected identifier or literal
22
--> $DIR/syntax-errors.rs:5:10
33
|
44
LL | ${concat()}
@@ -10,59 +10,91 @@ error: `concat` must have at least two elements
1010
LL | ${concat(aaaa)}
1111
| ^^^^^^
1212

13-
error: expected identifier
13+
error: expected identifier or literal
1414
--> $DIR/syntax-errors.rs:11:10
1515
|
1616
LL | ${concat(aaaa,)}
1717
| ^^^^^^^^^^^^^^^
1818

19-
error: expected identifier, found `1`
20-
--> $DIR/syntax-errors.rs:14:24
21-
|
22-
LL | ${concat(aaaa, 1)}
23-
| ^ help: try removing `1`
24-
2519
error: expected comma
26-
--> $DIR/syntax-errors.rs:19:10
20+
--> $DIR/syntax-errors.rs:16:10
2721
|
2822
LL | ${concat(aaaa aaaa)}
2923
| ^^^^^^^^^^^^^^^^^^^
3024

3125
error: `concat` must have at least two elements
32-
--> $DIR/syntax-errors.rs:22:11
26+
--> $DIR/syntax-errors.rs:19:11
3327
|
3428
LL | ${concat($ex)}
3529
| ^^^^^^
3630

3731
error: expected comma
38-
--> $DIR/syntax-errors.rs:28:10
32+
--> $DIR/syntax-errors.rs:25:10
3933
|
4034
LL | ${concat($ex, aaaa 123)}
4135
| ^^^^^^^^^^^^^^^^^^^^^^^
4236

43-
error: expected identifier
44-
--> $DIR/syntax-errors.rs:31:10
37+
error: expected identifier or literal
38+
--> $DIR/syntax-errors.rs:28:10
4539
|
4640
LL | ${concat($ex, aaaa,)}
4741
| ^^^^^^^^^^^^^^^^^^^^
4842

49-
error: expected identifier, found `123`
50-
--> $DIR/syntax-errors.rs:34:29
51-
|
52-
LL | ${concat($ex, aaaa, 123)}
53-
| ^^^ help: try removing `123`
54-
5543
error: `${concat(..)}` currently only accepts identifiers or meta-variables as parameters
56-
--> $DIR/syntax-errors.rs:25:19
44+
--> $DIR/syntax-errors.rs:22:19
5745
|
5846
LL | ${concat($ex, aaaa)}
5947
| ^^
6048

6149
error: variable `foo` is not recognized in meta-variable expression
62-
--> $DIR/syntax-errors.rs:41:30
50+
--> $DIR/syntax-errors.rs:35:30
6351
|
6452
LL | const ${concat(FOO, $foo)}: i32 = 2;
6553
| ^^^
6654

67-
error: aborting due to 11 previous errors
55+
error: `${concat(..)}` is not generating a valid identifier
56+
--> $DIR/syntax-errors.rs:42:14
57+
|
58+
LL | let ${concat(1, $ident)}: () = ();
59+
| ^^^^^^^^^^^^^^^^^^^
60+
...
61+
LL | starting_number!(_abc);
62+
| ---------------------- in this macro invocation
63+
|
64+
= note: this error originates in the macro `starting_number` (in Nightly builds, run with -Z macro-backtrace for more info)
65+
66+
error: `${concat(..)}` is not generating a valid identifier
67+
--> $DIR/syntax-errors.rs:49:14
68+
|
69+
LL | let ${concat("\u{342}", $ident)}: () = ();
70+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
71+
...
72+
LL | starting_unicode!(_abc);
73+
| ----------------------- in this macro invocation
74+
|
75+
= note: this error originates in the macro `starting_unicode` (in Nightly builds, run with -Z macro-backtrace for more info)
76+
77+
error: `${concat(..)}` is not generating a valid identifier
78+
--> $DIR/syntax-errors.rs:56:14
79+
|
80+
LL | let ${concat($ident, "\u{342}")}: () = ();
81+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
82+
...
83+
LL | ending_unicode!(_abc);
84+
| --------------------- in this macro invocation
85+
|
86+
= note: this error originates in the macro `ending_unicode` (in Nightly builds, run with -Z macro-backtrace for more info)
87+
88+
error: `${concat(..)}` is generating an empty identifier
89+
--> $DIR/syntax-errors.rs:63:14
90+
|
91+
LL | let ${concat("", "")}: () = ();
92+
| ^^^^^^^^^^^^^^^^
93+
...
94+
LL | empty!();
95+
| -------- in this macro invocation
96+
|
97+
= note: this error originates in the macro `empty` (in Nightly builds, run with -Z macro-backtrace for more info)
98+
99+
error: aborting due to 13 previous errors
68100

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ error: unrecognized meta-variable expression
190190
LL | ( $( $i:ident ),* ) => { ${ aaaaaaaaaaaaaa(i) } };
191191
| ^^^^^^^^^^^^^^ help: supported expressions are count, ignore, index and len
192192

193-
error: expected identifier
193+
error: expected identifier or literal
194194
--> $DIR/syntax-errors.rs:118:33
195195
|
196196
LL | ( $( $i:ident ),* ) => { ${ {} } };

0 commit comments

Comments
 (0)