1
- use rustc_ast:: token:: { self , Delimiter , IdentIsRaw , Lit , Token , TokenKind } ;
1
+ use rustc_ast:: token:: { self , Delimiter , IdentIsRaw , Token , TokenKind } ;
2
2
use rustc_ast:: tokenstream:: { TokenStream , TokenStreamIter , TokenTree } ;
3
- use rustc_ast:: { LitIntType , LitKind } ;
3
+ use rustc_ast:: { self as ast , LitIntType , LitKind } ;
4
4
use rustc_ast_pretty:: pprust;
5
- use rustc_errors:: { Applicability , PResult } ;
5
+ use rustc_errors:: PResult ;
6
+ use rustc_lexer:: is_id_continue;
6
7
use rustc_macros:: { Decodable , Encodable } ;
8
+ use rustc_session:: errors:: create_lit_error;
7
9
use rustc_session:: parse:: ParseSess ;
8
10
use rustc_span:: { Ident , Span , Symbol } ;
9
11
10
- use crate :: errors:: { self , MveExpectedIdentContext } ;
12
+ use crate :: errors:: { self , MveConcatInvalidReason , MveExpectedIdentContext } ;
11
13
12
14
pub ( 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" ;
14
17
15
18
/// Argument specification for a metavariable expression
16
19
#[ derive( Clone , Copy ) ]
@@ -190,7 +193,7 @@ fn iter_span(iter: &TokenStreamIter<'_>) -> Option<Span> {
190
193
pub ( crate ) enum MetaVarExprConcatElem {
191
194
/// Identifier WITHOUT a preceding dollar sign, which means that this identifier should be
192
195
/// interpreted as a literal.
193
- Ident ( Ident ) ,
196
+ Ident ( String ) ,
194
197
/// For example, a number or a string.
195
198
Literal ( Symbol ) ,
196
199
/// Identifier WITH a preceding dollar sign, which means that this identifier should be
@@ -206,30 +209,92 @@ fn parse_concat<'psess>(
206
209
expr_ident_span : Span ,
207
210
) -> PResult < ' psess , MetaVarExpr > {
208
211
let mut result = Vec :: new ( ) ;
212
+ let dcx = psess. dcx ( ) ;
209
213
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 ) ;
225
265
}
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 ) ;
227
285
}
286
+ MetaVarExprConcatElem :: Ident ( elem. as_str ( ) . to_string ( ) )
287
+ } else {
288
+ return make_err ( MveConcatInvalidReason :: UnsupportedInput ) ;
228
289
} ;
290
+
229
291
result. push ( element) ;
292
+
230
293
if iter. peek ( ) . is_none ( ) {
294
+ // break before trying to eat the comma
231
295
break ;
232
296
}
297
+
233
298
if !try_eat_comma ( iter) {
234
299
return Err ( psess. dcx ( ) . struct_span_err ( outer_span, "expected comma" ) ) ;
235
300
}
@@ -315,43 +380,6 @@ fn parse_ident<'psess>(
315
380
Ok ( elem)
316
381
}
317
382
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
-
355
383
/// Tries to move the iterator forward returning `true` if there is a comma. If not, then the
356
384
/// iterator is not modified and the result is `false`.
357
385
fn try_eat_comma ( iter : & mut TokenStreamIter < ' _ > ) -> bool {
@@ -362,14 +390,14 @@ fn try_eat_comma(iter: &mut TokenStreamIter<'_>) -> bool {
362
390
false
363
391
}
364
392
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 ( ) {
369
397
let _ = iter. next ( ) ;
370
- return true ;
398
+ return Some ( * span ) ;
371
399
}
372
- false
400
+ None
373
401
}
374
402
375
403
/// Expects that the next item is a dollar sign.
@@ -378,7 +406,7 @@ fn eat_dollar<'psess>(
378
406
psess : & ' psess ParseSess ,
379
407
span : Span ,
380
408
) -> PResult < ' psess , ( ) > {
381
- if try_eat_dollar ( iter) {
409
+ if try_eat_dollar ( iter) . is_some ( ) {
382
410
return Ok ( ( ) ) ;
383
411
}
384
412
Err ( psess. dcx ( ) . struct_span_err (
0 commit comments