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
/// List of the below list for diagnostics.
16
19
const VALID_METAVAR_EXPR_NAMES : & str = "`count`, `ignore`, `index`, `len`, and `concat`" ;
@@ -165,7 +168,7 @@ fn iter_span(iter: &TokenStreamIter<'_>) -> Option<Span> {
165
168
pub ( crate ) enum MetaVarExprConcatElem {
166
169
/// Identifier WITHOUT a preceding dollar sign, which means that this identifier should be
167
170
/// interpreted as a literal.
168
- Ident ( Ident ) ,
171
+ Ident ( String ) ,
169
172
/// For example, a number or a string.
170
173
Literal ( Symbol ) ,
171
174
/// Identifier WITH a preceding dollar sign, which means that this identifier should be
@@ -181,30 +184,92 @@ fn parse_concat<'psess>(
181
184
expr_ident_span : Span ,
182
185
) -> PResult < ' psess , MetaVarExpr > {
183
186
let mut result = Vec :: new ( ) ;
187
+ let dcx = psess. dcx ( ) ;
184
188
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 ) ;
200
240
}
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 ) ;
202
260
}
261
+ MetaVarExprConcatElem :: Ident ( elem. as_str ( ) . to_string ( ) )
262
+ } else {
263
+ return make_err ( MveConcatInvalidReason :: UnsupportedInput ) ;
203
264
} ;
265
+
204
266
result. push ( element) ;
267
+
205
268
if iter. peek ( ) . is_none ( ) {
269
+ // break before trying to eat the comma
206
270
break ;
207
271
}
272
+
208
273
if !try_eat_comma ( iter) {
209
274
return Err ( psess. dcx ( ) . struct_span_err ( outer_span, "expected comma" ) ) ;
210
275
}
@@ -290,43 +355,6 @@ fn parse_ident<'psess>(
290
355
Ok ( elem)
291
356
}
292
357
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
-
330
358
/// Tries to move the iterator forward returning `true` if there is a comma. If not, then the
331
359
/// iterator is not modified and the result is `false`.
332
360
fn try_eat_comma ( iter : & mut TokenStreamIter < ' _ > ) -> bool {
@@ -337,14 +365,14 @@ fn try_eat_comma(iter: &mut TokenStreamIter<'_>) -> bool {
337
365
false
338
366
}
339
367
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 ( ) {
344
372
let _ = iter. next ( ) ;
345
- return true ;
373
+ return Some ( * span ) ;
346
374
}
347
- false
375
+ None
348
376
}
349
377
350
378
/// Expects that the next item is a dollar sign.
@@ -353,7 +381,7 @@ fn eat_dollar<'psess>(
353
381
psess : & ' psess ParseSess ,
354
382
span : Span ,
355
383
) -> PResult < ' psess , ( ) > {
356
- if try_eat_dollar ( iter) {
384
+ if try_eat_dollar ( iter) . is_some ( ) {
357
385
return Ok ( ( ) ) ;
358
386
}
359
387
Err ( psess. dcx ( ) . struct_span_err (
0 commit comments