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,159 @@ 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 ;
279
+ }
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
+ let text = l. text . strip_prefix ( '"' ) ?. strip_suffix ( '"' ) ?;
291
+ let span = l. span ;
292
+ Some ( ( text, span) )
293
+ }
294
+ _ => None ,
295
+ } ,
296
+ tt:: TokenTree :: Subtree ( _) => None ,
297
+ }
298
+ } ) ( ) ;
299
+ let Some ( ( format_string, _format_string_span) ) = format_string else {
300
+ return expand_error;
301
+ } ;
302
+ let mut format_iter = format_string. chars ( ) . peekable ( ) ;
303
+ let mut parts = vec ! [ ] ;
304
+ let mut last_part = String :: new ( ) ;
305
+ let mut arg_tts = vec ! [ ] ;
306
+ let mut err = None ;
307
+ while let Some ( c) = format_iter. next ( ) {
308
+ // Parsing the format string. See https://doc.rust-lang.org/std/fmt/index.html#syntax for the grammar and more info
309
+ match c {
310
+ '{' => {
311
+ if format_iter. peek ( ) == Some ( & '{' ) {
312
+ format_iter. next ( ) ;
313
+ last_part. push ( '{' ) ;
314
+ continue ;
315
+ }
316
+ let mut argument = String :: new ( ) ;
317
+ while ![ Some ( & '}' ) , Some ( & ':' ) ] . contains ( & format_iter. peek ( ) ) {
318
+ argument. push ( match format_iter. next ( ) {
319
+ Some ( c) => c,
320
+ None => return expand_error,
321
+ } ) ;
322
+ }
323
+ let format_spec = match format_iter. next ( ) . unwrap ( ) {
324
+ '}' => "" . to_owned ( ) ,
325
+ ':' => {
326
+ let mut s = String :: new ( ) ;
327
+ while let Some ( c) = format_iter. next ( ) {
328
+ if c == '}' {
329
+ break ;
330
+ }
331
+ s. push ( c) ;
332
+ }
333
+ s
334
+ }
335
+ _ => unreachable ! ( ) ,
336
+ } ;
337
+ parts. push ( mem:: take ( & mut last_part) ) ;
338
+ let arg_tree = if argument. is_empty ( ) {
339
+ match args. next ( ) {
340
+ Some ( x) => x,
341
+ None => {
342
+ err = Some ( mbe:: ExpandError :: NoMatchingRule . into ( ) ) ;
343
+ tt:: Subtree :: empty ( )
344
+ }
345
+ }
346
+ } else if let Some ( tree) = key_args. get ( & argument) {
347
+ tree. clone ( )
348
+ } else {
349
+ // FIXME: we should pick the related substring of the `_format_string_span` as the span. You
350
+ // can use `.char_indices()` instead of `.char()` for `format_iter` to find the substring interval.
351
+ let ident = Ident :: new ( argument, tt:: TokenId :: unspecified ( ) ) ;
352
+ quote ! ( #ident)
353
+ } ;
354
+ let formatter = match & * format_spec {
355
+ "?" => quote ! ( #DOLLAR_CRATE :: fmt:: Debug :: fmt) ,
356
+ "" => quote ! ( #DOLLAR_CRATE :: fmt:: Display :: fmt) ,
357
+ _ => {
358
+ // FIXME: implement the rest and return expand error here
359
+ quote ! ( #DOLLAR_CRATE :: fmt:: Display :: fmt)
360
+ }
361
+ } ;
362
+ arg_tts. push (
363
+ quote ! { #DOLLAR_CRATE :: fmt:: ArgumentV1 :: new( & ( #arg_tree) , #formatter) , } ,
364
+ ) ;
365
+ }
366
+ '}' => {
367
+ if format_iter. peek ( ) == Some ( & '}' ) {
368
+ format_iter. next ( ) ;
369
+ last_part. push ( '}' ) ;
370
+ } else {
371
+ return expand_error;
372
+ }
260
373
}
374
+ _ => last_part. push ( c) ,
261
375
}
262
376
}
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 ) ;
377
+ last_part += end_string;
378
+ if !last_part. is_empty ( ) {
379
+ parts. push ( last_part) ;
380
+ }
381
+ let part_tts = parts. into_iter ( ) . map ( |x| {
382
+ let l = tt:: Literal { span : tt:: TokenId :: unspecified ( ) , text : format ! ( "\" {}\" " , x) . into ( ) } ;
383
+ quote ! ( #l , )
384
+ } ) ;
385
+ let arg_tts = arg_tts. into_iter ( ) . flat_map ( |arg| arg. token_trees ) ;
267
386
let expanded = quote ! {
268
- #DOLLAR_CRATE :: fmt:: Arguments :: new_v1( & [ ] , & [ ##arg_tts] )
387
+ #DOLLAR_CRATE :: fmt:: Arguments :: new_v1( & [ ##part_tts ] , & [ ##arg_tts] )
269
388
} ;
270
- ExpandResult :: ok ( expanded)
389
+ ExpandResult { value : expanded, err }
271
390
}
272
391
273
392
fn asm_expand (
0 commit comments