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/// Argument specification for a metavariable expression
1619#[ derive( Clone , Copy ) ]
@@ -190,7 +193,7 @@ fn iter_span(iter: &TokenStreamIter<'_>) -> Option<Span> {
190193pub ( crate ) enum MetaVarExprConcatElem {
191194 /// Identifier WITHOUT a preceding dollar sign, which means that this identifier should be
192195 /// interpreted as a literal.
193- Ident ( Ident ) ,
196+ Ident ( String ) ,
194197 /// For example, a number or a string.
195198 Literal ( Symbol ) ,
196199 /// Identifier WITH a preceding dollar sign, which means that this identifier should be
@@ -206,30 +209,92 @@ fn parse_concat<'psess>(
206209 expr_ident_span : Span ,
207210) -> PResult < ' psess , MetaVarExpr > {
208211 let mut result = Vec :: new ( ) ;
212+ let dcx = psess. dcx ( ) ;
209213 loop {
210- let is_var = try_eat_dollar ( iter) ;
211- let token = parse_token ( iter, psess, outer_span) ?;
212- let element = if is_var {
213- MetaVarExprConcatElem :: Var ( parse_ident_from_token ( psess, token) ?)
214- } else if let TokenKind :: Literal ( Lit { kind : token:: LitKind :: Str , symbol, suffix : None } ) =
215- token. kind
216- {
217- MetaVarExprConcatElem :: Literal ( symbol)
218- } else {
219- match parse_ident_from_token ( psess, token) {
220- Err ( err) => {
221- err. cancel ( ) ;
222- return Err ( psess
223- . dcx ( )
224- . struct_span_err ( token. span , UNSUPPORTED_CONCAT_ELEM_ERR ) ) ;
214+ let dollar = try_eat_dollar ( iter) ;
215+ let Some ( tt) = iter. next ( ) else {
216+ // May be hit only with the first iteration (peek is otherwise checked at the end).
217+ break ;
218+ } ;
219+
220+ let make_err = |reason| {
221+ let err = errors:: MveConcatInvalid {
222+ span : tt. span ( ) ,
223+ ident_span : expr_ident_span,
224+ reason,
225+ valid : VALID_EXPR_CONCAT_TYPES ,
226+ } ;
227+ Err ( dcx. create_err ( err) )
228+ } ;
229+
230+ let token = match tt {
231+ TokenTree :: Token ( token, _) => token,
232+ TokenTree :: Delimited ( ..) => {
233+ return make_err ( MveConcatInvalidReason :: UnexpectedGroup ) ;
234+ }
235+ } ;
236+
237+ let element = if let Some ( dollar) = dollar {
238+ // Expecting a metavar
239+ let Some ( ( ident, _) ) = token. ident ( ) else {
240+ return make_err ( MveConcatInvalidReason :: ExpectedMetavarIdent {
241+ found : pprust:: token_to_string ( token) . into_owned ( ) ,
242+ dollar,
243+ } ) ;
244+ } ;
245+
246+ // Variables get passed untouched
247+ MetaVarExprConcatElem :: Var ( ident)
248+ } else if let TokenKind :: Literal ( lit) = token. kind {
249+ // Preprocess with `from_token_lit` to handle unescaping, float / int literal suffix
250+ // stripping.
251+ //
252+ // For consistent user experience, please keep this in sync with the handling of
253+ // literals in `rustc_builtin_macros::concat`!
254+ let s = match ast:: LitKind :: from_token_lit ( lit. clone ( ) ) {
255+ Ok ( ast:: LitKind :: Str ( s, _) ) => s. to_string ( ) ,
256+ Ok ( ast:: LitKind :: Float ( ..) ) => {
257+ return make_err ( MveConcatInvalidReason :: FloatLit ) ;
258+ }
259+ Ok ( ast:: LitKind :: Char ( c) ) => c. to_string ( ) ,
260+ Ok ( ast:: LitKind :: Int ( i, _) ) => i. to_string ( ) ,
261+ Ok ( ast:: LitKind :: Bool ( b) ) => b. to_string ( ) ,
262+ Ok ( ast:: LitKind :: CStr ( ..) ) => return make_err ( MveConcatInvalidReason :: CStrLit ) ,
263+ Ok ( ast:: LitKind :: Byte ( ..) | ast:: LitKind :: ByteStr ( ..) ) => {
264+ return make_err ( MveConcatInvalidReason :: ByteStrLit ) ;
225265 }
226- Ok ( elem) => MetaVarExprConcatElem :: Ident ( elem) ,
266+ Ok ( ast:: LitKind :: Err ( _guarantee) ) => {
267+ // REVIEW: a diagnostic was already emitted, should we just break?
268+ return make_err ( MveConcatInvalidReason :: InvalidLiteral ) ;
269+ }
270+ Err ( err) => return Err ( create_lit_error ( psess, err, lit, token. span ) ) ,
271+ } ;
272+
273+ if !s. chars ( ) . all ( |ch| is_id_continue ( ch) ) {
274+ // Check that all characters are valid in the middle of an identifier. This doesn't
275+ // guarantee that the final identifier is valid (we still need to check it later),
276+ // but it allows us to catch errors with specific arguments before expansion time;
277+ // for example, string literal "foo.bar" gets flagged before the macro is invoked.
278+ return make_err ( MveConcatInvalidReason :: InvalidIdent ) ;
279+ }
280+
281+ MetaVarExprConcatElem :: Ident ( s)
282+ } else if let Some ( ( elem, is_raw) ) = token. ident ( ) {
283+ if is_raw == IdentIsRaw :: Yes {
284+ return make_err ( MveConcatInvalidReason :: RawIdentifier ) ;
227285 }
286+ MetaVarExprConcatElem :: Ident ( elem. as_str ( ) . to_string ( ) )
287+ } else {
288+ return make_err ( MveConcatInvalidReason :: UnsupportedInput ) ;
228289 } ;
290+
229291 result. push ( element) ;
292+
230293 if iter. peek ( ) . is_none ( ) {
294+ // break before trying to eat the comma
231295 break ;
232296 }
297+
233298 if !try_eat_comma ( iter) {
234299 return Err ( psess. dcx ( ) . struct_span_err ( outer_span, "expected comma" ) ) ;
235300 }
@@ -315,43 +380,6 @@ fn parse_ident<'psess>(
315380 Ok ( elem)
316381}
317382
318- fn parse_ident_from_token < ' psess > (
319- psess : & ' psess ParseSess ,
320- token : & Token ,
321- ) -> PResult < ' psess , Ident > {
322- if let Some ( ( elem, is_raw) ) = token. ident ( ) {
323- if let IdentIsRaw :: Yes = is_raw {
324- return Err ( psess. dcx ( ) . struct_span_err ( elem. span , RAW_IDENT_ERR ) ) ;
325- }
326- return Ok ( elem) ;
327- }
328- let token_str = pprust:: token_to_string ( token) ;
329- let mut err = psess
330- . dcx ( )
331- . struct_span_err ( token. span , format ! ( "expected identifier, found `{token_str}`" ) ) ;
332- err. span_suggestion (
333- token. span ,
334- format ! ( "try removing `{token_str}`" ) ,
335- "" ,
336- Applicability :: MaybeIncorrect ,
337- ) ;
338- Err ( err)
339- }
340-
341- fn parse_token < ' psess , ' t > (
342- iter : & mut TokenStreamIter < ' t > ,
343- psess : & ' psess ParseSess ,
344- fallback_span : Span ,
345- ) -> PResult < ' psess , & ' t Token > {
346- let Some ( tt) = iter. next ( ) else {
347- return Err ( psess. dcx ( ) . struct_span_err ( fallback_span, UNSUPPORTED_CONCAT_ELEM_ERR ) ) ;
348- } ;
349- let TokenTree :: Token ( token, _) = tt else {
350- return Err ( psess. dcx ( ) . struct_span_err ( tt. span ( ) , UNSUPPORTED_CONCAT_ELEM_ERR ) ) ;
351- } ;
352- Ok ( token)
353- }
354-
355383/// Tries to move the iterator forward returning `true` if there is a comma. If not, then the
356384/// iterator is not modified and the result is `false`.
357385fn try_eat_comma ( iter : & mut TokenStreamIter < ' _ > ) -> bool {
@@ -362,14 +390,14 @@ fn try_eat_comma(iter: &mut TokenStreamIter<'_>) -> bool {
362390 false
363391}
364392
365- /// Tries to move the iterator forward returning `true ` if there is a dollar sign. If not, then the
366- /// iterator is not modified and the result is `false `.
367- fn try_eat_dollar ( iter : & mut TokenStreamIter < ' _ > ) -> bool {
368- if let Some ( TokenTree :: Token ( Token { kind : token:: Dollar , .. } , _) ) = iter. peek ( ) {
393+ /// Tries to move the iterator forward returning `Some(dollar_span) ` if there is a dollar sign. If
394+ /// not, then the iterator is not modified and the result is `None `.
395+ fn try_eat_dollar ( iter : & mut TokenStreamIter < ' _ > ) -> Option < Span > {
396+ if let Some ( TokenTree :: Token ( Token { kind : token:: Dollar , span } , _) ) = iter. peek ( ) {
369397 let _ = iter. next ( ) ;
370- return true ;
398+ return Some ( * span ) ;
371399 }
372- false
400+ None
373401}
374402
375403/// Expects that the next item is a dollar sign.
@@ -378,7 +406,7 @@ fn eat_dollar<'psess>(
378406 psess : & ' psess ParseSess ,
379407 span : Span ,
380408) -> PResult < ' psess , ( ) > {
381- if try_eat_dollar ( iter) {
409+ if try_eat_dollar ( iter) . is_some ( ) {
382410 return Ok ( ( ) ) ;
383411 }
384412 Err ( psess. dcx ( ) . struct_span_err (
0 commit comments