Skip to content

Commit 6b48950

Browse files
committed
Auto merge of #14820 - HKalbasi:format-args, r=HKalbasi
Expand `format_args!` with more details
2 parents 0a806fe + a2fba7c commit 6b48950

File tree

3 files changed

+188
-35
lines changed

3 files changed

+188
-35
lines changed

crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -193,17 +193,17 @@ fn main() {
193193
format_args!("{} {:?}", arg1(a, b, c), arg2);
194194
}
195195
"#,
196-
expect![[r#"
196+
expect![[r##"
197197
#[rustc_builtin_macro]
198198
macro_rules! format_args {
199199
($fmt:expr) => ({ /* compiler built-in */ });
200200
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
201201
}
202202
203203
fn main() {
204-
$crate::fmt::Arguments::new_v1(&[], &[$crate::fmt::ArgumentV1::new(&(arg1(a, b, c)), $crate::fmt::Display::fmt), $crate::fmt::ArgumentV1::new(&(arg2), $crate::fmt::Display::fmt), ]);
204+
$crate::fmt::Arguments::new_v1(&["", " ", ], &[$crate::fmt::ArgumentV1::new(&(arg1(a, b, c)), $crate::fmt::Display::fmt), $crate::fmt::ArgumentV1::new(&(arg2), $crate::fmt::Debug::fmt), ]);
205205
}
206-
"#]],
206+
"##]],
207207
);
208208
}
209209

@@ -221,17 +221,51 @@ fn main() {
221221
format_args!("{} {:?}", a::<A,B>(), b);
222222
}
223223
"#,
224-
expect![[r#"
224+
expect![[r##"
225225
#[rustc_builtin_macro]
226226
macro_rules! format_args {
227227
($fmt:expr) => ({ /* compiler built-in */ });
228228
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
229229
}
230230
231231
fn main() {
232-
$crate::fmt::Arguments::new_v1(&[], &[$crate::fmt::ArgumentV1::new(&(a::<A, B>()), $crate::fmt::Display::fmt), $crate::fmt::ArgumentV1::new(&(b), $crate::fmt::Display::fmt), ]);
232+
$crate::fmt::Arguments::new_v1(&["", " ", ], &[$crate::fmt::ArgumentV1::new(&(a::<A, B>()), $crate::fmt::Display::fmt), $crate::fmt::ArgumentV1::new(&(b), $crate::fmt::Debug::fmt), ]);
233233
}
234-
"#]],
234+
"##]],
235+
);
236+
}
237+
238+
#[test]
239+
fn test_format_args_expand_eager() {
240+
check(
241+
r#"
242+
#[rustc_builtin_macro]
243+
macro_rules! concat {}
244+
245+
#[rustc_builtin_macro]
246+
macro_rules! format_args {
247+
($fmt:expr) => ({ /* compiler built-in */ });
248+
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
249+
}
250+
251+
fn main() {
252+
format_args!(concat!("xxx{}y", "{:?}zzz"), 2, b);
253+
}
254+
"#,
255+
expect![[r##"
256+
#[rustc_builtin_macro]
257+
macro_rules! concat {}
258+
259+
#[rustc_builtin_macro]
260+
macro_rules! format_args {
261+
($fmt:expr) => ({ /* compiler built-in */ });
262+
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
263+
}
264+
265+
fn main() {
266+
$crate::fmt::Arguments::new_v1(&["xxx", "y", "zzz", ], &[$crate::fmt::ArgumentV1::new(&(2), $crate::fmt::Display::fmt), $crate::fmt::ArgumentV1::new(&(b), $crate::fmt::Debug::fmt), ]);
267+
}
268+
"##]],
235269
);
236270
}
237271

@@ -250,7 +284,7 @@ fn main() {
250284
format_args!/*+errors*/("{} {:?}", a.);
251285
}
252286
"#,
253-
expect![[r#"
287+
expect![[r##"
254288
#[rustc_builtin_macro]
255289
macro_rules! format_args {
256290
($fmt:expr) => ({ /* compiler built-in */ });
@@ -259,10 +293,10 @@ macro_rules! format_args {
259293
260294
fn main() {
261295
let _ =
262-
/* parse error: expected field name or number */
263-
$crate::fmt::Arguments::new_v1(&[], &[$crate::fmt::ArgumentV1::new(&(a.), $crate::fmt::Display::fmt), ]);
296+
/* error: no rule matches input tokens *//* parse error: expected field name or number */
297+
$crate::fmt::Arguments::new_v1(&["", " ", ], &[$crate::fmt::ArgumentV1::new(&(a.), $crate::fmt::Display::fmt), $crate::fmt::ArgumentV1::new(&(), $crate::fmt::Debug::fmt), ]);
264298
}
265-
"#]],
299+
"##]],
266300
);
267301
}
268302

crates/hir-expand/src/builtin_fn_macro.rs

Lines changed: 143 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
//! Builtin macro
22
3+
use std::mem;
4+
5+
use ::tt::Ident;
36
use base_db::{AnchoredPath, Edition, FileId};
47
use cfg::CfgExpr;
58
use either::Either;
69
use mbe::{parse_exprs_with_sep, parse_to_token_tree, TokenMap};
10+
use rustc_hash::FxHashMap;
711
use 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,159 @@ fn file_expand(
232234
}
233235

234236
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(
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;
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+
}
260373
}
374+
_ => last_part.push(c),
261375
}
262376
}
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);
267386
let expanded = quote! {
268-
#DOLLAR_CRATE::fmt::Arguments::new_v1(&[], &[##arg_tts])
387+
#DOLLAR_CRATE::fmt::Arguments::new_v1(&[##part_tts], &[##arg_tts])
269388
};
270-
ExpandResult::ok(expanded)
389+
ExpandResult { value: expanded, err }
271390
}
272391

273392
fn asm_expand(

crates/ide/src/syntax_highlighting/test_data/highlight_strings.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,5 +171,5 @@
171171
<span class="macro">assert</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="bool_literal macro">true</span><span class="comma macro">,</span> <span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> asdasd"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
172172
<span class="macro">toho</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">fmt"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
173173
<span class="macro unsafe">asm</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"mov eax, </span><span class="format_specifier">{</span><span class="numeric_literal">0</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
174-
<span class="macro">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="none macro">concat</span><span class="punctuation macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="string_literal macro">"{}"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
174+
<span class="macro">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="none macro">concat</span><span class="punctuation macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
175175
<span class="brace">}</span></code></pre>

0 commit comments

Comments
 (0)