@@ -9,8 +9,11 @@ use rustc_span::symbol::Ident;
99use rustc_span:: Span ;
1010
1111/// A meta-variable expression, for expansions based on properties of meta-variables.
12- #[ derive( Debug , Clone , PartialEq , Encodable , Decodable ) ]
12+ #[ derive( Debug , PartialEq , Encodable , Decodable ) ]
1313pub ( crate ) enum MetaVarExpr {
14+ /// Unification of two or more identifiers.
15+ Concat ( Box < [ MetaVarExprConcatElem ] > ) ,
16+
1417 /// The number of repetitions of an identifier.
1518 Count ( Ident , usize ) ,
1619
@@ -42,6 +45,31 @@ impl MetaVarExpr {
4245 check_trailing_token ( & mut tts, psess) ?;
4346 let mut iter = args. trees ( ) ;
4447 let rslt = match ident. as_str ( ) {
48+ "concat" => {
49+ let mut result = Vec :: new ( ) ;
50+ loop {
51+ let is_var = try_eat_dollar ( & mut iter) ;
52+ let element_ident = parse_ident ( & mut iter, psess, outer_span) ?;
53+ let element = if is_var {
54+ MetaVarExprConcatElem :: Var ( element_ident)
55+ } else {
56+ MetaVarExprConcatElem :: Ident ( element_ident)
57+ } ;
58+ result. push ( element) ;
59+ if iter. look_ahead ( 0 ) . is_none ( ) {
60+ break ;
61+ }
62+ if !try_eat_comma ( & mut iter) {
63+ return Err ( psess. dcx . struct_span_err ( outer_span, "expected comma" ) ) ;
64+ }
65+ }
66+ if result. len ( ) < 2 {
67+ return Err ( psess
68+ . dcx
69+ . struct_span_err ( ident. span , "`concat` must have at least two elements" ) ) ;
70+ }
71+ MetaVarExpr :: Concat ( result. into ( ) )
72+ }
4573 "count" => parse_count ( & mut iter, psess, ident. span ) ?,
4674 "ignore" => {
4775 eat_dollar ( & mut iter, psess, ident. span ) ?;
@@ -68,11 +96,21 @@ impl MetaVarExpr {
6896 pub ( crate ) fn ident ( & self ) -> Option < Ident > {
6997 match * self {
7098 MetaVarExpr :: Count ( ident, _) | MetaVarExpr :: Ignore ( ident) => Some ( ident) ,
71- MetaVarExpr :: Index ( ..) | MetaVarExpr :: Len ( ..) => None ,
99+ MetaVarExpr :: Concat { .. } | MetaVarExpr :: Index ( ..) | MetaVarExpr :: Len ( ..) => None ,
72100 }
73101 }
74102}
75103
104+ #[ derive( Debug , Decodable , Encodable , PartialEq ) ]
105+ pub ( crate ) enum MetaVarExprConcatElem {
106+ /// There is NO preceding dollar sign, which means that this identifier should be interpreted
107+ /// as a literal.
108+ Ident ( Ident ) ,
109+ /// There is a preceding dollar sign, which means that this identifier should be expanded
110+ /// and interpreted as a variable.
111+ Var ( Ident ) ,
112+ }
113+
76114// Checks if there are any remaining tokens. For example, `${ignore(ident ... a b c ...)}`
77115fn check_trailing_token < ' psess > (
78116 iter : & mut RefTokenTreeCursor < ' _ > ,
@@ -138,26 +176,27 @@ fn parse_depth<'psess>(
138176fn parse_ident < ' psess > (
139177 iter : & mut RefTokenTreeCursor < ' _ > ,
140178 psess : & ' psess ParseSess ,
141- span : Span ,
179+ fallback_span : Span ,
142180) -> 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) ;
148- }
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) ;
181+ let Some ( tt) = iter. next ( ) else {
182+ return Err ( psess. dcx . struct_span_err ( fallback_span, "expected identifier" ) ) ;
183+ } ;
184+ let TokenTree :: Token ( token, _) = tt else {
185+ return Err ( psess. dcx . struct_span_err ( tt. span ( ) , "expected identifier" ) ) ;
186+ } ;
187+ if let Some ( ( elem, IdentIsRaw :: No ) ) = token. ident ( ) {
188+ return Ok ( elem) ;
159189 }
160- Err ( psess. dcx . struct_span_err ( span, "expected identifier" ) )
190+ let token_str = pprust:: token_to_string ( token) ;
191+ let mut err =
192+ psess. dcx . struct_span_err ( token. span , format ! ( "expected identifier, found `{token_str}`" ) ) ;
193+ err. span_suggestion (
194+ token. span ,
195+ format ! ( "try removing `{token_str}`" ) ,
196+ "" ,
197+ Applicability :: MaybeIncorrect ,
198+ ) ;
199+ Err ( err)
161200}
162201
163202/// Tries to move the iterator forward returning `true` if there is a comma. If not, then the
@@ -170,6 +209,17 @@ fn try_eat_comma(iter: &mut RefTokenTreeCursor<'_>) -> bool {
170209 false
171210}
172211
212+ /// Tries to move the iterator forward returning `true` if there is a dollar sign. If not, then the
213+ /// iterator is not modified and the result is `false`.
214+ fn try_eat_dollar ( iter : & mut RefTokenTreeCursor < ' _ > ) -> bool {
215+ if let Some ( TokenTree :: Token ( token:: Token { kind : token:: Dollar , .. } , _) ) = iter. look_ahead ( 0 )
216+ {
217+ let _ = iter. next ( ) ;
218+ return true ;
219+ }
220+ false
221+ }
222+
173223/// Expects that the next item is a dollar sign.
174224fn eat_dollar < ' psess > (
175225 iter : & mut RefTokenTreeCursor < ' _ > ,
0 commit comments