1
1
//! Builtin macro
2
2
3
+ use std:: mem;
4
+
5
+ use :: tt:: Ident ;
3
6
use base_db:: { AnchoredPath , Edition , FileId } ;
4
7
use cfg:: CfgExpr ;
5
8
use either:: Either ;
6
9
use mbe:: { parse_exprs_with_sep, parse_to_token_tree, TokenMap } ;
10
+ use rustc_hash:: FxHashMap ;
7
11
use syntax:: {
8
12
ast:: { self , AstToken } ,
9
13
SmolStr ,
@@ -90,11 +94,6 @@ register_builtin! {
90
94
( module_path, ModulePath ) => module_path_expand,
91
95
( assert, Assert ) => assert_expand,
92
96
( stringify, Stringify ) => stringify_expand,
93
- ( format_args, FormatArgs ) => format_args_expand,
94
- ( const_format_args, ConstFormatArgs ) => format_args_expand,
95
- // format_args_nl only differs in that it adds a newline in the end,
96
- // so we use the same stub expansion for now
97
- ( format_args_nl, FormatArgsNl ) => format_args_expand,
98
97
( llvm_asm, LlvmAsm ) => asm_expand,
99
98
( asm, Asm ) => asm_expand,
100
99
( global_asm, GlobalAsm ) => global_asm_expand,
@@ -106,6 +105,9 @@ register_builtin! {
106
105
( trace_macros, TraceMacros ) => trace_macros_expand,
107
106
108
107
EAGER :
108
+ ( format_args, FormatArgs ) => format_args_expand,
109
+ ( const_format_args, ConstFormatArgs ) => format_args_expand,
110
+ ( format_args_nl, FormatArgsNl ) => format_args_nl_expand,
109
111
( compile_error, CompileError ) => compile_error_expand,
110
112
( concat, Concat ) => concat_expand,
111
113
( concat_idents, ConcatIdents ) => concat_idents_expand,
@@ -232,42 +234,174 @@ fn file_expand(
232
234
}
233
235
234
236
fn format_args_expand (
237
+ db : & dyn ExpandDatabase ,
238
+ id : MacroCallId ,
239
+ tt : & tt:: Subtree ,
240
+ ) -> ExpandResult < ExpandedEager > {
241
+ format_args_expand_general ( db, id, tt, "" )
242
+ . map ( |x| ExpandedEager { subtree : x, included_file : None } )
243
+ }
244
+
245
+ fn format_args_nl_expand (
246
+ db : & dyn ExpandDatabase ,
247
+ id : MacroCallId ,
248
+ tt : & tt:: Subtree ,
249
+ ) -> ExpandResult < ExpandedEager > {
250
+ format_args_expand_general ( db, id, tt, "\\ n" )
251
+ . map ( |x| ExpandedEager { subtree : x, included_file : None } )
252
+ }
253
+
254
+ fn format_args_expand_general (
235
255
_db : & dyn ExpandDatabase ,
236
256
_id : MacroCallId ,
237
257
tt : & tt:: Subtree ,
258
+ end_string : & str ,
238
259
) -> ExpandResult < tt:: Subtree > {
239
- // We expand `format_args!("", a1, a2)` to
240
- // ```
241
- // $crate::fmt::Arguments::new_v1(&[], &[
242
- // $crate::fmt::ArgumentV1::new(&arg1,$crate::fmt::Display::fmt),
243
- // $crate::fmt::ArgumentV1::new(&arg2,$crate::fmt::Display::fmt),
244
- // ])
245
- // ```,
246
- // which is still not really correct, but close enough for now
247
- let mut args = parse_exprs_with_sep ( tt, ',' ) ;
260
+ let args = parse_exprs_with_sep ( tt, ',' ) ;
261
+
262
+ let expand_error =
263
+ ExpandResult :: new ( tt:: Subtree :: empty ( ) , mbe:: ExpandError :: NoMatchingRule . into ( ) ) ;
248
264
249
265
if args. is_empty ( ) {
250
- return ExpandResult :: new ( tt :: Subtree :: empty ( ) , mbe :: ExpandError :: NoMatchingRule . into ( ) ) ;
266
+ return expand_error ;
251
267
}
252
- for arg in & mut args {
268
+ let mut key_args = FxHashMap :: default ( ) ;
269
+ let mut args = args. into_iter ( ) . filter_map ( |mut arg| {
253
270
// Remove `key =`.
254
271
if matches ! ( arg. token_trees. get( 1 ) , Some ( tt:: TokenTree :: Leaf ( tt:: Leaf :: Punct ( p) ) ) if p. char == '=' )
255
272
{
256
273
// but not with `==`
257
- if !matches ! ( arg. token_trees. get( 2 ) , Some ( tt:: TokenTree :: Leaf ( tt:: Leaf :: Punct ( p) ) ) if p. char == '=' )
274
+ if !matches ! ( arg. token_trees. get( 2 ) , Some ( tt:: TokenTree :: Leaf ( tt:: Leaf :: Punct ( p) ) ) if p. char == '=' )
258
275
{
259
- arg. token_trees . drain ( ..2 ) ;
276
+ let key = arg. token_trees . drain ( ..2 ) . next ( ) . unwrap ( ) ;
277
+ key_args. insert ( key. to_string ( ) , arg) ;
278
+ return None ;
260
279
}
261
280
}
281
+ Some ( arg)
282
+ } ) . collect :: < Vec < _ > > ( ) . into_iter ( ) ;
283
+ // ^^^^^^^ we need this collect, to enforce the side effect of the filter_map closure (building the `key_args`)
284
+ let format_subtree = args. next ( ) . unwrap ( ) ;
285
+ let format_string = ( || {
286
+ let token_tree = format_subtree. token_trees . get ( 0 ) ?;
287
+ match token_tree {
288
+ tt:: TokenTree :: Leaf ( l) => match l {
289
+ tt:: Leaf :: Literal ( l) => {
290
+ if let Some ( mut text) = l. text . strip_prefix ( 'r' ) {
291
+ let mut raw_sharps = String :: new ( ) ;
292
+ while let Some ( t) = text. strip_prefix ( '#' ) {
293
+ text = t;
294
+ raw_sharps. push ( '#' ) ;
295
+ }
296
+ text = text. strip_suffix ( & raw_sharps) ?. strip_suffix ( '"' ) ?;
297
+ Some ( ( text, l. span , Some ( raw_sharps) ) )
298
+ } else {
299
+ let text = l. text . strip_prefix ( '"' ) ?. strip_suffix ( '"' ) ?;
300
+ let span = l. span ;
301
+ Some ( ( text, span, None ) )
302
+ }
303
+ }
304
+ _ => None ,
305
+ } ,
306
+ tt:: TokenTree :: Subtree ( _) => None ,
307
+ }
308
+ } ) ( ) ;
309
+ let Some ( ( format_string, _format_string_span, raw_sharps) ) = format_string else {
310
+ return expand_error;
311
+ } ;
312
+ let mut format_iter = format_string. chars ( ) . peekable ( ) ;
313
+ let mut parts = vec ! [ ] ;
314
+ let mut last_part = String :: new ( ) ;
315
+ let mut arg_tts = vec ! [ ] ;
316
+ let mut err = None ;
317
+ while let Some ( c) = format_iter. next ( ) {
318
+ // Parsing the format string. See https://doc.rust-lang.org/std/fmt/index.html#syntax for the grammar and more info
319
+ match c {
320
+ '{' => {
321
+ if format_iter. peek ( ) == Some ( & '{' ) {
322
+ format_iter. next ( ) ;
323
+ last_part. push ( '{' ) ;
324
+ continue ;
325
+ }
326
+ let mut argument = String :: new ( ) ;
327
+ while ![ Some ( & '}' ) , Some ( & ':' ) ] . contains ( & format_iter. peek ( ) ) {
328
+ argument. push ( match format_iter. next ( ) {
329
+ Some ( c) => c,
330
+ None => return expand_error,
331
+ } ) ;
332
+ }
333
+ let format_spec = match format_iter. next ( ) . unwrap ( ) {
334
+ '}' => "" . to_owned ( ) ,
335
+ ':' => {
336
+ let mut s = String :: new ( ) ;
337
+ while let Some ( c) = format_iter. next ( ) {
338
+ if c == '}' {
339
+ break ;
340
+ }
341
+ s. push ( c) ;
342
+ }
343
+ s
344
+ }
345
+ _ => unreachable ! ( ) ,
346
+ } ;
347
+ parts. push ( mem:: take ( & mut last_part) ) ;
348
+ let arg_tree = if argument. is_empty ( ) {
349
+ match args. next ( ) {
350
+ Some ( x) => x,
351
+ None => {
352
+ err = Some ( mbe:: ExpandError :: NoMatchingRule . into ( ) ) ;
353
+ tt:: Subtree :: empty ( )
354
+ }
355
+ }
356
+ } else if let Some ( tree) = key_args. get ( & argument) {
357
+ tree. clone ( )
358
+ } else {
359
+ // FIXME: we should pick the related substring of the `_format_string_span` as the span. You
360
+ // can use `.char_indices()` instead of `.char()` for `format_iter` to find the substring interval.
361
+ let ident = Ident :: new ( argument, tt:: TokenId :: unspecified ( ) ) ;
362
+ quote ! ( #ident)
363
+ } ;
364
+ let formatter = match & * format_spec {
365
+ "?" => quote ! ( #DOLLAR_CRATE :: fmt:: Debug :: fmt) ,
366
+ "" => quote ! ( #DOLLAR_CRATE :: fmt:: Display :: fmt) ,
367
+ _ => {
368
+ // FIXME: implement the rest and return expand error here
369
+ quote ! ( #DOLLAR_CRATE :: fmt:: Display :: fmt)
370
+ }
371
+ } ;
372
+ arg_tts. push (
373
+ quote ! { #DOLLAR_CRATE :: fmt:: ArgumentV1 :: new( & ( #arg_tree) , #formatter) , } ,
374
+ ) ;
375
+ }
376
+ '}' => {
377
+ if format_iter. peek ( ) == Some ( & '}' ) {
378
+ format_iter. next ( ) ;
379
+ last_part. push ( '}' ) ;
380
+ } else {
381
+ return expand_error;
382
+ }
383
+ }
384
+ _ => last_part. push ( c) ,
385
+ }
386
+ }
387
+ last_part += end_string;
388
+ if !last_part. is_empty ( ) {
389
+ parts. push ( last_part) ;
262
390
}
263
- let _format_string = args. remove ( 0 ) ;
264
- let arg_tts = args. into_iter ( ) . flat_map ( |arg| {
265
- quote ! { #DOLLAR_CRATE :: fmt:: ArgumentV1 :: new( & ( #arg) , #DOLLAR_CRATE :: fmt:: Display :: fmt) , }
266
- } . token_trees ) ;
391
+ let part_tts = parts. into_iter ( ) . map ( |x| {
392
+ let text = if let Some ( raw) = & raw_sharps {
393
+ format ! ( "r{raw}\" {}\" {raw}" , x) . into ( )
394
+ } else {
395
+ format ! ( "\" {}\" " , x) . into ( )
396
+ } ;
397
+ let l = tt:: Literal { span : tt:: TokenId :: unspecified ( ) , text } ;
398
+ quote ! ( #l , )
399
+ } ) ;
400
+ let arg_tts = arg_tts. into_iter ( ) . flat_map ( |arg| arg. token_trees ) ;
267
401
let expanded = quote ! {
268
- #DOLLAR_CRATE :: fmt:: Arguments :: new_v1( & [ ] , & [ ##arg_tts] )
402
+ #DOLLAR_CRATE :: fmt:: Arguments :: new_v1( & [ ##part_tts ] , & [ ##arg_tts] )
269
403
} ;
270
- ExpandResult :: ok ( expanded)
404
+ ExpandResult { value : expanded, err }
271
405
}
272
406
273
407
fn asm_expand (
0 commit comments