Skip to content

Commit f9be796

Browse files
committed
Auto merge of rust-lang#14820 - HKalbasi:format-args, r=HKalbasi
Expand `format_args!` with more details
2 parents e4977e7 + 5c83e22 commit f9be796

File tree

3 files changed

+237
-35
lines changed

3 files changed

+237
-35
lines changed

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

+77-10
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,84 @@ 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_with_raw_strings() {
240+
check(
241+
r##"
242+
#[rustc_builtin_macro]
243+
macro_rules! format_args {
244+
($fmt:expr) => ({ /* compiler built-in */ });
245+
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
246+
}
247+
248+
fn main() {
249+
format_args!(
250+
r#"{},mismatch,"{}","{}""#,
251+
location_csv_pat(db, &analysis, vfs, &sm, pat_id),
252+
mismatch.expected.display(db),
253+
mismatch.actual.display(db)
254+
);
255+
}
256+
"##,
257+
expect![[r##"
258+
#[rustc_builtin_macro]
259+
macro_rules! format_args {
260+
($fmt:expr) => ({ /* compiler built-in */ });
261+
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
262+
}
263+
264+
fn main() {
265+
$crate::fmt::Arguments::new_v1(&[r#""#, r#",mismatch,""#, r#"",""#, r#"""#, ], &[$crate::fmt::ArgumentV1::new(&(location_csv_pat(db, &analysis, vfs, &sm, pat_id)), $crate::fmt::Display::fmt), $crate::fmt::ArgumentV1::new(&(mismatch.expected.display(db)), $crate::fmt::Display::fmt), $crate::fmt::ArgumentV1::new(&(mismatch.actual.display(db)), $crate::fmt::Display::fmt), ]);
266+
}
267+
"##]],
268+
);
269+
}
270+
271+
#[test]
272+
fn test_format_args_expand_eager() {
273+
check(
274+
r#"
275+
#[rustc_builtin_macro]
276+
macro_rules! concat {}
277+
278+
#[rustc_builtin_macro]
279+
macro_rules! format_args {
280+
($fmt:expr) => ({ /* compiler built-in */ });
281+
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
282+
}
283+
284+
fn main() {
285+
format_args!(concat!("xxx{}y", "{:?}zzz"), 2, b);
286+
}
287+
"#,
288+
expect![[r##"
289+
#[rustc_builtin_macro]
290+
macro_rules! concat {}
291+
292+
#[rustc_builtin_macro]
293+
macro_rules! format_args {
294+
($fmt:expr) => ({ /* compiler built-in */ });
295+
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
296+
}
297+
298+
fn main() {
299+
$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), ]);
300+
}
301+
"##]],
235302
);
236303
}
237304

@@ -250,7 +317,7 @@ fn main() {
250317
format_args!/*+errors*/("{} {:?}", a.);
251318
}
252319
"#,
253-
expect![[r#"
320+
expect![[r##"
254321
#[rustc_builtin_macro]
255322
macro_rules! format_args {
256323
($fmt:expr) => ({ /* compiler built-in */ });
@@ -259,10 +326,10 @@ macro_rules! format_args {
259326
260327
fn main() {
261328
let _ =
262-
/* parse error: expected field name or number */
263-
$crate::fmt::Arguments::new_v1(&[], &[$crate::fmt::ArgumentV1::new(&(a.), $crate::fmt::Display::fmt), ]);
329+
/* error: no rule matches input tokens *//* parse error: expected field name or number */
330+
$crate::fmt::Arguments::new_v1(&["", " ", ], &[$crate::fmt::ArgumentV1::new(&(a.), $crate::fmt::Display::fmt), $crate::fmt::ArgumentV1::new(&(), $crate::fmt::Debug::fmt), ]);
264331
}
265-
"#]],
332+
"##]],
266333
);
267334
}
268335

crates/hir-expand/src/builtin_fn_macro.rs

+159-24
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,175 @@ 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;
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 =
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);
262391
}
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);
267402
let expanded = quote! {
268-
#DOLLAR_CRATE::fmt::Arguments::new_v1(&[], &[##arg_tts])
403+
#DOLLAR_CRATE::fmt::Arguments::new_v1(&[##part_tts], &[##arg_tts])
269404
};
270-
ExpandResult::ok(expanded)
405+
ExpandResult { value: expanded, err }
271406
}
272407

273408
fn asm_expand(

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

+1-1
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)