]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/manual_string_new.rs
Auto merge of #101154 - RalfJung:validation-perf, r=oli-obk
[rust.git] / src / tools / clippy / clippy_lints / src / manual_string_new.rs
1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use rustc_ast::LitKind;
3 use rustc_errors::Applicability::MachineApplicable;
4 use rustc_hir::{Expr, ExprKind, PathSegment, QPath, TyKind};
5 use rustc_lint::{LateContext, LateLintPass};
6 use rustc_middle::ty;
7 use rustc_session::{declare_lint_pass, declare_tool_lint};
8 use rustc_span::{sym, symbol, Span};
9
10 declare_clippy_lint! {
11     /// ### What it does
12     ///
13     /// Checks for usage of `""` to create a `String`, such as `"".to_string()`, `"".to_owned()`,
14     /// `String::from("")` and others.
15     ///
16     /// ### Why is this bad?
17     ///
18     /// Different ways of creating an empty string makes your code less standardized, which can
19     /// be confusing.
20     ///
21     /// ### Example
22     /// ```rust
23     /// let a = "".to_string();
24     /// let b: String = "".into();
25     /// ```
26     /// Use instead:
27     /// ```rust
28     /// let a = String::new();
29     /// let b = String::new();
30     /// ```
31     #[clippy::version = "1.65.0"]
32     pub MANUAL_STRING_NEW,
33     pedantic,
34     "empty String is being created manually"
35 }
36 declare_lint_pass!(ManualStringNew => [MANUAL_STRING_NEW]);
37
38 impl LateLintPass<'_> for ManualStringNew {
39     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
40         if expr.span.from_expansion() {
41             return;
42         }
43
44         let ty = cx.typeck_results().expr_ty(expr);
45         match ty.kind() {
46             ty::Adt(adt_def, _) if adt_def.is_struct() => {
47                 if !cx.tcx.is_diagnostic_item(sym::String, adt_def.did()) {
48                     return;
49                 }
50             },
51             _ => return,
52         }
53
54         match expr.kind {
55             ExprKind::Call(func, args) => {
56                 parse_call(cx, expr.span, func, args);
57             },
58             ExprKind::MethodCall(path_segment, args, _) => {
59                 parse_method_call(cx, expr.span, path_segment, args);
60             },
61             _ => (),
62         }
63     }
64 }
65
66 /// Checks if an expression's kind corresponds to an empty &str.
67 fn is_expr_kind_empty_str(expr_kind: &ExprKind<'_>) -> bool {
68     if  let ExprKind::Lit(lit) = expr_kind &&
69         let LitKind::Str(value, _) = lit.node &&
70         value == symbol::kw::Empty
71     {
72         return true;
73     }
74
75     false
76 }
77
78 fn warn_then_suggest(cx: &LateContext<'_>, span: Span) {
79     span_lint_and_sugg(
80         cx,
81         MANUAL_STRING_NEW,
82         span,
83         "empty String is being created manually",
84         "consider using",
85         "String::new()".into(),
86         MachineApplicable,
87     );
88 }
89
90 /// Tries to parse an expression as a method call, emitting the warning if necessary.
91 fn parse_method_call(cx: &LateContext<'_>, span: Span, path_segment: &PathSegment<'_>, args: &[Expr<'_>]) {
92     if args.is_empty() {
93         // When parsing TryFrom::try_from(...).expect(...), we will have more than 1 arg.
94         return;
95     }
96
97     let ident = path_segment.ident.as_str();
98     let method_arg_kind = &args[0].kind;
99     if ["to_string", "to_owned", "into"].contains(&ident) && is_expr_kind_empty_str(method_arg_kind) {
100         warn_then_suggest(cx, span);
101     } else if let ExprKind::Call(func, args) = method_arg_kind {
102         // If our first argument is a function call itself, it could be an `unwrap`-like function.
103         // E.g. String::try_from("hello").unwrap(), TryFrom::try_from("").expect("hello"), etc.
104         parse_call(cx, span, func, args);
105     }
106 }
107
108 /// Tries to parse an expression as a function call, emitting the warning if necessary.
109 fn parse_call(cx: &LateContext<'_>, span: Span, func: &Expr<'_>, args: &[Expr<'_>]) {
110     if args.len() != 1 {
111         return;
112     }
113
114     let arg_kind = &args[0].kind;
115     if let ExprKind::Path(qpath) = &func.kind {
116         if let QPath::TypeRelative(_, _) = qpath {
117             // String::from(...) or String::try_from(...)
118             if  let QPath::TypeRelative(ty, path_seg) = qpath &&
119                 [sym::from, sym::try_from].contains(&path_seg.ident.name) &&
120                 let TyKind::Path(qpath) = &ty.kind &&
121                 let QPath::Resolved(_, path) = qpath &&
122                 let [path_seg] = path.segments &&
123                 path_seg.ident.name == sym::String &&
124                 is_expr_kind_empty_str(arg_kind)
125             {
126                 warn_then_suggest(cx, span);
127             }
128         } else if let QPath::Resolved(_, path) = qpath {
129             // From::from(...) or TryFrom::try_from(...)
130             if  let [path_seg1, path_seg2] = path.segments &&
131                 is_expr_kind_empty_str(arg_kind) && (
132                     (path_seg1.ident.name == sym::From && path_seg2.ident.name == sym::from) ||
133                     (path_seg1.ident.name == sym::TryFrom && path_seg2.ident.name == sym::try_from)
134                 )
135             {
136                 warn_then_suggest(cx, span);
137             }
138         }
139     }
140 }