Skip to content

Commit 1564306

Browse files
tesujiflip1995
authored andcommitted
Re-factor useless_format lint
1 parent 92bd160 commit 1564306

File tree

3 files changed

+105
-93
lines changed

3 files changed

+105
-93
lines changed

clippy_lints/src/format.rs

Lines changed: 100 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use crate::utils::{
66
use if_chain::if_chain;
77
use rustc::hir::*;
88
use rustc::lint::{LateContext, LateLintPass, LintArray, LintContext, LintPass};
9-
use rustc::ty;
109
use rustc::{declare_lint_pass, declare_tool_lint};
1110
use rustc_errors::Applicability;
1211
use syntax::ast::LitKind;
@@ -39,56 +38,16 @@ declare_lint_pass!(UselessFormat => [USELESS_FORMAT]);
3938

4039
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UselessFormat {
4140
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
42-
if let Some(span) = is_expn_of(expr.span, "format") {
43-
if in_macro_or_desugar(span) {
44-
return;
45-
}
46-
match expr.node {
47-
// `format!("{}", foo)` expansion
48-
ExprKind::Call(ref fun, ref args) => {
49-
if_chain! {
50-
if let ExprKind::Path(ref qpath) = fun.node;
51-
if let Some(fun_def_id) = resolve_node(cx, qpath, fun.hir_id).opt_def_id();
52-
let new_v1 = match_def_path(cx, fun_def_id, &paths::FMT_ARGUMENTS_NEWV1);
53-
let new_v1_fmt = match_def_path(cx,
54-
fun_def_id,
55-
&paths::FMT_ARGUMENTS_NEWV1FORMATTED
56-
);
57-
if new_v1 || new_v1_fmt;
58-
if check_single_piece(&args[0]);
59-
if let Some(format_arg) = get_single_string_arg(cx, &args[1]);
60-
if new_v1 || check_unformatted(&args[2]);
61-
if let ExprKind::AddrOf(_, ref format_arg) = format_arg.node;
62-
then {
63-
let (message, sugg) = if_chain! {
64-
if let ExprKind::MethodCall(ref path, _, _) = format_arg.node;
65-
if path.ident.as_interned_str().as_symbol() == sym!(to_string);
66-
then {
67-
("`to_string()` is enough",
68-
snippet(cx, format_arg.span, "<arg>").to_string())
69-
} else {
70-
("consider using .to_string()",
71-
format!("{}.to_string()", snippet(cx, format_arg.span, "<arg>")))
72-
}
73-
};
41+
let span = match is_expn_of(expr.span, "format") {
42+
Some(s) if !in_macro_or_desugar(s) => s,
43+
_ => return,
44+
};
7445

75-
span_useless_format(cx, span, message, sugg);
76-
}
77-
}
78-
},
79-
// `format!("foo")` expansion contains `match () { () => [], }`
80-
ExprKind::Match(ref matchee, _, _) => {
81-
if let ExprKind::Tup(ref tup) = matchee.node {
82-
if tup.is_empty() {
83-
let actual_snippet = snippet(cx, expr.span, "<expr>").to_string();
84-
let actual_snippet = actual_snippet.replace("{{}}", "{}");
85-
let sugg = format!("{}.to_string()", actual_snippet);
86-
span_useless_format(cx, span, "consider using .to_string()", sugg);
87-
}
88-
}
89-
},
90-
_ => (),
91-
}
46+
// Operate on the only argument of `alloc::fmt::format`.
47+
if let Some(sugg) = on_new_v1(cx, expr) {
48+
span_useless_format(cx, span, "consider using .to_string()", sugg);
49+
} else if let Some(sugg) = on_new_v1_fmt(cx, expr) {
50+
span_useless_format(cx, span, "consider using .to_string()", sugg);
9251
}
9352
}
9453
}
@@ -112,56 +71,105 @@ fn span_useless_format<T: LintContext>(cx: &T, span: Span, help: &str, mut sugg:
11271
});
11372
}
11473

115-
/// Checks if the expressions matches `&[""]`
116-
fn check_single_piece(expr: &Expr) -> bool {
74+
fn on_argumentv1_new<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr, arms: &'a [Arm]) -> Option<String> {
11775
if_chain! {
118-
if let ExprKind::AddrOf(_, ref expr) = expr.node; // &[""]
119-
if let ExprKind::Array(ref exprs) = expr.node; // [""]
120-
if exprs.len() == 1;
121-
if let ExprKind::Lit(ref lit) = exprs[0].node;
122-
if let LitKind::Str(ref lit, _) = lit.node;
76+
if let ExprKind::AddrOf(_, ref format_args) = expr.node;
77+
if let ExprKind::Array(ref elems) = arms[0].body.node;
78+
if elems.len() == 1;
79+
if let ExprKind::Call(ref fun, ref args) = elems[0].node;
80+
if let ExprKind::Path(ref qpath) = fun.node;
81+
if let Some(did) = resolve_node(cx, qpath, fun.hir_id).opt_def_id();
82+
if match_def_path(cx, did, &paths::FMT_ARGUMENTV1_NEW);
83+
// matches `core::fmt::Display::fmt`
84+
if args.len() == 2;
85+
if let ExprKind::Path(ref qpath) = args[1].node;
86+
if let Some(did) = resolve_node(cx, qpath, args[1].hir_id).opt_def_id();
87+
if match_def_path(cx, did, &paths::DISPLAY_FMT_METHOD);
88+
if arms[0].pats.len() == 1;
89+
// check `(arg0,)` in match block
90+
if let PatKind::Tuple(ref pats, None) = arms[0].pats[0].node;
91+
if pats.len() == 1;
12392
then {
124-
return lit.as_str().is_empty();
93+
if let ExprKind::Lit(ref lit) = format_args.node {
94+
if let LitKind::Str(ref s, _) = lit.node {
95+
let snip = s.as_str().replace("{{}}", "{}");
96+
let sugg = format!("\"{}\".to_string()", snip);
97+
return Some(sugg);
98+
}
99+
return None;
100+
} else {
101+
let snip = snippet(cx, format_args.span, "<arg>");
102+
if let ExprKind::MethodCall(ref path, _, _) = format_args.node {
103+
if path.ident.name == sym!(to_string) {
104+
return Some(format!("{}", snip));
105+
}
106+
}
107+
return Some(format!("{}.to_string()", snip));
108+
}
125109
}
126110
}
127-
128-
false
111+
None
129112
}
130113

131-
/// Checks if the expressions matches
132-
/// ```rust,ignore
133-
/// &match (&"arg",) {
134-
/// (__arg0,) => [::std::fmt::ArgumentV1::new(__arg0,
135-
/// ::std::fmt::Display::fmt)],
136-
/// }
137-
/// ```
138-
/// and that the type of `__arg0` is `&str` or `String`,
139-
/// then returns the span of first element of the matched tuple.
140-
fn get_single_string_arg<'a>(cx: &LateContext<'_, '_>, expr: &'a Expr) -> Option<&'a Expr> {
114+
fn on_new_v1<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) -> Option<String> {
141115
if_chain! {
142-
if let ExprKind::AddrOf(_, ref expr) = expr.node;
143-
if let ExprKind::Match(ref match_expr, ref arms, _) = expr.node;
144-
if arms.len() == 1;
145-
if arms[0].pats.len() == 1;
146-
if let PatKind::Tuple(ref pat, None) = arms[0].pats[0].node;
147-
if pat.len() == 1;
148-
if let ExprKind::Array(ref exprs) = arms[0].body.node;
149-
if exprs.len() == 1;
150-
if let ExprKind::Call(_, ref args) = exprs[0].node;
116+
if let ExprKind::Call(ref fun, ref args) = expr.node;
151117
if args.len() == 2;
152-
if let ExprKind::Path(ref qpath) = args[1].node;
153-
if let Some(fun_def_id) = resolve_node(cx, qpath, args[1].hir_id).opt_def_id();
154-
if match_def_path(cx, fun_def_id, &paths::DISPLAY_FMT_METHOD);
118+
if let ExprKind::Path(ref qpath) = fun.node;
119+
if let Some(did) = resolve_node(cx, qpath, fun.hir_id).opt_def_id();
120+
if match_def_path(cx, did, &paths::FMT_ARGUMENTS_NEW_V1);
121+
// Argument 1 in `new_v1()`
122+
if let ExprKind::AddrOf(_, ref arr) = args[0].node;
123+
if let ExprKind::Array(ref pieces) = arr.node;
124+
if pieces.len() == 1;
125+
if let ExprKind::Lit(ref lit) = pieces[0].node;
126+
if let LitKind::Str(ref s, _) = lit.node;
127+
// Argument 2 in `new_v1()`
128+
if let ExprKind::AddrOf(_, ref arg1) = args[1].node;
129+
if let ExprKind::Match(ref matchee, ref arms, MatchSource::Normal) = arg1.node;
130+
if arms.len() == 1;
131+
if let ExprKind::Tup(ref tup) = matchee.node;
155132
then {
156-
let ty = walk_ptrs_ty(cx.tables.pat_ty(&pat[0]));
157-
if ty.sty == ty::Str || match_type(cx, ty, &paths::STRING) {
158-
if let ExprKind::Tup(ref values) = match_expr.node {
159-
return Some(&values[0]);
133+
// `format!("foo")` expansion contains `match () { () => [], }`
134+
if tup.is_empty() {
135+
let snip = s.as_str().replace("{{}}", "{}");
136+
let sugg = format!("\"{}\".to_string()", snip);
137+
return Some(sugg);
138+
} else {
139+
if s.as_str().is_empty() {
140+
return on_argumentv1_new(cx, &tup[0], arms);
141+
} else {
142+
return None;
160143
}
161144
}
162145
}
163146
}
147+
None
148+
}
164149

150+
fn on_new_v1_fmt<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) -> Option<String> {
151+
if_chain! {
152+
if let ExprKind::Call(ref fun, ref args) = expr.node;
153+
if args.len() == 3;
154+
if let ExprKind::Path(ref qpath) = fun.node;
155+
if let Some(did) = resolve_node(cx, qpath, fun.hir_id).opt_def_id();
156+
if match_def_path(cx, did, &paths::FMT_ARGUMENTS_NEW_V1_FORMATTED);
157+
if check_unformatted(&args[2]);
158+
// Argument 1 in `new_v1_formatted()`
159+
if let ExprKind::AddrOf(_, ref arr) = args[0].node;
160+
if let ExprKind::Array(ref pieces) = arr.node;
161+
if pieces.len() == 1;
162+
if let ExprKind::Lit(ref lit) = pieces[0].node;
163+
if let LitKind::Str(..) = lit.node;
164+
// Argument 2 in `new_v1_formatted()`
165+
if let ExprKind::AddrOf(_, ref arg1) = args[1].node;
166+
if let ExprKind::Match(ref matchee, ref arms, MatchSource::Normal) = arg1.node;
167+
if arms.len() == 1;
168+
if let ExprKind::Tup(ref tup) = matchee.node;
169+
then {
170+
return on_argumentv1_new(cx, &tup[0], arms);
171+
}
172+
}
165173
None
166174
}
167175

@@ -170,6 +178,7 @@ fn get_single_string_arg<'a>(cx: &LateContext<'_, '_>, expr: &'a Expr) -> Option
170178
/// &[_ {
171179
/// format: _ {
172180
/// width: _::Implied,
181+
/// precision: _::Implied,
173182
/// ...
174183
/// },
175184
/// ...,
@@ -180,15 +189,17 @@ fn check_unformatted(expr: &Expr) -> bool {
180189
if let ExprKind::AddrOf(_, ref expr) = expr.node;
181190
if let ExprKind::Array(ref exprs) = expr.node;
182191
if exprs.len() == 1;
192+
// struct `core::fmt::rt::v1::Argument`
183193
if let ExprKind::Struct(_, ref fields, _) = exprs[0].node;
184194
if let Some(format_field) = fields.iter().find(|f| f.ident.name == sym!(format));
195+
// struct `core::fmt::rt::v1::FormatSpec`
185196
if let ExprKind::Struct(_, ref fields, _) = format_field.expr.node;
186-
if let Some(width_field) = fields.iter().find(|f| f.ident.name == sym!(width));
187-
if let ExprKind::Path(ref width_qpath) = width_field.expr.node;
188-
if last_path_segment(width_qpath).ident.name == sym!(Implied);
189197
if let Some(precision_field) = fields.iter().find(|f| f.ident.name == sym!(precision));
190198
if let ExprKind::Path(ref precision_path) = precision_field.expr.node;
191199
if last_path_segment(precision_path).ident.name == sym!(Implied);
200+
if let Some(width_field) = fields.iter().find(|f| f.ident.name == sym!(width));
201+
if let ExprKind::Path(ref width_qpath) = width_field.expr.node;
202+
if last_path_segment(width_qpath).ident.name == sym!(Implied);
192203
then {
193204
return true;
194205
}

clippy_lints/src/utils/paths.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ pub const DROP: [&str; 3] = ["core", "mem", "drop"];
2727
pub const DROP_TRAIT: [&str; 4] = ["core", "ops", "drop", "Drop"];
2828
pub const DURATION: [&str; 3] = ["core", "time", "Duration"];
2929
pub const EARLY_CONTEXT: [&str; 4] = ["rustc", "lint", "context", "EarlyContext"];
30-
pub const FMT_ARGUMENTS_NEWV1: [&str; 4] = ["core", "fmt", "Arguments", "new_v1"];
31-
pub const FMT_ARGUMENTS_NEWV1FORMATTED: [&str; 4] = ["core", "fmt", "Arguments", "new_v1_formatted"];
30+
pub const FMT_ARGUMENTS_NEW_V1: [&str; 4] = ["core", "fmt", "Arguments", "new_v1"];
31+
pub const FMT_ARGUMENTS_NEW_V1_FORMATTED: [&str; 4] = ["core", "fmt", "Arguments", "new_v1_formatted"];
32+
pub const FMT_ARGUMENTV1_NEW: [&str; 4] = ["core", "fmt", "ArgumentV1", "new"];
3233
pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
3334
pub const FROM_TRAIT: [&str; 3] = ["core", "convert", "From"];
3435
pub const HASH: [&str; 2] = ["hash", "Hash"];

tests/ui/format.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,13 @@ error: useless use of `format!`
5858
--> $DIR/format.rs:59:5
5959
|
6060
LL | format!("{}", 42.to_string());
61-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: `to_string()` is enough: `42.to_string();`
61+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using .to_string(): `42.to_string();`
6262

6363
error: useless use of `format!`
6464
--> $DIR/format.rs:61:5
6565
|
6666
LL | format!("{}", x.display().to_string());
67-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: `to_string()` is enough: `x.display().to_string();`
67+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using .to_string(): `x.display().to_string();`
6868

6969
error: aborting due to 11 previous errors
7070

0 commit comments

Comments
 (0)