Skip to content

Commit f8e5660

Browse files
committed
Auto merge of rust-lang#118958 - c410-f3r:concat-again, r=petrochenkov
Add a new concat metavar expr Revival of rust-lang#111930 Giving it another try now that rust-lang#117050 was merged. With the new rules, meta-variable expressions must be referenced with a dollar sign (`$`) and this can cause misunderstands with `$concat`. ```rust macro_rules! foo { ( $bar:ident ) => { const ${concat(VAR, bar)}: i32 = 1; }; } // Will produce `VARbar` instead of `VAR_123` foo!(_123); ``` In other words, forgetting the dollar symbol can produce undesired outputs. cc rust-lang#29599 cc rust-lang#124225
2 parents 7ac6c2f + 4b82afb commit f8e5660

File tree

15 files changed

+551
-29
lines changed

15 files changed

+551
-29
lines changed

compiler/rustc_expand/src/mbe/metavar_expr.rs

+74-19
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,14 @@ use rustc_session::parse::ParseSess;
88
use rustc_span::symbol::Ident;
99
use rustc_span::Span;
1010

11+
pub(crate) const RAW_IDENT_ERR: &str = "`${concat(..)}` currently does not support raw identifiers";
12+
1113
/// A meta-variable expression, for expansions based on properties of meta-variables.
12-
#[derive(Debug, Clone, PartialEq, Encodable, Decodable)]
14+
#[derive(Debug, PartialEq, Encodable, Decodable)]
1315
pub(crate) enum MetaVarExpr {
16+
/// Unification of two or more identifiers.
17+
Concat(Box<[MetaVarExprConcatElem]>),
18+
1419
/// The number of repetitions of an identifier.
1520
Count(Ident, usize),
1621

@@ -42,6 +47,31 @@ impl MetaVarExpr {
4247
check_trailing_token(&mut tts, psess)?;
4348
let mut iter = args.trees();
4449
let rslt = match ident.as_str() {
50+
"concat" => {
51+
let mut result = Vec::new();
52+
loop {
53+
let is_var = try_eat_dollar(&mut iter);
54+
let element_ident = parse_ident(&mut iter, psess, outer_span)?;
55+
let element = if is_var {
56+
MetaVarExprConcatElem::Var(element_ident)
57+
} else {
58+
MetaVarExprConcatElem::Ident(element_ident)
59+
};
60+
result.push(element);
61+
if iter.look_ahead(0).is_none() {
62+
break;
63+
}
64+
if !try_eat_comma(&mut iter) {
65+
return Err(psess.dcx.struct_span_err(outer_span, "expected comma"));
66+
}
67+
}
68+
if result.len() < 2 {
69+
return Err(psess
70+
.dcx
71+
.struct_span_err(ident.span, "`concat` must have at least two elements"));
72+
}
73+
MetaVarExpr::Concat(result.into())
74+
}
4575
"count" => parse_count(&mut iter, psess, ident.span)?,
4676
"ignore" => {
4777
eat_dollar(&mut iter, psess, ident.span)?;
@@ -68,11 +98,21 @@ impl MetaVarExpr {
6898
pub(crate) fn ident(&self) -> Option<Ident> {
6999
match *self {
70100
MetaVarExpr::Count(ident, _) | MetaVarExpr::Ignore(ident) => Some(ident),
71-
MetaVarExpr::Index(..) | MetaVarExpr::Len(..) => None,
101+
MetaVarExpr::Concat { .. } | MetaVarExpr::Index(..) | MetaVarExpr::Len(..) => None,
72102
}
73103
}
74104
}
75105

106+
#[derive(Debug, Decodable, Encodable, PartialEq)]
107+
pub(crate) enum MetaVarExprConcatElem {
108+
/// There is NO preceding dollar sign, which means that this identifier should be interpreted
109+
/// as a literal.
110+
Ident(Ident),
111+
/// There is a preceding dollar sign, which means that this identifier should be expanded
112+
/// and interpreted as a variable.
113+
Var(Ident),
114+
}
115+
76116
// Checks if there are any remaining tokens. For example, `${ignore(ident ... a b c ...)}`
77117
fn check_trailing_token<'psess>(
78118
iter: &mut RefTokenTreeCursor<'_>,
@@ -138,26 +178,30 @@ fn parse_depth<'psess>(
138178
fn parse_ident<'psess>(
139179
iter: &mut RefTokenTreeCursor<'_>,
140180
psess: &'psess ParseSess,
141-
span: Span,
181+
fallback_span: Span,
142182
) -> PResult<'psess, Ident> {
143-
if let Some(tt) = iter.next()
144-
&& let TokenTree::Token(token, _) = tt
145-
{
146-
if let Some((elem, IdentIsRaw::No)) = token.ident() {
147-
return Ok(elem);
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+
};
189+
if let Some((elem, is_raw)) = token.ident() {
190+
if let IdentIsRaw::Yes = is_raw {
191+
return Err(psess.dcx.struct_span_err(elem.span, RAW_IDENT_ERR));
148192
}
149-
let token_str = pprust::token_to_string(token);
150-
let mut err =
151-
psess.dcx.struct_span_err(span, format!("expected identifier, found `{}`", &token_str));
152-
err.span_suggestion(
153-
token.span,
154-
format!("try removing `{}`", &token_str),
155-
"",
156-
Applicability::MaybeIncorrect,
157-
);
158-
return Err(err);
193+
return Ok(elem);
159194
}
160-
Err(psess.dcx.struct_span_err(span, "expected identifier"))
195+
let token_str = pprust::token_to_string(token);
196+
let mut err =
197+
psess.dcx.struct_span_err(token.span, format!("expected identifier, found `{token_str}`"));
198+
err.span_suggestion(
199+
token.span,
200+
format!("try removing `{token_str}`"),
201+
"",
202+
Applicability::MaybeIncorrect,
203+
);
204+
Err(err)
161205
}
162206

163207
/// Tries to move the iterator forward returning `true` if there is a comma. If not, then the
@@ -170,6 +214,17 @@ fn try_eat_comma(iter: &mut RefTokenTreeCursor<'_>) -> bool {
170214
false
171215
}
172216

217+
/// Tries to move the iterator forward returning `true` if there is a dollar sign. If not, then the
218+
/// iterator is not modified and the result is `false`.
219+
fn try_eat_dollar(iter: &mut RefTokenTreeCursor<'_>) -> bool {
220+
if let Some(TokenTree::Token(token::Token { kind: token::Dollar, .. }, _)) = iter.look_ahead(0)
221+
{
222+
let _ = iter.next();
223+
return true;
224+
}
225+
false
226+
}
227+
173228
/// Expects that the next item is a dollar sign.
174229
fn eat_dollar<'psess>(
175230
iter: &mut RefTokenTreeCursor<'_>,

compiler/rustc_expand/src/mbe/quoted.rs

+20-5
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,13 @@ fn maybe_emit_macro_metavar_expr_feature(features: &Features, sess: &Session, sp
155155
}
156156
}
157157

158+
fn maybe_emit_macro_metavar_expr_concat_feature(features: &Features, sess: &Session, span: Span) {
159+
if !features.macro_metavar_expr_concat {
160+
let msg = "the `concat` meta-variable expression is unstable";
161+
feature_err(sess, sym::macro_metavar_expr_concat, span, msg).emit();
162+
}
163+
}
164+
158165
/// Takes a `tokenstream::TokenTree` and returns a `self::TokenTree`. Specifically, this takes a
159166
/// generic `TokenTree`, such as is used in the rest of the compiler, and returns a `TokenTree`
160167
/// for use in parsing a macro.
@@ -217,11 +224,19 @@ fn parse_tree<'a>(
217224
return TokenTree::token(token::Dollar, dollar_span);
218225
}
219226
Ok(elem) => {
220-
maybe_emit_macro_metavar_expr_feature(
221-
features,
222-
sess,
223-
delim_span.entire(),
224-
);
227+
if let MetaVarExpr::Concat(_) = elem {
228+
maybe_emit_macro_metavar_expr_concat_feature(
229+
features,
230+
sess,
231+
delim_span.entire(),
232+
);
233+
} else {
234+
maybe_emit_macro_metavar_expr_feature(
235+
features,
236+
sess,
237+
delim_span.entire(),
238+
);
239+
}
225240
return TokenTree::MetaVarExpr(delim_span, elem);
226241
}
227242
}

compiler/rustc_expand/src/mbe/transcribe.rs

+51-3
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,19 @@ use crate::errors::{
33
NoSyntaxVarsExprRepeat, VarStillRepeating,
44
};
55
use crate::mbe::macro_parser::{NamedMatch, NamedMatch::*};
6+
use crate::mbe::metavar_expr::{MetaVarExprConcatElem, RAW_IDENT_ERR};
67
use crate::mbe::{self, KleeneOp, MetaVarExpr};
78
use rustc_ast::mut_visit::{self, MutVisitor};
9+
use rustc_ast::token::IdentIsRaw;
810
use rustc_ast::token::{self, Delimiter, Token, TokenKind};
911
use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree};
1012
use rustc_data_structures::fx::FxHashMap;
1113
use rustc_errors::{pluralize, Diag, DiagCtxt, PResult};
1214
use rustc_parse::parser::ParseNtResult;
15+
use rustc_session::parse::ParseSess;
1316
use rustc_span::hygiene::{LocalExpnId, Transparency};
1417
use rustc_span::symbol::{sym, Ident, MacroRulesNormalizedIdent};
15-
use rustc_span::{with_metavar_spans, Span, SyntaxContext};
16-
17-
use rustc_session::parse::ParseSess;
18+
use rustc_span::{with_metavar_spans, Span, Symbol, SyntaxContext};
1819
use smallvec::{smallvec, SmallVec};
1920
use std::mem;
2021

@@ -675,6 +676,23 @@ fn transcribe_metavar_expr<'a>(
675676
span
676677
};
677678
match *expr {
679+
MetaVarExpr::Concat(ref elements) => {
680+
let mut concatenated = String::new();
681+
for element in elements.into_iter() {
682+
let string = match element {
683+
MetaVarExprConcatElem::Ident(ident) => ident.to_string(),
684+
MetaVarExprConcatElem::Var(ident) => extract_ident(dcx, *ident, interp)?,
685+
};
686+
concatenated.push_str(&string);
687+
}
688+
// The current implementation marks the span as coming from the macro regardless of
689+
// contexts of the concatenated identifiers but this behavior may change in the
690+
// future.
691+
result.push(TokenTree::Token(
692+
Token::from_ast_ident(Ident::new(Symbol::intern(&concatenated), visited_span())),
693+
Spacing::Alone,
694+
));
695+
}
678696
MetaVarExpr::Count(original_ident, depth) => {
679697
let matched = matched_from_ident(dcx, original_ident, interp)?;
680698
let count = count_repetitions(dcx, depth, matched, repeats, sp)?;
@@ -709,3 +727,33 @@ fn transcribe_metavar_expr<'a>(
709727
}
710728
Ok(())
711729
}
730+
731+
/// Extracts an identifier that can be originated from a `$var:ident` variable or from a token tree.
732+
fn extract_ident<'a>(
733+
dcx: &'a DiagCtxt,
734+
ident: Ident,
735+
interp: &FxHashMap<MacroRulesNormalizedIdent, NamedMatch>,
736+
) -> PResult<'a, String> {
737+
if let NamedMatch::MatchedSingle(pnr) = matched_from_ident(dcx, ident, interp)? {
738+
if let ParseNtResult::Ident(nt_ident, is_raw) = pnr {
739+
if let IdentIsRaw::Yes = is_raw {
740+
return Err(dcx.struct_span_err(ident.span, RAW_IDENT_ERR));
741+
}
742+
return Ok(nt_ident.to_string());
743+
}
744+
if let ParseNtResult::Tt(TokenTree::Token(
745+
Token { kind: TokenKind::Ident(token_ident, is_raw), .. },
746+
_,
747+
)) = pnr
748+
{
749+
if let IdentIsRaw::Yes = is_raw {
750+
return Err(dcx.struct_span_err(ident.span, RAW_IDENT_ERR));
751+
}
752+
return Ok(token_ident.to_string());
753+
}
754+
}
755+
Err(dcx.struct_span_err(
756+
ident.span,
757+
"`${concat(..)}` currently only accepts identifiers or meta-variables as parameters",
758+
))
759+
}

compiler/rustc_feature/src/unstable.rs

+2
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,8 @@ declare_features! (
516516
(unstable, lint_reasons, "1.31.0", Some(54503)),
517517
/// Give access to additional metadata about declarative macro meta-variables.
518518
(unstable, macro_metavar_expr, "1.61.0", Some(83527)),
519+
/// Provides a way to concatenate identifiers using metavariable expressions.
520+
(unstable, macro_metavar_expr_concat, "CURRENT_RUSTC_VERSION", Some(124225)),
519521
/// Allows `#[marker]` on certain traits allowing overlapping implementations.
520522
(unstable, marker_trait_attr, "1.30.0", Some(29864)),
521523
/// Allows exhaustive pattern matching on types that contain uninhabited types in cases that are

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1118,6 +1118,7 @@ symbols! {
11181118
macro_lifetime_matcher,
11191119
macro_literal_matcher,
11201120
macro_metavar_expr,
1121+
macro_metavar_expr_concat,
11211122
macro_reexport,
11221123
macro_use,
11231124
macro_vis_matcher,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
macro_rules! join {
2+
($lhs:ident, $rhs:ident) => {
3+
let ${concat($lhs, $rhs)}: () = ();
4+
//~^ ERROR the `concat` meta-variable expression is unstable
5+
};
6+
}
7+
8+
fn main() {
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
error[E0658]: the `concat` meta-variable expression is unstable
2+
--> $DIR/feature-gate-macro-metavar-expr-concat.rs:3:14
3+
|
4+
LL | let ${concat($lhs, $rhs)}: () = ();
5+
| ^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: see issue #124225 <https://github.com/rust-lang/rust/issues/124225> for more information
8+
= help: add `#![feature(macro_metavar_expr_concat)]` to the crate attributes to enable
9+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
10+
11+
error: aborting due to 1 previous error
12+
13+
For more information about this error, try `rustc --explain E0658`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//@ run-pass
2+
3+
#![allow(dead_code, non_camel_case_types, non_upper_case_globals)]
4+
#![feature(macro_metavar_expr_concat)]
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! valid_tts {
28+
($_0:tt, $_1:tt) => {
29+
const ${concat($_0, $_1)}: i32 = 1;
30+
}
31+
}
32+
33+
macro_rules! without_dollar_sign_is_an_ident {
34+
($ident:ident) => {
35+
const ${concat(VAR, ident)}: i32 = 1;
36+
const ${concat(VAR, $ident)}: i32 = 2;
37+
};
38+
}
39+
40+
fn main() {
41+
create_things!(behold);
42+
behold_separated_idents_in_a_fn();
43+
let _ = behold_separated_idents_in_a_module::FOO;
44+
let _ = behold_separated_idents_in_a_struct {
45+
foo: 1,
46+
behold_separated_idents_in_a_field: 2,
47+
};
48+
49+
many_idents!(A, C);
50+
assert_eq!(ABCD, 1);
51+
52+
valid_tts!(X, YZ);
53+
assert_eq!(XYZ, 1);
54+
55+
without_dollar_sign_is_an_ident!(_123);
56+
assert_eq!(VARident, 1);
57+
assert_eq!(VAR_123, 2);
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#![feature(macro_metavar_expr_concat)]
2+
3+
macro_rules! join {
4+
($lhs:ident, $rhs:ident) => {
5+
${concat($lhs, $rhs)}
6+
//~^ ERROR 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/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`.

0 commit comments

Comments
 (0)