@@ -8,9 +8,14 @@ use rustc_session::parse::ParseSess;
88use rustc_span:: symbol:: Ident ;
99use 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 ) ]
1315pub ( 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 ...)}`
77117fn check_trailing_token < ' psess > (
78118 iter : & mut RefTokenTreeCursor < ' _ > ,
@@ -138,26 +178,30 @@ fn parse_depth<'psess>(
138178fn 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.
174229fn eat_dollar < ' psess > (
175230 iter : & mut RefTokenTreeCursor < ' _ > ,
0 commit comments