Skip to content

Commit e8518b7

Browse files
committed
wip
1 parent 3fca1e9 commit e8518b7

File tree

1 file changed

+51
-25
lines changed

1 file changed

+51
-25
lines changed

clippy_lints/src/format_args.rs

+51-25
Original file line numberDiff line numberDiff line change
@@ -145,28 +145,8 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
145145
check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg.param.value);
146146
check_to_string_in_format_args(cx, name, arg.param.value);
147147
}
148-
149-
if !meets_msrv(self.msrv, msrvs::FORMAT_ARGS_CAPTURE) {
150-
return;
151-
}
152-
let mut inline_spans = Vec::new();
153-
// If any of the arguments are referenced by an index number,
154-
// and that argument is not a simple variable and cannot be inlined,
155-
// we cannot remove any other arguments in the format string,
156-
// because the index numbers might be wrong after inlining.
157-
// Example of an un-inlinable format: print!("{}{1}", foo, 2)
158-
let do_inlining = format_args.params().all(|p| check_inline(cx, &p, &mut inline_spans));
159-
if do_inlining && !inline_spans.is_empty()
160-
{
161-
span_lint_and_then(
162-
cx,
163-
UNINLINED_FORMAT_ARGS,
164-
outermost_expn_data.call_site,
165-
"variables can be used directly in the `format!` string",
166-
|diag| {
167-
diag.multipart_suggestion("change this to", inline_spans, Applicability::MachineApplicable);
168-
},
169-
);
148+
if meets_msrv(self.msrv, msrvs::FORMAT_ARGS_CAPTURE) {
149+
check_uninlined_args(cx, &format_args, outermost_expn_data.call_site);
170150
}
171151
}
172152
}
@@ -175,7 +155,53 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
175155
extract_msrv_attr!(LateContext);
176156
}
177157

178-
fn check_inline(cx: &LateContext<'_>, param: &FormatParam<'_>, inline_spans: &mut Vec<(Span, String)>) -> bool {
158+
fn check_uninlined_args(cx: &LateContext<'_>, args: &FormatArgsExpn<'_>, call_site: Span) {
159+
let mut fixes = Vec::new();
160+
// If any of the arguments are referenced by an index number,
161+
// and that argument is not a simple variable and cannot be inlined,
162+
// we cannot remove any other arguments in the format string,
163+
// because the index numbers might be wrong after inlining.
164+
// Example of an un-inlinable format: print!("{}{1}", foo, 2)
165+
if !args.params().all(|p| check_one_arg(cx, &p, &mut fixes)) || fixes.is_empty() {
166+
return;
167+
}
168+
169+
// Handle a very rare case: `format!(indoc!("{}"), foo)` must not be inlined.
170+
// If inlined, it will cause a compilation error:
171+
// > to avoid ambiguity, `format_args!` cannot capture variables
172+
// > when the format string is expanded from a macro
173+
// @Alexendoo explanation:
174+
// > indoc! is a proc macro that is producing a string literal with its span
175+
// > set to its input it's not marked as from expansion, and since it's compatible
176+
// > tokenization wise clippy_utils::is_from_proc_macro wouldn't catch it either
177+
// Hacky solution (TBD if it should be improved):
178+
// iterate over all tokens between the format string and the first argument,
179+
// and ensure we do not see any closing brackets/braces/parens.
180+
// This might be a relatively expensive test, so do it only we are ready to replace.
181+
182+
/*
183+
let fmt_str_span = args.format_string.span;
184+
println!("1st: {}", snippet_opt(cx, fmt_str_span).unwrap());
185+
let first_arg_span = walk_chain(fmt_str_span, fmt_str_span.ctxt());
186+
println!("2nd: {}", snippet_opt(cx, first_arg_span).unwrap());
187+
let between = fmt_str_span.with_lo(fmt_str_span.hi()).with_hi(first_arg_span.lo());
188+
println!("3rd: {}", snippet_opt(cx, between).unwrap());
189+
if let Some(between_snippet) = snippet_opt(cx, between) {
190+
if !tokenize(&between_snippet).any(|t| matches!(t.kind, CloseParen | CloseBrace | CloseBracket))
191+
*/
192+
193+
span_lint_and_then(
194+
cx,
195+
UNINLINED_FORMAT_ARGS,
196+
call_site,
197+
"variables can be used directly in the `format!` string",
198+
|diag| {
199+
diag.multipart_suggestion("change this to", fixes, Applicability::MachineApplicable);
200+
},
201+
);
202+
}
203+
204+
fn check_one_arg(cx: &LateContext<'_>, param: &FormatParam<'_>, fixes: &mut Vec<(Span, String)>) -> bool {
179205
if matches!(param.kind, Implicit | Starred | Named(_) | Numbered)
180206
&& let ExprKind::Path(QPath::Resolved(None, path)) = param.value.kind
181207
&& let Path { span, segments, .. } = path
@@ -186,9 +212,9 @@ fn check_inline(cx: &LateContext<'_>, param: &FormatParam<'_>, inline_spans: &mu
186212
FormatParamUsage::Width => format!("{}$", segment.ident.name),
187213
FormatParamUsage::Precision => format!(".{}$", segment.ident.name),
188214
};
189-
inline_spans.push((param.span, replacement));
215+
fixes.push((param.span, replacement));
190216
let arg_span = expand_past_previous_comma(cx, *span);
191-
inline_spans.push((arg_span, String::new()));
217+
fixes.push((arg_span, String::new()));
192218
true // successful inlining, continue checking
193219
} else {
194220
// if we can't inline a numbered argument, we can't continue

0 commit comments

Comments
 (0)