Skip to content

Commit 9f2bd79

Browse files
committed
Address review comments
1 parent 6045478 commit 9f2bd79

File tree

9 files changed

+379
-362
lines changed

9 files changed

+379
-362
lines changed

clippy_lints/src/format.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
2-
use clippy_utils::higher::FormatExpn;
2+
use clippy_utils::higher::{FormatArgsArg, FormatExpn};
33
use clippy_utils::source::{snippet_opt, snippet_with_applicability};
44
use clippy_utils::sugg::Sugg;
55
use if_chain::if_chain;
@@ -68,8 +68,9 @@ impl<'tcx> LateLintPass<'tcx> for UselessFormat {
6868
ty::Str => true,
6969
_ => false,
7070
};
71-
if format_args.args().all(|arg| arg.is_display());
72-
if !format_args.has_string_formatting();
71+
if let Some(args) = format_args.args();
72+
if args.iter().all(FormatArgsArg::is_display);
73+
if !args.iter().any(FormatArgsArg::has_string_formatting);
7374
then {
7475
let is_new_string = match value.kind {
7576
ExprKind::Binary(..) => true,

clippy_lints/src/format_args.rs

Lines changed: 105 additions & 189 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ use clippy_utils::source::snippet_opt;
44
use clippy_utils::ty::implements_trait;
55
use clippy_utils::{get_trait_def_id, match_def_path, paths};
66
use if_chain::if_chain;
7+
use rustc_ast::ast::LitKind;
78
use rustc_errors::Applicability;
9+
use rustc_hir::def_id::DefId;
810
use rustc_hir::{Expr, ExprKind};
911
use rustc_lint::{LateContext, LateLintPass};
12+
use rustc_middle::ty::subst::GenericArg;
13+
use rustc_middle::ty::Ty;
1014
use rustc_session::{declare_lint_pass, declare_tool_lint};
11-
use rustc_span::{BytePos, ExpnKind, Span, Symbol};
15+
use rustc_span::{sym, BytePos, ExpnKind, Span, Symbol};
1216

1317
declare_clippy_lint! {
1418
/// ### What it does
@@ -34,14 +38,6 @@ declare_clippy_lint! {
3438
"`format!` used in a macro that does formatting"
3539
}
3640

37-
declare_lint_pass!(FormatInFormatArgs => [FORMAT_IN_FORMAT_ARGS]);
38-
39-
impl<'tcx> LateLintPass<'tcx> for FormatInFormatArgs {
40-
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
41-
check_expr(cx, expr, format_in_format_args);
42-
}
43-
}
44-
4541
declare_clippy_lint! {
4642
/// ### What it does
4743
/// Checks for [`ToString::to_string`](https://doc.rust-lang.org/std/string/trait.ToString.html#tymethod.to_string)
@@ -67,216 +63,115 @@ declare_clippy_lint! {
6763
"`to_string` applied to a type that implements `Display` in format args"
6864
}
6965

70-
declare_lint_pass!(ToStringInFormatArgs => [TO_STRING_IN_FORMAT_ARGS]);
71-
72-
impl<'tcx> LateLintPass<'tcx> for ToStringInFormatArgs {
73-
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
74-
check_expr(cx, expr, to_string_in_format_args);
75-
}
76-
}
77-
78-
fn check_expr<'tcx, F>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, check_value: F)
79-
where
80-
F: Fn(&LateContext<'_>, &FormatArgsExpn<'_>, Span, Symbol, usize, &FormatArgsArg<'_>) -> bool,
81-
{
82-
if_chain! {
83-
if let Some(format_args) = FormatArgsExpn::parse(expr);
84-
let call_site = expr.span.ctxt().outer_expn_data().call_site;
85-
if call_site.from_expansion();
86-
let expn_data = call_site.ctxt().outer_expn_data();
87-
if let ExpnKind::Macro(_, name) = expn_data.kind;
88-
if !format_args.has_string_formatting();
89-
then {
90-
for (i, arg) in format_args.args().enumerate() {
91-
if !arg.is_display() {
92-
continue;
66+
declare_lint_pass!(FormatArgs => [FORMAT_IN_FORMAT_ARGS, TO_STRING_IN_FORMAT_ARGS]);
67+
68+
impl<'tcx> LateLintPass<'tcx> for FormatArgs {
69+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
70+
if_chain! {
71+
if let Some(format_args) = FormatArgsExpn::parse(expr);
72+
let expn_data = {
73+
let expn_data = expr.span.ctxt().outer_expn_data();
74+
if expn_data.call_site.from_expansion() {
75+
expn_data.call_site.ctxt().outer_expn_data()
76+
} else {
77+
expn_data
9378
}
94-
if check_value(cx, &format_args, expn_data.call_site, name, i, &arg) {
95-
break;
79+
};
80+
if let ExpnKind::Macro(_, name) = expn_data.kind;
81+
if let Some(args) = format_args.args();
82+
if !args.iter().any(FormatArgsArg::has_string_formatting);
83+
then {
84+
for (i, arg) in args.iter().enumerate() {
85+
if !arg.is_display() {
86+
continue;
87+
}
88+
if is_aliased(&args, i) {
89+
continue;
90+
}
91+
check_format_in_format_args(cx, expn_data.call_site, name, arg);
92+
check_to_string_in_format_args(cx, expn_data.call_site, name, arg);
9693
}
9794
}
9895
}
9996
}
10097
}
10198

102-
fn format_in_format_args(
103-
cx: &LateContext<'_>,
104-
format_args: &FormatArgsExpn<'_>,
105-
call_site: Span,
106-
name: Symbol,
107-
i: usize,
108-
arg: &FormatArgsArg<'_>,
109-
) -> bool {
110-
if let Some(FormatExpn {
111-
format_args: inner_format_args,
112-
..
113-
}) = FormatExpn::parse(arg.value())
114-
{
115-
if let Some((sugg, applicability)) = format_in_format_args_sugg(cx, name, format_args, i, &inner_format_args) {
116-
span_lint_and_sugg(
117-
cx,
118-
FORMAT_IN_FORMAT_ARGS,
119-
trim_semicolon(cx, call_site),
120-
&format!("`format!` in `{}!` args", name),
121-
"inline the `format!` call",
122-
sugg,
123-
applicability,
124-
);
125-
} else {
126-
span_lint_and_help(
127-
cx,
128-
FORMAT_IN_FORMAT_ARGS,
129-
trim_semicolon(cx, call_site),
130-
&format!("`format!` in `{}!` args", name),
131-
None,
132-
"inline the `format!` call",
133-
);
134-
}
135-
// Report only the first instance.
136-
return true;
99+
fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symbol, arg: &FormatArgsArg<'_, '_>) {
100+
if FormatExpn::parse(arg.value).is_some() {
101+
span_lint_and_help(
102+
cx,
103+
FORMAT_IN_FORMAT_ARGS,
104+
trim_semicolon(cx, call_site),
105+
&format!("`format!` in `{}!` args", name),
106+
None,
107+
"inline the `format!` call",
108+
);
137109
}
138-
false
139110
}
140111

141-
fn to_string_in_format_args(
142-
cx: &LateContext<'_>,
143-
_format_args: &FormatArgsExpn<'_>,
112+
fn check_to_string_in_format_args<'tcx>(
113+
cx: &LateContext<'tcx>,
144114
_call_site: Span,
145115
name: Symbol,
146-
_i: usize,
147-
arg: &FormatArgsArg<'_>,
148-
) -> bool {
149-
let value = arg.value();
116+
arg: &FormatArgsArg<'_, 'tcx>,
117+
) {
118+
let value = arg.value;
150119
if_chain! {
151120
if let ExprKind::MethodCall(_, _, args, _) = value.kind;
152121
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id);
153122
if match_def_path(cx, method_def_id, &paths::TO_STRING_METHOD);
154123
if let Some(receiver) = args.get(0);
155124
let ty = cx.typeck_results().expr_ty(receiver);
156125
if let Some(display_trait_id) = get_trait_def_id(cx, &paths::DISPLAY_TRAIT);
157-
if implements_trait(cx, ty, display_trait_id, &[]);
158126
if let Some(snippet) = snippet_opt(cx, value.span);
159127
if let Some(dot) = snippet.rfind('.');
160128
then {
161129
let span = value.span.with_lo(
162130
value.span.lo() + BytePos(u32::try_from(dot).unwrap())
163131
);
164-
span_lint_and_sugg(
165-
cx,
166-
TO_STRING_IN_FORMAT_ARGS,
167-
span,
168-
&format!("`to_string` applied to a type that implements `Display` in `{}!` args", name),
169-
"remove this",
170-
String::new(),
171-
Applicability::MachineApplicable,
172-
);
173-
}
174-
}
175-
false
176-
}
177-
178-
fn format_in_format_args_sugg(
179-
cx: &LateContext<'_>,
180-
name: Symbol,
181-
format_args: &FormatArgsExpn<'_>,
182-
i: usize,
183-
inner_format_args: &FormatArgsExpn<'_>,
184-
) -> Option<(String, Applicability)> {
185-
let format_string = snippet_opt(cx, format_args.format_string_span)?;
186-
if is_positional(&format_string) {
187-
return None;
188-
}
189-
let (left, right) = split_format_string(&format_string, i);
190-
let inner_format_string = snippet_opt(cx, inner_format_args.format_string_span)?;
191-
if is_positional(&inner_format_string) {
192-
return None;
193-
}
194-
// If the inner format string is raw, the user is on their own.
195-
let (new_format_string, applicability) = if inner_format_string.starts_with('r') {
196-
(left + ".." + &right, Applicability::HasPlaceholders)
197-
} else {
198-
(
199-
left + &trim_quotes(&inner_format_string) + &right,
200-
Applicability::MachineApplicable,
201-
)
202-
};
203-
let values = format_args
204-
.value_args
205-
.iter()
206-
.map(|value| snippet_opt(cx, value.span))
207-
.collect::<Option<Vec<_>>>()?;
208-
let inner_values = inner_format_args
209-
.value_args
210-
.iter()
211-
.map(|value| snippet_opt(cx, value.span))
212-
.collect::<Option<Vec<_>>>()?;
213-
let new_values = itertools::join(
214-
values
215-
.iter()
216-
.take(i)
217-
.chain(inner_values.iter())
218-
.chain(values.iter().skip(i + 1)),
219-
", ",
220-
);
221-
Some((
222-
format!(r#"{}!({}, {})"#, name, new_format_string, new_values),
223-
applicability,
224-
))
225-
}
226-
227-
// Checking the position fields of the `core::fmt::rt::v1::Argument` would not be sufficient
228-
// because, for example, "{}{}" and "{}{1:}" could not be distinguished. Note that the `{}` could be
229-
// replaced in the former, but not the latter.
230-
fn is_positional(format_string: &str) -> bool {
231-
let mut iter = format_string.chars();
232-
while let Some(first_char) = iter.next() {
233-
if first_char != '{' {
234-
continue;
235-
}
236-
let second_char = iter.next().unwrap();
237-
if second_char.is_digit(10) {
238-
return true;
132+
if implements_trait(cx, ty, display_trait_id, &[]) {
133+
span_lint_and_sugg(
134+
cx,
135+
TO_STRING_IN_FORMAT_ARGS,
136+
span,
137+
&format!("`to_string` applied to a type that implements `Display` in `{}!` args", name),
138+
"remove this",
139+
String::new(),
140+
Applicability::MachineApplicable,
141+
);
142+
} else if implements_trait_via_deref(cx, ty, display_trait_id, &[]) {
143+
span_lint_and_sugg(
144+
cx,
145+
TO_STRING_IN_FORMAT_ARGS,
146+
span,
147+
&format!("`to_string` applied to a type that implements `Display` in `{}!` args", name),
148+
"use this",
149+
String::from(".deref()"),
150+
Applicability::MachineApplicable,
151+
);
152+
}
239153
}
240154
}
241-
false
242-
}
243-
244-
fn split_format_string(format_string: &str, i: usize) -> (String, String) {
245-
let mut iter = format_string.chars();
246-
for j in 0..=i {
247-
assert!(advance(&mut iter) == '}' || j < i);
248-
}
249-
250-
let right = iter.collect::<String>();
251-
252-
let size = format_string.len();
253-
let right_size = right.len();
254-
let left_size = size - right_size;
255-
assert!(left_size >= 2);
256-
let left = std::str::from_utf8(&format_string.as_bytes()[..left_size - 2])
257-
.unwrap()
258-
.to_owned();
259-
(left, right)
260155
}
261156

262-
fn advance(iter: &mut std::str::Chars<'_>) -> char {
263-
loop {
264-
let first_char = iter.next().unwrap();
265-
if first_char != '{' {
266-
continue;
267-
}
268-
let second_char = iter.next().unwrap();
269-
if second_char == '{' {
270-
continue;
157+
// Returns true if `args[i]` "refers to" or "is referred to by" another argument.
158+
fn is_aliased(args: &[FormatArgsArg<'_, '_>], i: usize) -> bool {
159+
args.iter().enumerate().any(|(j, arg)| {
160+
if_chain! {
161+
if let Some(fmt) = arg.fmt;
162+
// struct `core::fmt::rt::v1::Argument`
163+
if let ExprKind::Struct(_, fields, _) = fmt.kind;
164+
if let Some(position_field) = fields.iter().find(|f| f.ident.name == sym::position);
165+
if let ExprKind::Lit(lit) = &position_field.expr.kind;
166+
if let LitKind::Int(position, _) = lit.node;
167+
then {
168+
let position = usize::try_from(position).unwrap();
169+
(j == i && position != i) || (j != i && position == i)
170+
} else {
171+
false
172+
}
271173
}
272-
return second_char;
273-
}
274-
}
275-
276-
fn trim_quotes(string_literal: &str) -> String {
277-
let len = string_literal.chars().count();
278-
assert!(len >= 2);
279-
string_literal.chars().skip(1).take(len - 2).collect()
174+
})
280175
}
281176

282177
fn trim_semicolon(cx: &LateContext<'_>, span: Span) -> Span {
@@ -285,3 +180,24 @@ fn trim_semicolon(cx: &LateContext<'_>, span: Span) -> Span {
285180
span.with_hi(span.lo() + BytePos(u32::try_from(snippet.len()).unwrap()))
286181
})
287182
}
183+
184+
fn implements_trait_via_deref<'tcx>(
185+
cx: &LateContext<'tcx>,
186+
ty: Ty<'tcx>,
187+
trait_id: DefId,
188+
ty_params: &[GenericArg<'tcx>],
189+
) -> bool {
190+
if_chain! {
191+
if let Some(deref_trait_id) = cx.tcx.lang_items().deref_trait();
192+
if implements_trait(cx, ty, deref_trait_id, &[]);
193+
if let Some(deref_target_id) = cx.tcx.lang_items().deref_target();
194+
then {
195+
let substs = cx.tcx.mk_substs_trait(ty, &[]);
196+
let projection_ty = cx.tcx.mk_projection(deref_target_id, substs);
197+
let target_ty = cx.tcx.normalize_erasing_regions(cx.param_env, projection_ty);
198+
implements_trait(cx, target_ty, trait_id, ty_params)
199+
} else {
200+
false
201+
}
202+
}
203+
}

clippy_lints/src/lib.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -772,8 +772,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
772772
let enable_raw_pointer_heuristic_for_send = conf.enable_raw_pointer_heuristic_for_send;
773773
store.register_late_pass(move || Box::new(non_send_fields_in_send_ty::NonSendFieldInSendTy::new(enable_raw_pointer_heuristic_for_send)));
774774
store.register_late_pass(move || Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks::default()));
775-
store.register_late_pass(move || Box::new(format_args::FormatInFormatArgs));
776-
store.register_late_pass(move || Box::new(format_args::ToStringInFormatArgs));
775+
store.register_late_pass(move || Box::new(format_args::FormatArgs));
777776
}
778777

779778
#[rustfmt::skip]

0 commit comments

Comments
 (0)