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