Skip to content

Commit b141157

Browse files
committed
Enhance uninlined_format_args to inline str and char literals
1 parent 22c1389 commit b141157

File tree

7 files changed

+259
-106
lines changed

7 files changed

+259
-106
lines changed

clippy_lints/src/format_args.rs

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -407,8 +407,8 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
407407
// we cannot remove any other arguments in the format string,
408408
// because the index numbers might be wrong after inlining.
409409
// Example of an un-inlinable format: print!("{}{1}", foo, 2)
410-
for (pos, usage) in self.format_arg_positions() {
411-
if !self.check_one_arg(pos, usage, &mut fixes) {
410+
for (placeholder, pos, usage) in self.format_arg_positions() {
411+
if !self.check_one_arg(placeholder, pos, usage, &mut fixes) {
412412
return;
413413
}
414414
}
@@ -443,31 +443,64 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
443443
);
444444
}
445445

446-
fn check_one_arg(&self, pos: &FormatArgPosition, usage: FormatParamUsage, fixes: &mut Vec<(Span, String)>) -> bool {
446+
fn check_one_arg(
447+
&self,
448+
placeholder: &FormatPlaceholder,
449+
pos: &FormatArgPosition,
450+
usage: FormatParamUsage,
451+
fixes: &mut Vec<(Span, String)>,
452+
) -> bool {
447453
let index = pos.index.unwrap();
448454
let arg = &self.format_args.arguments.all_args()[index];
449455

450-
if !matches!(arg.kind, FormatArgumentKind::Captured(_))
451-
&& let rustc_ast::ExprKind::Path(None, path) = &arg.expr.kind
452-
&& let [segment] = path.segments.as_slice()
453-
&& segment.args.is_none()
454-
&& let Some(arg_span) = format_arg_removal_span(self.format_args, index)
455-
&& let Some(pos_span) = pos.span
456-
{
457-
let replacement = match usage {
458-
FormatParamUsage::Argument => segment.ident.name.to_string(),
459-
FormatParamUsage::Width => format!("{}$", segment.ident.name),
460-
FormatParamUsage::Precision => format!(".{}$", segment.ident.name),
461-
};
462-
fixes.push((pos_span, replacement));
463-
fixes.push((arg_span, String::new()));
464-
true // successful inlining, continue checking
465-
} else {
456+
if matches!(arg.kind, FormatArgumentKind::Captured(_)) {
466457
// Do not continue inlining (return false) in case
467458
// * if we can't inline a numbered argument, e.g. `print!("{0} ...", foo.bar, ...)`
468459
// * if allow_mixed_uninlined_format_args is false and this arg hasn't been inlined already
469460
pos.kind != FormatArgPositionKind::Number
470461
&& (!self.ignore_mixed || matches!(arg.kind, FormatArgumentKind::Captured(_)))
462+
} else {
463+
match &arg.expr.kind {
464+
rustc_ast::ExprKind::Path(None, path)
465+
if let [segment] = path.segments.as_slice()
466+
&& segment.args.is_none()
467+
&& let Some(arg_span) = format_arg_removal_span(self.format_args, index)
468+
&& let Some(pos_span) = pos.span =>
469+
{
470+
let replacement = match usage {
471+
FormatParamUsage::Argument => segment.ident.name.to_string(),
472+
FormatParamUsage::Width => format!("{}$", segment.ident.name),
473+
FormatParamUsage::Precision => format!(".{}$", segment.ident.name),
474+
};
475+
fixes.push((pos_span, replacement));
476+
fixes.push((arg_span, String::new()));
477+
true // successful inlining, continue checking
478+
},
479+
rustc_ast::ExprKind::Lit(lit)
480+
if matches!(
481+
lit.kind,
482+
rustc_ast::token::LitKind::Str | rustc_ast::token::LitKind::Char
483+
) && matches!(usage, FormatParamUsage::Argument)
484+
// Only inline string and char literals when there are no format options
485+
&& placeholder.format_options == FormatOptions::default()
486+
&& let Some(pos_span) = pos.span
487+
&& let Some(arg_span) = format_arg_removal_span(self.format_args, index)
488+
&& arg.expr.span.eq_ctxt(arg_span) =>
489+
{
490+
// Skip the surrounding `{` and `}` when replacing
491+
let pos_span = pos_span
492+
.with_lo(pos_span.lo() - rustc_span::BytePos(1))
493+
.with_hi(pos_span.hi() + rustc_span::BytePos(1));
494+
let replacement = lit.symbol.to_string();
495+
fixes.push((pos_span, replacement));
496+
fixes.push((arg_span, String::new()));
497+
true
498+
},
499+
_ => {
500+
pos.kind != FormatArgPositionKind::Number
501+
&& (!self.ignore_mixed || matches!(arg.kind, FormatArgumentKind::Captured(_)))
502+
},
503+
}
471504
}
472505
}
473506

@@ -578,17 +611,17 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
578611
}
579612
}
580613

581-
fn format_arg_positions(&self) -> impl Iterator<Item = (&FormatArgPosition, FormatParamUsage)> {
614+
fn format_arg_positions(&self) -> impl Iterator<Item = (&FormatPlaceholder, &FormatArgPosition, FormatParamUsage)> {
582615
self.format_args.template.iter().flat_map(|piece| match piece {
583616
FormatArgsPiece::Placeholder(placeholder) => {
584617
let mut positions = ArrayVec::<_, 3>::new();
585618

586-
positions.push((&placeholder.argument, FormatParamUsage::Argument));
619+
positions.push((placeholder, &placeholder.argument, FormatParamUsage::Argument));
587620
if let Some(FormatCount::Argument(position)) = &placeholder.format_options.width {
588-
positions.push((position, FormatParamUsage::Width));
621+
positions.push((placeholder, position, FormatParamUsage::Width));
589622
}
590623
if let Some(FormatCount::Argument(position)) = &placeholder.format_options.precision {
591-
positions.push((position, FormatParamUsage::Precision));
624+
positions.push((placeholder, position, FormatParamUsage::Precision));
592625
}
593626

594627
positions
@@ -600,7 +633,7 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
600633
/// Returns true if the format argument at `index` is referred to by multiple format params
601634
fn is_aliased(&self, index: usize) -> bool {
602635
self.format_arg_positions()
603-
.filter(|(position, _)| position.index == Ok(index))
636+
.filter(|(_, position, _)| position.index == Ok(index))
604637
.at_most_one()
605638
.is_err()
606639
}

tests/ui-toml/allow_mixed_uninlined_format_args/uninlined_format_args.fixed

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,11 @@ fn main() {
1818
println!("{local_i32}, {}", local_opt.unwrap());
1919
//~^ uninlined_format_args
2020
}
21+
22+
fn issue16310() {
23+
let x = 1;
24+
format!("The answer is: 42 and x is {x}");
25+
//~^ uninlined_format_args
26+
format!("The answer is: 4 and x is {x}");
27+
//~^ uninlined_format_args
28+
}

tests/ui-toml/allow_mixed_uninlined_format_args/uninlined_format_args.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,11 @@ fn main() {
1818
println!("{}, {}", local_i32, local_opt.unwrap());
1919
//~^ uninlined_format_args
2020
}
21+
22+
fn issue16310() {
23+
let x = 1;
24+
format!("The answer is: {} and x is {}", "42", x);
25+
//~^ uninlined_format_args
26+
format!("The answer is: {} and x is {}", '4', x);
27+
//~^ uninlined_format_args
28+
}

tests/ui-toml/allow_mixed_uninlined_format_args/uninlined_format_args.stderr

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ LL | println!("Hello {} is {:.*}", "x", local_i32, local_f64);
2121
help: change this to
2222
|
2323
LL - println!("Hello {} is {:.*}", "x", local_i32, local_f64);
24-
LL + println!("Hello {} is {local_f64:.local_i32$}", "x");
24+
LL + println!("Hello x is {local_f64:.local_i32$}");
2525
|
2626

2727
error: literal with an empty format string
@@ -74,5 +74,29 @@ LL - println!("{}, {}", local_i32, local_opt.unwrap());
7474
LL + println!("{local_i32}, {}", local_opt.unwrap());
7575
|
7676

77-
error: aborting due to 6 previous errors
77+
error: variables can be used directly in the `format!` string
78+
--> tests/ui-toml/allow_mixed_uninlined_format_args/uninlined_format_args.rs:24:5
79+
|
80+
LL | format!("The answer is: {} and x is {}", "42", x);
81+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
82+
|
83+
help: change this to
84+
|
85+
LL - format!("The answer is: {} and x is {}", "42", x);
86+
LL + format!("The answer is: 42 and x is {x}");
87+
|
88+
89+
error: variables can be used directly in the `format!` string
90+
--> tests/ui-toml/allow_mixed_uninlined_format_args/uninlined_format_args.rs:26:5
91+
|
92+
LL | format!("The answer is: {} and x is {}", '4', x);
93+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
94+
|
95+
help: change this to
96+
|
97+
LL - format!("The answer is: {} and x is {}", '4', x);
98+
LL + format!("The answer is: 4 and x is {x}");
99+
|
100+
101+
error: aborting due to 8 previous errors
78102

tests/ui/uninlined_format_args.fixed

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
clippy::eq_op,
77
clippy::format_in_format_args,
88
clippy::print_literal,
9-
clippy::unnecessary_literal_unwrap
9+
clippy::unnecessary_literal_unwrap,
10+
clippy::useless_format
1011
)]
1112

1213
extern crate proc_macros;
@@ -78,7 +79,8 @@ fn tester(fn_arg: i32) {
7879
//~^ uninlined_format_args
7980
println!("{local_f64:.1}");
8081
//~^ uninlined_format_args
81-
println!("Hello {} is {:.*}", "x", local_i32, local_f64);
82+
println!("Hello x is {local_f64:.local_i32$}");
83+
//~^ uninlined_format_args
8284
println!("Hello {} is {:.*}", local_i32, 5, local_f64);
8385
println!("Hello {} is {2:.*}", local_i32, 5, local_f64);
8486
println!("{local_i32} {local_f64}");
@@ -121,7 +123,8 @@ fn tester(fn_arg: i32) {
121123
//~^ uninlined_format_args
122124
println!("{local_f64} {local_i32} {local_f64} {local_i32}");
123125
//~^ uninlined_format_args
124-
println!("{1} {0}", "str", local_i32);
126+
println!("{local_i32} str");
127+
//~^ uninlined_format_args
125128
println!("{local_i32}");
126129
//~^ uninlined_format_args
127130
println!("{local_i32:width$}");
@@ -363,3 +366,16 @@ fn user_format() {
363366
usr_println!(true, "{local_f64:.1}");
364367
//~^ uninlined_format_args
365368
}
369+
370+
fn issue16310() {
371+
format!("The answer is: 42");
372+
//~^ uninlined_format_args
373+
format!("The answer is: 4");
374+
//~^ uninlined_format_args
375+
376+
let n_refs = 10;
377+
let receiver_snippet = "receiver_snippet";
378+
format!("{:&>n_refs$}{receiver_snippet}", "");
379+
380+
format!("env: {}", env!("USER"));
381+
}

tests/ui/uninlined_format_args.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
clippy::eq_op,
77
clippy::format_in_format_args,
88
clippy::print_literal,
9-
clippy::unnecessary_literal_unwrap
9+
clippy::unnecessary_literal_unwrap,
10+
clippy::useless_format
1011
)]
1112

1213
extern crate proc_macros;
@@ -81,6 +82,7 @@ fn tester(fn_arg: i32) {
8182
println!("{:.1}", local_f64);
8283
//~^ uninlined_format_args
8384
println!("Hello {} is {:.*}", "x", local_i32, local_f64);
85+
//~^ uninlined_format_args
8486
println!("Hello {} is {:.*}", local_i32, 5, local_f64);
8587
println!("Hello {} is {2:.*}", local_i32, 5, local_f64);
8688
println!("{} {}", local_i32, local_f64);
@@ -124,6 +126,7 @@ fn tester(fn_arg: i32) {
124126
println!("{1} {0} {1} {0}", local_i32, local_f64);
125127
//~^ uninlined_format_args
126128
println!("{1} {0}", "str", local_i32);
129+
//~^ uninlined_format_args
127130
println!("{v}", v = local_i32);
128131
//~^ uninlined_format_args
129132
println!("{local_i32:0$}", width);
@@ -368,3 +371,16 @@ fn user_format() {
368371
usr_println!(true, "{:.1}", local_f64);
369372
//~^ uninlined_format_args
370373
}
374+
375+
fn issue16310() {
376+
format!("The answer is: {}", "42");
377+
//~^ uninlined_format_args
378+
format!("The answer is: {}", '4');
379+
//~^ uninlined_format_args
380+
381+
let n_refs = 10;
382+
let receiver_snippet = "receiver_snippet";
383+
format!("{:&>n_refs$}{receiver_snippet}", "");
384+
385+
format!("env: {}", env!("USER"));
386+
}

0 commit comments

Comments
 (0)