]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/format.rs
Auto merge of #88865 - guswynn:must_not_suspend, r=oli-obk
[rust.git] / src / tools / clippy / clippy_lints / src / format.rs
1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use clippy_utils::higher::FormatExpn;
3 use clippy_utils::last_path_segment;
4 use clippy_utils::source::{snippet_opt, snippet_with_applicability};
5 use clippy_utils::sugg::Sugg;
6 use if_chain::if_chain;
7 use rustc_errors::Applicability;
8 use rustc_hir::{BorrowKind, Expr, ExprKind, QPath};
9 use rustc_lint::{LateContext, LateLintPass};
10 use rustc_middle::ty;
11 use rustc_session::{declare_lint_pass, declare_tool_lint};
12 use rustc_span::symbol::kw;
13 use rustc_span::{sym, Span};
14
15 declare_clippy_lint! {
16     /// ### What it does
17     /// Checks for the use of `format!("string literal with no
18     /// argument")` and `format!("{}", foo)` where `foo` is a string.
19     ///
20     /// ### Why is this bad?
21     /// There is no point of doing that. `format!("foo")` can
22     /// be replaced by `"foo".to_owned()` if you really need a `String`. The even
23     /// worse `&format!("foo")` is often encountered in the wild. `format!("{}",
24     /// foo)` can be replaced by `foo.clone()` if `foo: String` or `foo.to_owned()`
25     /// if `foo: &str`.
26     ///
27     /// ### Examples
28     /// ```rust
29     ///
30     /// // Bad
31     /// let foo = "foo";
32     /// format!("{}", foo);
33     ///
34     /// // Good
35     /// foo.to_owned();
36     /// ```
37     pub USELESS_FORMAT,
38     complexity,
39     "useless use of `format!`"
40 }
41
42 declare_lint_pass!(UselessFormat => [USELESS_FORMAT]);
43
44 impl<'tcx> LateLintPass<'tcx> for UselessFormat {
45     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
46         let FormatExpn { call_site, format_args } = match FormatExpn::parse(expr) {
47             Some(e) if !e.call_site.from_expansion() => e,
48             _ => return,
49         };
50
51         let mut applicability = Applicability::MachineApplicable;
52         if format_args.value_args.is_empty() {
53             if_chain! {
54                 if let [e] = &*format_args.format_string_parts;
55                 if let ExprKind::Lit(lit) = &e.kind;
56                 if let Some(s_src) = snippet_opt(cx, lit.span);
57                 then {
58                     // Simulate macro expansion, converting {{ and }} to { and }.
59                     let s_expand = s_src.replace("{{", "{").replace("}}", "}");
60                     let sugg = format!("{}.to_string()", s_expand);
61                     span_useless_format(cx, call_site, sugg, applicability);
62                 }
63             }
64         } else if let [value] = *format_args.value_args {
65             if_chain! {
66                 if format_args.format_string_symbols == [kw::Empty];
67                 if match cx.typeck_results().expr_ty(value).peel_refs().kind() {
68                     ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(sym::string_type, adt.did),
69                     ty::Str => true,
70                     _ => false,
71                 };
72                 if format_args.args.iter().all(|e| is_display_arg(e));
73                 if format_args.fmt_expr.map_or(true, |e| check_unformatted(e));
74                 then {
75                     let is_new_string = match value.kind {
76                         ExprKind::Binary(..) => true,
77                         ExprKind::MethodCall(path, ..) => path.ident.name.as_str() == "to_string",
78                         _ => false,
79                     };
80                     let sugg = if is_new_string {
81                         snippet_with_applicability(cx, value.span, "..", &mut applicability).into_owned()
82                     } else {
83                         let sugg = Sugg::hir_with_applicability(cx, value, "<arg>", &mut applicability);
84                         format!("{}.to_string()", sugg.maybe_par())
85                     };
86                     span_useless_format(cx, call_site, sugg, applicability);
87                 }
88             }
89         };
90     }
91 }
92
93 fn span_useless_format(cx: &LateContext<'_>, span: Span, mut sugg: String, mut applicability: Applicability) {
94     // The callsite span contains the statement semicolon for some reason.
95     if snippet_with_applicability(cx, span, "..", &mut applicability).ends_with(';') {
96         sugg.push(';');
97     }
98
99     span_lint_and_sugg(
100         cx,
101         USELESS_FORMAT,
102         span,
103         "useless use of `format!`",
104         "consider using `.to_string()`",
105         sugg,
106         applicability,
107     );
108 }
109
110 fn is_display_arg(expr: &Expr<'_>) -> bool {
111     if_chain! {
112         if let ExprKind::Call(_, [_, fmt]) = expr.kind;
113         if let ExprKind::Path(QPath::Resolved(_, path)) = fmt.kind;
114         if let [.., t, _] = path.segments;
115         if t.ident.name.as_str() == "Display";
116         then { true } else { false }
117     }
118 }
119
120 /// Checks if the expression matches
121 /// ```rust,ignore
122 /// &[_ {
123 ///    format: _ {
124 ///         width: _::Implied,
125 ///         precision: _::Implied,
126 ///         ...
127 ///    },
128 ///    ...,
129 /// }]
130 /// ```
131 fn check_unformatted(expr: &Expr<'_>) -> bool {
132     if_chain! {
133         if let ExprKind::AddrOf(BorrowKind::Ref, _, expr) = expr.kind;
134         if let ExprKind::Array([expr]) = expr.kind;
135         // struct `core::fmt::rt::v1::Argument`
136         if let ExprKind::Struct(_, fields, _) = expr.kind;
137         if let Some(format_field) = fields.iter().find(|f| f.ident.name == sym::format);
138         // struct `core::fmt::rt::v1::FormatSpec`
139         if let ExprKind::Struct(_, fields, _) = format_field.expr.kind;
140         if let Some(precision_field) = fields.iter().find(|f| f.ident.name == sym::precision);
141         if let ExprKind::Path(ref precision_path) = precision_field.expr.kind;
142         if last_path_segment(precision_path).ident.name == sym::Implied;
143         if let Some(width_field) = fields.iter().find(|f| f.ident.name == sym::width);
144         if let ExprKind::Path(ref width_qpath) = width_field.expr.kind;
145         if last_path_segment(width_qpath).ident.name == sym::Implied;
146         then {
147             return true;
148         }
149     }
150
151     false
152 }