11//! Builtin macro
22
3+ use std:: mem;
4+
5+ use :: tt:: Ident ;
36use base_db:: { AnchoredPath , Edition , FileId } ;
47use cfg:: CfgExpr ;
58use either:: Either ;
69use mbe:: { parse_exprs_with_sep, parse_to_token_tree, TokenMap } ;
10+ use rustc_hash:: FxHashMap ;
711use syntax:: {
812 ast:: { self , AstToken } ,
913 SmolStr ,
@@ -90,11 +94,6 @@ register_builtin! {
9094 ( module_path, ModulePath ) => module_path_expand,
9195 ( assert, Assert ) => assert_expand,
9296 ( 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,
9897 ( llvm_asm, LlvmAsm ) => asm_expand,
9998 ( asm, Asm ) => asm_expand,
10099 ( global_asm, GlobalAsm ) => global_asm_expand,
@@ -106,6 +105,9 @@ register_builtin! {
106105 ( trace_macros, TraceMacros ) => trace_macros_expand,
107106
108107 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,
109111 ( compile_error, CompileError ) => compile_error_expand,
110112 ( concat, Concat ) => concat_expand,
111113 ( concat_idents, ConcatIdents ) => concat_idents_expand,
@@ -232,42 +234,174 @@ fn file_expand(
232234}
233235
234236fn 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 (
235255 _db : & dyn ExpandDatabase ,
236256 _id : MacroCallId ,
237257 tt : & tt:: Subtree ,
258+ end_string : & str ,
238259) -> 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 ( ) ) ;
248264
249265 if args. is_empty ( ) {
250- return ExpandResult :: new ( tt :: Subtree :: empty ( ) , mbe :: ExpandError :: NoMatchingRule . into ( ) ) ;
266+ return expand_error ;
251267 }
252- for arg in & mut args {
268+ let mut key_args = FxHashMap :: default ( ) ;
269+ let mut args = args. into_iter ( ) . filter_map ( |mut arg| {
253270 // Remove `key =`.
254271 if matches ! ( arg. token_trees. get( 1 ) , Some ( tt:: TokenTree :: Leaf ( tt:: Leaf :: Punct ( p) ) ) if p. char == '=' )
255272 {
256273 // 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 == '=' )
258275 {
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 ;
260279 }
261280 }
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) ;
262390 }
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 ) ;
267401 let expanded = quote ! {
268- #DOLLAR_CRATE :: fmt:: Arguments :: new_v1( & [ ] , & [ ##arg_tts] )
402+ #DOLLAR_CRATE :: fmt:: Arguments :: new_v1( & [ ##part_tts ] , & [ ##arg_tts] )
269403 } ;
270- ExpandResult :: ok ( expanded)
404+ ExpandResult { value : expanded, err }
271405}
272406
273407fn asm_expand (
0 commit comments