1- use rustc_ast:: token:: { self , Delimiter , IdentIsRaw , Lit , Token , TokenKind } ;
1+ use rustc_ast:: token:: { self , Delimiter , IdentIsRaw , Token , TokenKind } ;
22use rustc_ast:: tokenstream:: { TokenStream , TokenStreamIter , TokenTree } ;
3- use rustc_ast:: { LitIntType , LitKind } ;
3+ use rustc_ast:: { self as ast , LitIntType , LitKind } ;
44use rustc_ast_pretty:: pprust;
5- use rustc_errors:: { Applicability , PResult } ;
5+ use rustc_errors:: PResult ;
6+ use rustc_lexer:: is_id_continue;
67use rustc_macros:: { Decodable , Encodable } ;
8+ use rustc_session:: errors:: create_lit_error;
79use rustc_session:: parse:: ParseSess ;
810use rustc_span:: { Ident , Span , Symbol } ;
911
10- use crate :: errors:: { self , MveExpectedIdentContext } ;
12+ use crate :: errors:: { self , MveConcatInvalidReason , MveExpectedIdentContext } ;
1113
1214pub ( crate ) const RAW_IDENT_ERR : & str = "`${concat(..)}` currently does not support raw identifiers" ;
13- pub ( crate ) const UNSUPPORTED_CONCAT_ELEM_ERR : & str = "expected identifier or string literal" ;
15+ pub ( crate ) const VALID_EXPR_CONCAT_TYPES : & str =
16+ "metavariables, identifiers, string literals, and integer literals" ;
1417
1518/// List of the below list for diagnostics.
1619const VALID_METAVAR_EXPR_NAMES : & str = "`count`, `ignore`, `index`, `len`, and `concat`" ;
@@ -165,7 +168,7 @@ fn iter_span(iter: &TokenStreamIter<'_>) -> Option<Span> {
165168pub ( crate ) enum MetaVarExprConcatElem {
166169 /// Identifier WITHOUT a preceding dollar sign, which means that this identifier should be
167170 /// interpreted as a literal.
168- Ident ( Ident ) ,
171+ Ident ( String ) ,
169172 /// For example, a number or a string.
170173 Literal ( Symbol ) ,
171174 /// Identifier WITH a preceding dollar sign, which means that this identifier should be
@@ -181,30 +184,92 @@ fn parse_concat<'psess>(
181184 expr_ident_span : Span ,
182185) -> PResult < ' psess , MetaVarExpr > {
183186 let mut result = Vec :: new ( ) ;
187+ let dcx = psess. dcx ( ) ;
184188 loop {
185- let is_var = try_eat_dollar ( iter) ;
186- let token = parse_token ( iter, psess, outer_span) ?;
187- let element = if is_var {
188- MetaVarExprConcatElem :: Var ( parse_ident_from_token ( psess, token) ?)
189- } else if let TokenKind :: Literal ( Lit { kind : token:: LitKind :: Str , symbol, suffix : None } ) =
190- token. kind
191- {
192- MetaVarExprConcatElem :: Literal ( symbol)
193- } else {
194- match parse_ident_from_token ( psess, token) {
195- Err ( err) => {
196- err. cancel ( ) ;
197- return Err ( psess
198- . dcx ( )
199- . struct_span_err ( token. span , UNSUPPORTED_CONCAT_ELEM_ERR ) ) ;
189+ let dollar = try_eat_dollar ( iter) ;
190+ let Some ( tt) = iter. next ( ) else {
191+ // May be hit only with the first iteration (peek is otherwise checked at the end).
192+ break ;
193+ } ;
194+
195+ let make_err = |reason| {
196+ let err = errors:: MveConcatInvalid {
197+ span : tt. span ( ) ,
198+ ident_span : expr_ident_span,
199+ reason,
200+ valid : VALID_EXPR_CONCAT_TYPES ,
201+ } ;
202+ Err ( dcx. create_err ( err) )
203+ } ;
204+
205+ let token = match tt {
206+ TokenTree :: Token ( token, _) => token,
207+ TokenTree :: Delimited ( ..) => {
208+ return make_err ( MveConcatInvalidReason :: UnexpectedGroup ) ;
209+ }
210+ } ;
211+
212+ let element = if let Some ( dollar) = dollar {
213+ // Expecting a metavar
214+ let Some ( ( ident, _) ) = token. ident ( ) else {
215+ return make_err ( MveConcatInvalidReason :: ExpectedMetavarIdent {
216+ found : pprust:: token_to_string ( token) . into_owned ( ) ,
217+ dollar,
218+ } ) ;
219+ } ;
220+
221+ // Variables get passed untouched
222+ MetaVarExprConcatElem :: Var ( ident)
223+ } else if let TokenKind :: Literal ( lit) = token. kind {
224+ // Preprocess with `from_token_lit` to handle unescaping, float / int literal suffix
225+ // stripping.
226+ //
227+ // For consistent user experience, please keep this in sync with the handling of
228+ // literals in `rustc_builtin_macros::concat`!
229+ let s = match ast:: LitKind :: from_token_lit ( lit. clone ( ) ) {
230+ Ok ( ast:: LitKind :: Str ( s, _) ) => s. to_string ( ) ,
231+ Ok ( ast:: LitKind :: Float ( ..) ) => {
232+ return make_err ( MveConcatInvalidReason :: FloatLit ) ;
233+ }
234+ Ok ( ast:: LitKind :: Char ( c) ) => c. to_string ( ) ,
235+ Ok ( ast:: LitKind :: Int ( i, _) ) => i. to_string ( ) ,
236+ Ok ( ast:: LitKind :: Bool ( b) ) => b. to_string ( ) ,
237+ Ok ( ast:: LitKind :: CStr ( ..) ) => return make_err ( MveConcatInvalidReason :: CStrLit ) ,
238+ Ok ( ast:: LitKind :: Byte ( ..) | ast:: LitKind :: ByteStr ( ..) ) => {
239+ return make_err ( MveConcatInvalidReason :: ByteStrLit ) ;
200240 }
201- Ok ( elem) => MetaVarExprConcatElem :: Ident ( elem) ,
241+ Ok ( ast:: LitKind :: Err ( _guarantee) ) => {
242+ // REVIEW: a diagnostic was already emitted, should we just break?
243+ return make_err ( MveConcatInvalidReason :: InvalidLiteral ) ;
244+ }
245+ Err ( err) => return Err ( create_lit_error ( psess, err, lit, token. span ) ) ,
246+ } ;
247+
248+ if !s. chars ( ) . all ( |ch| is_id_continue ( ch) ) {
249+ // Check that all characters are valid in the middle of an identifier. This doesn't
250+ // guarantee that the final identifier is valid (we still need to check it later),
251+ // but it allows us to catch errors with specific arguments before expansion time;
252+ // for example, string literal "foo.bar" gets flagged before the macro is invoked.
253+ return make_err ( MveConcatInvalidReason :: InvalidIdent ) ;
254+ }
255+
256+ MetaVarExprConcatElem :: Ident ( s)
257+ } else if let Some ( ( elem, is_raw) ) = token. ident ( ) {
258+ if is_raw == IdentIsRaw :: Yes {
259+ return make_err ( MveConcatInvalidReason :: RawIdentifier ) ;
202260 }
261+ MetaVarExprConcatElem :: Ident ( elem. as_str ( ) . to_string ( ) )
262+ } else {
263+ return make_err ( MveConcatInvalidReason :: UnsupportedInput ) ;
203264 } ;
265+
204266 result. push ( element) ;
267+
205268 if iter. peek ( ) . is_none ( ) {
269+ // break before trying to eat the comma
206270 break ;
207271 }
272+
208273 if !try_eat_comma ( iter) {
209274 return Err ( psess. dcx ( ) . struct_span_err ( outer_span, "expected comma" ) ) ;
210275 }
@@ -290,43 +355,6 @@ fn parse_ident<'psess>(
290355 Ok ( elem)
291356}
292357
293- fn parse_ident_from_token < ' psess > (
294- psess : & ' psess ParseSess ,
295- token : & Token ,
296- ) -> PResult < ' psess , Ident > {
297- if let Some ( ( elem, is_raw) ) = token. ident ( ) {
298- if let IdentIsRaw :: Yes = is_raw {
299- return Err ( psess. dcx ( ) . struct_span_err ( elem. span , RAW_IDENT_ERR ) ) ;
300- }
301- return Ok ( elem) ;
302- }
303- let token_str = pprust:: token_to_string ( token) ;
304- let mut err = psess
305- . dcx ( )
306- . struct_span_err ( token. span , format ! ( "expected identifier, found `{token_str}`" ) ) ;
307- err. span_suggestion (
308- token. span ,
309- format ! ( "try removing `{token_str}`" ) ,
310- "" ,
311- Applicability :: MaybeIncorrect ,
312- ) ;
313- Err ( err)
314- }
315-
316- fn parse_token < ' psess , ' t > (
317- iter : & mut TokenStreamIter < ' t > ,
318- psess : & ' psess ParseSess ,
319- fallback_span : Span ,
320- ) -> PResult < ' psess , & ' t Token > {
321- let Some ( tt) = iter. next ( ) else {
322- return Err ( psess. dcx ( ) . struct_span_err ( fallback_span, UNSUPPORTED_CONCAT_ELEM_ERR ) ) ;
323- } ;
324- let TokenTree :: Token ( token, _) = tt else {
325- return Err ( psess. dcx ( ) . struct_span_err ( tt. span ( ) , UNSUPPORTED_CONCAT_ELEM_ERR ) ) ;
326- } ;
327- Ok ( token)
328- }
329-
330358/// Tries to move the iterator forward returning `true` if there is a comma. If not, then the
331359/// iterator is not modified and the result is `false`.
332360fn try_eat_comma ( iter : & mut TokenStreamIter < ' _ > ) -> bool {
@@ -337,14 +365,14 @@ fn try_eat_comma(iter: &mut TokenStreamIter<'_>) -> bool {
337365 false
338366}
339367
340- /// Tries to move the iterator forward returning `true ` if there is a dollar sign. If not, then the
341- /// iterator is not modified and the result is `false `.
342- fn try_eat_dollar ( iter : & mut TokenStreamIter < ' _ > ) -> bool {
343- if let Some ( TokenTree :: Token ( Token { kind : token:: Dollar , .. } , _) ) = iter. peek ( ) {
368+ /// Tries to move the iterator forward returning `Some(dollar_span) ` if there is a dollar sign. If
369+ /// not, then the iterator is not modified and the result is `None `.
370+ fn try_eat_dollar ( iter : & mut TokenStreamIter < ' _ > ) -> Option < Span > {
371+ if let Some ( TokenTree :: Token ( Token { kind : token:: Dollar , span } , _) ) = iter. peek ( ) {
344372 let _ = iter. next ( ) ;
345- return true ;
373+ return Some ( * span ) ;
346374 }
347- false
375+ None
348376}
349377
350378/// Expects that the next item is a dollar sign.
@@ -353,7 +381,7 @@ fn eat_dollar<'psess>(
353381 psess : & ' psess ParseSess ,
354382 span : Span ,
355383) -> PResult < ' psess , ( ) > {
356- if try_eat_dollar ( iter) {
384+ if try_eat_dollar ( iter) . is_some ( ) {
357385 return Ok ( ( ) ) ;
358386 }
359387 Err ( psess. dcx ( ) . struct_span_err (
0 commit comments