Skip to content

Commit 5db1f71

Browse files
committed
Address review comments
1 parent 3109c5a commit 5db1f71

File tree

8 files changed

+378
-153
lines changed

8 files changed

+378
-153
lines changed

clippy_lints/src/format.rs

Lines changed: 1 addition & 1 deletion
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::{FormatArgsArg, FormatExpn};
2+
use clippy_utils::higher::FormatExpn;
33
use clippy_utils::source::{snippet_opt, snippet_with_applicability};
44
use clippy_utils::sugg::Sugg;
55
use if_chain::if_chain;

clippy_lints/src/format_args.rs

Lines changed: 98 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
1-
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
1+
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
22
use clippy_utils::higher::{FormatArgsArg, FormatArgsExpn, FormatExpn};
33
use clippy_utils::source::snippet_opt;
44
use clippy_utils::ty::implements_trait;
5-
use clippy_utils::{get_trait_def_id, match_def_path, paths};
5+
use clippy_utils::{get_trait_def_id, is_diag_trait_item, match_def_path, paths};
66
use if_chain::if_chain;
7-
use rustc_ast::ast::LitKind;
87
use rustc_errors::Applicability;
9-
use rustc_hir::def_id::DefId;
108
use rustc_hir::{Expr, ExprKind};
119
use rustc_lint::{LateContext, LateLintPass};
12-
use rustc_middle::ty::subst::GenericArg;
10+
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
1311
use rustc_middle::ty::Ty;
1412
use rustc_session::{declare_lint_pass, declare_tool_lint};
15-
use rustc_span::{sym, BytePos, ExpnKind, Span, Symbol};
13+
use rustc_span::{sym, BytePos, ExpnData, ExpnKind, Span, Symbol};
1614

1715
declare_clippy_lint! {
1816
/// ### What it does
@@ -65,112 +63,129 @@ declare_clippy_lint! {
6563

6664
declare_lint_pass!(FormatArgs => [FORMAT_IN_FORMAT_ARGS, TO_STRING_IN_FORMAT_ARGS]);
6765

66+
const FORMAT_MACROS: &[&[&str]] = &[
67+
&["alloc", "macros", "format"],
68+
&["core", "macros", "assert_eq"],
69+
&["core", "macros", "assert_ne"],
70+
&["core", "macros", "write"],
71+
&["core", "macros", "writeln"],
72+
&["core", "macros", "builtin", "assert"],
73+
&["core", "macros", "builtin", "format_args"],
74+
&["std", "macros", "eprint"],
75+
&["std", "macros", "eprintln"],
76+
&["std", "macros", "panic"],
77+
&["std", "macros", "print"],
78+
&["std", "macros", "println"],
79+
];
80+
6881
impl<'tcx> LateLintPass<'tcx> for FormatArgs {
6982
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
7083
if_chain! {
7184
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
78-
}
79-
};
80-
if let ExpnKind::Macro(_, name) = expn_data.kind;
85+
let expr_expn_data = expr.span.ctxt().outer_expn_data();
86+
let outermost_expn_data = outermost_expn_data(expr_expn_data);
87+
if let Some(macro_def_id) = outermost_expn_data.macro_def_id;
88+
if FORMAT_MACROS.iter().any(|path| match_def_path(cx, macro_def_id, path));
89+
if let ExpnKind::Macro(_, name) = outermost_expn_data.kind;
8190
if let Some(args) = format_args.args();
82-
if !args.iter().any(FormatArgsArg::has_string_formatting);
8391
then {
8492
for (i, arg) in args.iter().enumerate() {
8593
if !arg.is_display() {
8694
continue;
8795
}
96+
if arg.has_string_formatting() {
97+
continue;
98+
}
8899
if is_aliased(&args, i) {
89100
continue;
90101
}
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);
102+
check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg);
103+
check_to_string_in_format_args(cx, name, arg);
93104
}
94105
}
95106
}
96107
}
97108
}
98109

99-
fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symbol, arg: &FormatArgsArg<'_, '_>) {
110+
fn outermost_expn_data(expn_data: ExpnData) -> ExpnData {
111+
if expn_data.call_site.from_expansion() {
112+
outermost_expn_data(expn_data.call_site.ctxt().outer_expn_data())
113+
} else {
114+
expn_data
115+
}
116+
}
117+
118+
fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symbol, arg: &FormatArgsArg<'_>) {
100119
if FormatExpn::parse(arg.value).is_some() {
101-
span_lint_and_help(
120+
span_lint_and_then(
102121
cx,
103122
FORMAT_IN_FORMAT_ARGS,
104123
trim_semicolon(cx, call_site),
105124
&format!("`format!` in `{}!` args", name),
106-
None,
107-
"inline the `format!` call",
125+
|diag| {
126+
diag.help(&format!(
127+
"combine the `format!(..)` arguments with the outer `{}!(..)` call",
128+
name
129+
));
130+
diag.help("or consider changing `format!` to `format_args!`");
131+
},
108132
);
109133
}
110134
}
111135

112-
fn check_to_string_in_format_args<'tcx>(
113-
cx: &LateContext<'tcx>,
114-
name: Symbol,
115-
arg: &FormatArgsArg<'_, 'tcx>,
116-
) {
136+
fn check_to_string_in_format_args<'tcx>(cx: &LateContext<'tcx>, name: Symbol, arg: &FormatArgsArg<'tcx>) {
117137
let value = arg.value;
118138
if_chain! {
119-
if let ExprKind::MethodCall(_, _, [receiver], span) = value.kind;
139+
if let ExprKind::MethodCall(_, _, [receiver], _) = value.kind;
120140
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id);
121141
if is_diag_trait_item(cx, method_def_id, sym::ToString);
122-
if let Some(receiver) = args.get(0);
123-
let ty = cx.typeck_results().expr_ty(receiver);
142+
let receiver_ty = cx.typeck_results().expr_ty(receiver);
124143
if let Some(display_trait_id) = get_trait_def_id(cx, &paths::DISPLAY_TRAIT);
125-
if let Some(snippet) = snippet_opt(cx, value.span);
126-
if let Some(dot) = snippet.rfind('.');
144+
if let Some(value_snippet) = snippet_opt(cx, value.span);
145+
if let Some(dot) = value_snippet.rfind('.');
146+
if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
127147
then {
128-
let span = value.span.with_lo(
129-
value.span.lo() + BytePos(u32::try_from(dot).unwrap())
148+
let (n_derefs, target) = count_derefs(
149+
0,
150+
receiver_ty,
151+
&mut cx.typeck_results().expr_adjustments(receiver).iter(),
130152
);
131-
if implements_trait(cx, ty, display_trait_id, &[]) {
132-
span_lint_and_sugg(
133-
cx,
134-
TO_STRING_IN_FORMAT_ARGS,
135-
span,
136-
&format!("`to_string` applied to a type that implements `Display` in `{}!` args", name),
137-
"remove this",
138-
String::new(),
139-
Applicability::MachineApplicable,
140-
);
141-
} else if implements_trait_via_deref(cx, ty, display_trait_id, &[]) {
142-
span_lint_and_sugg(
143-
cx,
144-
TO_STRING_IN_FORMAT_ARGS,
145-
span,
146-
&format!("`to_string` applied to a type that implements `Display` in `{}!` args", name),
147-
"use this",
148-
String::from(".deref()"),
149-
Applicability::MachineApplicable,
150-
);
153+
if implements_trait(cx, target, display_trait_id, &[]) {
154+
if n_derefs == 0 {
155+
let span = value.span.with_lo(
156+
value.span.lo() + BytePos(u32::try_from(dot).unwrap())
157+
);
158+
span_lint_and_sugg(
159+
cx,
160+
TO_STRING_IN_FORMAT_ARGS,
161+
span,
162+
&format!("`to_string` applied to a type that implements `Display` in `{}!` args", name),
163+
"remove this",
164+
String::new(),
165+
Applicability::MachineApplicable,
166+
);
167+
} else {
168+
span_lint_and_sugg(
169+
cx,
170+
TO_STRING_IN_FORMAT_ARGS,
171+
value.span,
172+
&format!("`to_string` applied to a type that implements `Display` in `{}!` args", name),
173+
"use this",
174+
format!("{:*>width$}{}", "", receiver_snippet, width = n_derefs),
175+
Applicability::MachineApplicable,
176+
);
177+
}
151178
}
152179
}
153180
}
154181
}
155182

156183
// Returns true if `args[i]` "refers to" or "is referred to by" another argument.
157-
fn is_aliased(args: &[FormatArgsArg<'_, '_>], i: usize) -> bool {
158-
args.iter().enumerate().any(|(j, arg)| {
159-
if_chain! {
160-
if let Some(fmt) = arg.fmt;
161-
// struct `core::fmt::rt::v1::Argument`
162-
if let ExprKind::Struct(_, fields, _) = fmt.kind;
163-
if let Some(position_field) = fields.iter().find(|f| f.ident.name == sym::position);
164-
if let ExprKind::Lit(lit) = &position_field.expr.kind;
165-
if let LitKind::Int(position, _) = lit.node;
166-
then {
167-
let position = usize::try_from(position).unwrap();
168-
(j == i && position != i) || (j != i && position == i)
169-
} else {
170-
false
171-
}
172-
}
173-
})
184+
fn is_aliased(args: &[FormatArgsArg<'_>], i: usize) -> bool {
185+
let value = args[i].value;
186+
args.iter()
187+
.enumerate()
188+
.any(|(j, arg)| i != j && std::ptr::eq(value, arg.value))
174189
}
175190

176191
fn trim_semicolon(cx: &LateContext<'_>, span: Span) -> Span {
@@ -180,23 +195,17 @@ fn trim_semicolon(cx: &LateContext<'_>, span: Span) -> Span {
180195
})
181196
}
182197

183-
fn implements_trait_via_deref<'tcx>(
184-
cx: &LateContext<'tcx>,
185-
ty: Ty<'tcx>,
186-
trait_id: DefId,
187-
ty_params: &[GenericArg<'tcx>],
188-
) -> bool {
189-
if_chain! {
190-
if let Some(deref_trait_id) = cx.tcx.lang_items().deref_trait();
191-
if implements_trait(cx, ty, deref_trait_id, &[]);
192-
if let Some(deref_target_id) = cx.tcx.lang_items().deref_target();
193-
then {
194-
let substs = cx.tcx.mk_substs_trait(ty, &[]);
195-
let projection_ty = cx.tcx.mk_projection(deref_target_id, substs);
196-
let target_ty = cx.tcx.normalize_erasing_regions(cx.param_env, projection_ty);
197-
implements_trait(cx, target_ty, trait_id, ty_params)
198-
} else {
199-
false
200-
}
198+
fn count_derefs<'tcx, I>(n: usize, ty: Ty<'tcx>, iter: &mut I) -> (usize, Ty<'tcx>)
199+
where
200+
I: Iterator<Item = &'tcx Adjustment<'tcx>>,
201+
{
202+
if let Some(Adjustment {
203+
kind: Adjust::Deref(Some(_)),
204+
target,
205+
}) = iter.next()
206+
{
207+
count_derefs(n + 1, target, iter)
208+
} else {
209+
(n, ty)
201210
}
202211
}

clippy_utils/src/higher.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -571,13 +571,13 @@ impl FormatArgsExpn<'tcx> {
571571
}
572572

573573
/// Returns a vector of `FormatArgsArg`.
574-
pub fn args<'fmt>(&'fmt self) -> Option<Vec<FormatArgsArg<'fmt, 'tcx>>> {
574+
pub fn args(&self) -> Option<Vec<FormatArgsArg<'tcx>>> {
575575
if_chain! {
576576
if let Some(expr) = self.fmt_expr;
577577
if let ExprKind::AddrOf(BorrowKind::Ref, _, expr) = expr.kind;
578578
if let ExprKind::Array(exprs) = expr.kind;
579579
then {
580-
exprs.iter().map(|fmt| {
580+
return exprs.iter().map(|fmt| {
581581
if_chain! {
582582
// struct `core::fmt::rt::v1::Argument`
583583
if let ExprKind::Struct(_, fields, _) = fmt.kind;
@@ -592,17 +592,20 @@ impl FormatArgsExpn<'tcx> {
592592
}
593593
}
594594
}).collect()
595-
} else {
596-
Some(self.value_args.iter().zip(self.args.iter()).map(|(value, arg)| {
597-
FormatArgsArg { value, arg, fmt: None }
598-
}).collect())
599595
}
600596
}
597+
Some(
598+
self.value_args
599+
.iter()
600+
.zip(self.args.iter())
601+
.map(|(value, arg)| FormatArgsArg { value, arg, fmt: None })
602+
.collect(),
603+
)
601604
}
602605
}
603606

604607
/// Type representing a `FormatArgsExpn`'s format arguments
605-
pub struct FormatArgsArg<'fmt, 'tcx> {
608+
pub struct FormatArgsArg<'tcx> {
606609
/// An element of `value_args` according to `position`
607610
pub value: &'tcx Expr<'tcx>,
608611
/// An element of `args` according to `position`
@@ -611,7 +614,7 @@ pub struct FormatArgsArg<'fmt, 'tcx> {
611614
pub fmt: Option<&'tcx Expr<'tcx>>,
612615
}
613616

614-
impl<'fmt, 'tcx> FormatArgsArg<'fmt, 'tcx> {
617+
impl<'tcx> FormatArgsArg<'tcx> {
615618
/// Returns true if any formatting parameters are used that would have an effect on strings,
616619
/// like `{:+2}` instead of just `{}`.
617620
pub fn has_string_formatting(&self) -> bool {

tests/ui/format_args.fixed

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
// run-rustfix
22

33
#![allow(unreachable_code)]
4+
#![allow(unused_macros)]
45
#![allow(unused_variables)]
56
#![allow(clippy::assertions_on_constants)]
67
#![allow(clippy::eq_op)]
78
#![warn(clippy::to_string_in_format_args)]
89

10+
use std::io::{stdout, Write};
911
use std::ops::Deref;
1012
use std::panic::Location;
1113

@@ -27,34 +29,66 @@ impl Deref for X {
2729
}
2830
}
2931

30-
struct Y(u32);
32+
struct Y(X);
3133

3234
impl Deref for Y {
35+
type Target = X;
36+
37+
fn deref(&self) -> &X {
38+
&self.0
39+
}
40+
}
41+
42+
struct Z(u32);
43+
44+
impl Deref for Z {
3345
type Target = u32;
3446

3547
fn deref(&self) -> &u32 {
3648
&self.0
3749
}
3850
}
3951

40-
impl std::fmt::Display for Y {
52+
impl std::fmt::Display for Z {
4153
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42-
write!(f, "Y")
54+
write!(f, "Z")
4355
}
4456
}
4557

4658
fn main() {
4759
let x = 'x';
4860

49-
println!("error: something failed at {}", Location::caller());
5061
let _ = format!("error: something failed at {}", Location::caller());
62+
let _ = write!(
63+
stdout(),
64+
"error: something failed at {}",
65+
Location::caller()
66+
);
67+
let _ = writeln!(
68+
stdout(),
69+
"error: something failed at {}",
70+
Location::caller()
71+
);
72+
print!("error: something failed at {}", Location::caller());
73+
println!("error: something failed at {}", Location::caller());
74+
eprint!("error: something failed at {}", Location::caller());
75+
eprintln!("error: something failed at {}", Location::caller());
5176
let _ = format_args!("error: something failed at {}", Location::caller());
5277
assert!(true, "error: something failed at {}", Location::caller());
5378
assert_eq!(0, 0, "error: something failed at {}", Location::caller());
79+
assert_ne!(0, 0, "error: something failed at {}", Location::caller());
5480
panic!("error: something failed at {}", Location::caller());
55-
println!("{}", X(1).deref());
56-
println!("{}", Y(1));
81+
println!("{}", *X(1));
82+
println!("{}", **Y(X(1)));
83+
println!("{}", Z(1));
5784

5885
println!("error: something failed at {}", Somewhere.to_string());
5986
println!("{} and again {0}", x.to_string());
6087
}
88+
89+
macro_rules! my_macro {
90+
() => {
91+
// here be dragons, do not enter (or lint)
92+
println!("error: something failed at {}", Location::caller().to_string());
93+
};
94+
}

0 commit comments

Comments
 (0)