]>
Commit | Line | Data |
---|---|---|
ea8adc8c | 1 | use rustc::hir::*; |
ea8adc8c XL |
2 | use rustc::lint::*; |
3 | use rustc::ty; | |
4 | use syntax::ast::LitKind; | |
83c7162d | 5 | use syntax_pos::Span; |
ea8adc8c | 6 | use utils::paths; |
83c7162d | 7 | use utils::{in_macro, is_expn_of, last_path_segment, match_def_path, match_type, opt_def_id, resolve_node, snippet, span_lint_and_then, walk_ptrs_ty}; |
ea8adc8c XL |
8 | |
9 | /// **What it does:** Checks for the use of `format!("string literal with no | |
10 | /// argument")` and `format!("{}", foo)` where `foo` is a string. | |
11 | /// | |
12 | /// **Why is this bad?** There is no point of doing that. `format!("too")` can | |
13 | /// be replaced by `"foo".to_owned()` if you really need a `String`. The even | |
14 | /// worse `&format!("foo")` is often encountered in the wild. `format!("{}", | |
15 | /// foo)` can be replaced by `foo.clone()` if `foo: String` or `foo.to_owned()` | |
16 | /// if `foo: &str`. | |
17 | /// | |
18 | /// **Known problems:** None. | |
19 | /// | |
20 | /// **Examples:** | |
21 | /// ```rust | |
22 | /// format!("foo") | |
23 | /// format!("{}", foo) | |
24 | /// ``` | |
0531ce1d | 25 | declare_clippy_lint! { |
ea8adc8c | 26 | pub USELESS_FORMAT, |
0531ce1d | 27 | complexity, |
ea8adc8c XL |
28 | "useless use of `format!`" |
29 | } | |
30 | ||
31 | #[derive(Copy, Clone, Debug)] | |
32 | pub struct Pass; | |
33 | ||
34 | impl LintPass for Pass { | |
35 | fn get_lints(&self) -> LintArray { | |
36 | lint_array![USELESS_FORMAT] | |
37 | } | |
38 | } | |
39 | ||
40 | impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass { | |
41 | fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) { | |
42 | if let Some(span) = is_expn_of(expr.span, "format") { | |
83c7162d XL |
43 | if in_macro(span) { |
44 | return; | |
45 | } | |
ea8adc8c | 46 | match expr.node { |
83c7162d | 47 | |
ea8adc8c XL |
48 | // `format!("{}", foo)` expansion |
49 | ExprCall(ref fun, ref args) => { | |
abe05a73 XL |
50 | if_chain! { |
51 | if let ExprPath(ref qpath) = fun.node; | |
83c7162d | 52 | if args.len() == 3; |
abe05a73 | 53 | if let Some(fun_def_id) = opt_def_id(resolve_node(cx, qpath, fun.hir_id)); |
83c7162d XL |
54 | if match_def_path(cx.tcx, fun_def_id, &paths::FMT_ARGUMENTS_NEWV1FORMATTED); |
55 | if check_single_piece(&args[0]); | |
56 | if let Some(format_arg) = get_single_string_arg(cx, &args[1]); | |
57 | if check_unformatted(&args[2]); | |
abe05a73 | 58 | then { |
83c7162d | 59 | let sugg = format!("{}.to_string()", snippet(cx, format_arg, "<arg>").into_owned()); |
0531ce1d XL |
60 | span_lint_and_then(cx, USELESS_FORMAT, span, "useless use of `format!`", |db| { |
61 | db.span_suggestion(expr.span, "consider using .to_string()", sugg); | |
62 | }); | |
ea8adc8c XL |
63 | } |
64 | } | |
65 | }, | |
abe05a73 XL |
66 | // `format!("foo")` expansion contains `match () { () => [], }` |
67 | ExprMatch(ref matchee, _, _) => if let ExprTup(ref tup) = matchee.node { | |
68 | if tup.is_empty() { | |
0531ce1d XL |
69 | let sugg = format!("{}.to_string()", snippet(cx, expr.span, "<expr>").into_owned()); |
70 | span_lint_and_then(cx, USELESS_FORMAT, span, "useless use of `format!`", |db| { | |
71 | db.span_suggestion(span, "consider using .to_string()", sugg); | |
72 | }); | |
abe05a73 XL |
73 | } |
74 | }, | |
ea8adc8c XL |
75 | _ => (), |
76 | } | |
77 | } | |
78 | } | |
79 | } | |
80 | ||
abe05a73 | 81 | /// Checks if the expressions matches `&[""]` |
83c7162d | 82 | fn check_single_piece(expr: &Expr) -> bool { |
abe05a73 XL |
83 | if_chain! { |
84 | if let ExprAddrOf(_, ref expr) = expr.node; // &[""] | |
85 | if let ExprArray(ref exprs) = expr.node; // [""] | |
86 | if exprs.len() == 1; | |
87 | if let ExprLit(ref lit) = exprs[0].node; | |
88 | if let LitKind::Str(ref lit, _) = lit.node; | |
89 | then { | |
90 | return lit.as_str().is_empty(); | |
ea8adc8c | 91 | } |
ea8adc8c | 92 | } |
abe05a73 XL |
93 | |
94 | false | |
ea8adc8c XL |
95 | } |
96 | ||
97 | /// Checks if the expressions matches | |
98 | /// ```rust,ignore | |
83c7162d | 99 | /// &match (&"arg",) { |
ea8adc8c XL |
100 | /// (__arg0,) => [::std::fmt::ArgumentV1::new(__arg0, |
101 | /// ::std::fmt::Display::fmt)], | |
102 | /// } | |
103 | /// ``` | |
83c7162d XL |
104 | /// and that type of `__arg0` is `&str` or `String` |
105 | /// then returns the span of first element of the matched tuple | |
106 | fn get_single_string_arg(cx: &LateContext, expr: &Expr) -> Option<Span> { | |
abe05a73 XL |
107 | if_chain! { |
108 | if let ExprAddrOf(_, ref expr) = expr.node; | |
83c7162d | 109 | if let ExprMatch(ref match_expr, ref arms, _) = expr.node; |
abe05a73 XL |
110 | if arms.len() == 1; |
111 | if arms[0].pats.len() == 1; | |
112 | if let PatKind::Tuple(ref pat, None) = arms[0].pats[0].node; | |
113 | if pat.len() == 1; | |
114 | if let ExprArray(ref exprs) = arms[0].body.node; | |
115 | if exprs.len() == 1; | |
116 | if let ExprCall(_, ref args) = exprs[0].node; | |
117 | if args.len() == 2; | |
118 | if let ExprPath(ref qpath) = args[1].node; | |
119 | if let Some(fun_def_id) = opt_def_id(resolve_node(cx, qpath, args[1].hir_id)); | |
120 | if match_def_path(cx.tcx, fun_def_id, &paths::DISPLAY_FMT_METHOD); | |
121 | then { | |
122 | let ty = walk_ptrs_ty(cx.tables.pat_ty(&pat[0])); | |
83c7162d XL |
123 | if ty.sty == ty::TyStr || match_type(cx, ty, &paths::STRING) { |
124 | if let ExprTup(ref values) = match_expr.node { | |
125 | return Some(values[0].span); | |
126 | } | |
127 | } | |
128 | } | |
129 | } | |
130 | ||
131 | None | |
132 | } | |
ea8adc8c | 133 | |
83c7162d XL |
134 | /// Checks if the expression matches |
135 | /// ```rust,ignore | |
136 | /// &[_ { | |
137 | /// format: _ { | |
138 | /// width: _::Implied, | |
139 | /// ... | |
140 | /// }, | |
141 | /// ..., | |
142 | /// }] | |
143 | /// ``` | |
144 | fn check_unformatted(expr: &Expr) -> bool { | |
145 | if_chain! { | |
146 | if let ExprAddrOf(_, ref expr) = expr.node; | |
147 | if let ExprArray(ref exprs) = expr.node; | |
148 | if exprs.len() == 1; | |
149 | if let ExprStruct(_, ref fields, _) = exprs[0].node; | |
150 | if let Some(format_field) = fields.iter().find(|f| f.name.node == "format"); | |
151 | if let ExprStruct(_, ref fields, _) = format_field.expr.node; | |
152 | if let Some(align_field) = fields.iter().find(|f| f.name.node == "width"); | |
153 | if let ExprPath(ref qpath) = align_field.expr.node; | |
154 | if last_path_segment(qpath).name == "Implied"; | |
155 | then { | |
156 | return true; | |
abe05a73 XL |
157 | } |
158 | } | |
ea8adc8c XL |
159 | |
160 | false | |
161 | } |