]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/default.rs
Auto merge of #6518 - ThibsG:CopyException, r=ebroto
[rust.git] / clippy_lints / src / default.rs
1 use crate::utils::{
2     any_parent_is_automatically_derived, contains_name, match_def_path, paths, qpath_res, snippet_with_macro_callsite,
3 };
4 use crate::utils::{span_lint_and_note, span_lint_and_sugg};
5 use if_chain::if_chain;
6 use rustc_data_structures::fx::FxHashSet;
7 use rustc_errors::Applicability;
8 use rustc_hir::def::Res;
9 use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind};
10 use rustc_lint::{LateContext, LateLintPass};
11 use rustc_middle::ty;
12 use rustc_session::{declare_tool_lint, impl_lint_pass};
13 use rustc_span::symbol::{Ident, Symbol};
14 use rustc_span::Span;
15
16 declare_clippy_lint! {
17     /// **What it does:** Checks for literal calls to `Default::default()`.
18     ///
19     /// **Why is this bad?** It's more clear to the reader to use the name of the type whose default is
20     /// being gotten than the generic `Default`.
21     ///
22     /// **Known problems:** None.
23     ///
24     /// **Example:**
25     /// ```rust
26     /// // Bad
27     /// let s: String = Default::default();
28     ///
29     /// // Good
30     /// let s = String::default();
31     /// ```
32     pub DEFAULT_TRAIT_ACCESS,
33     pedantic,
34     "checks for literal calls to `Default::default()`"
35 }
36
37 declare_clippy_lint! {
38     /// **What it does:** Checks for immediate reassignment of fields initialized
39     /// with Default::default().
40     ///
41     /// **Why is this bad?**It's more idiomatic to use the [functional update syntax](https://doc.rust-lang.org/reference/expressions/struct-expr.html#functional-update-syntax).
42     ///
43     /// **Known problems:** Assignments to patterns that are of tuple type are not linted.
44     ///
45     /// **Example:**
46     /// Bad:
47     /// ```
48     /// # #[derive(Default)]
49     /// # struct A { i: i32 }
50     /// let mut a: A = Default::default();
51     /// a.i = 42;
52     /// ```
53     /// Use instead:
54     /// ```
55     /// # #[derive(Default)]
56     /// # struct A { i: i32 }
57     /// let a = A {
58     ///     i: 42,
59     ///     .. Default::default()
60     /// };
61     /// ```
62     pub FIELD_REASSIGN_WITH_DEFAULT,
63     style,
64     "binding initialized with Default should have its fields set in the initializer"
65 }
66
67 #[derive(Default)]
68 pub struct Default {
69     // Spans linted by `field_reassign_with_default`.
70     reassigned_linted: FxHashSet<Span>,
71 }
72
73 impl_lint_pass!(Default => [DEFAULT_TRAIT_ACCESS, FIELD_REASSIGN_WITH_DEFAULT]);
74
75 impl LateLintPass<'_> for Default {
76     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
77         if_chain! {
78             // Avoid cases already linted by `field_reassign_with_default`
79             if !self.reassigned_linted.contains(&expr.span);
80             if let ExprKind::Call(ref path, ..) = expr.kind;
81             if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id);
82             if let ExprKind::Path(ref qpath) = path.kind;
83             if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
84             if match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD);
85             // Detect and ignore <Foo as Default>::default() because these calls do explicitly name the type.
86             if let QPath::Resolved(None, _path) = qpath;
87             then {
88                 let expr_ty = cx.typeck_results().expr_ty(expr);
89                 if let ty::Adt(def, ..) = expr_ty.kind() {
90                     // TODO: Work out a way to put "whatever the imported way of referencing
91                     // this type in this file" rather than a fully-qualified type.
92                     let replacement = format!("{}::default()", cx.tcx.def_path_str(def.did));
93                     span_lint_and_sugg(
94                         cx,
95                         DEFAULT_TRAIT_ACCESS,
96                         expr.span,
97                         &format!("calling `{}` is more clear than this expression", replacement),
98                         "try",
99                         replacement,
100                         Applicability::Unspecified, // First resolve the TODO above
101                     );
102                 }
103             }
104         }
105     }
106
107     fn check_block<'tcx>(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) {
108         // start from the `let mut _ = _::default();` and look at all the following
109         // statements, see if they re-assign the fields of the binding
110         let stmts_head = match block.stmts {
111             // Skip the last statement since there cannot possibly be any following statements that re-assign fields.
112             [head @ .., _] if !head.is_empty() => head,
113             _ => return,
114         };
115         for (stmt_idx, stmt) in stmts_head.iter().enumerate() {
116             // find all binding statements like `let mut _ = T::default()` where `T::default()` is the
117             // `default` method of the `Default` trait, and store statement index in current block being
118             // checked and the name of the bound variable
119             let (local, variant, binding_name, binding_type, span) = if_chain! {
120                 // only take `let ...` statements
121                 if let StmtKind::Local(local) = stmt.kind;
122                 if let Some(expr) = local.init;
123                 // only take bindings to identifiers
124                 if let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind;
125                 // only when assigning `... = Default::default()`
126                 if is_expr_default(expr, cx);
127                 let binding_type = cx.typeck_results().node_type(binding_id);
128                 if let Some(adt) = binding_type.ty_adt_def();
129                 if adt.is_struct();
130                 let variant = adt.non_enum_variant();
131                 if adt.did.is_local() || !variant.is_field_list_non_exhaustive();
132                 let module_did = cx.tcx.parent_module(stmt.hir_id).to_def_id();
133                 if variant
134                     .fields
135                     .iter()
136                     .all(|field| field.vis.is_accessible_from(module_did, cx.tcx));
137                 then {
138                     (local, variant, ident.name, binding_type, expr.span)
139                 } else {
140                     continue;
141                 }
142             };
143
144             // find all "later statement"'s where the fields of the binding set as
145             // Default::default() get reassigned, unless the reassignment refers to the original binding
146             let mut first_assign = None;
147             let mut assigned_fields = Vec::new();
148             let mut cancel_lint = false;
149             for consecutive_statement in &block.stmts[stmt_idx + 1..] {
150                 // find out if and which field was set by this `consecutive_statement`
151                 if let Some((field_ident, assign_rhs)) = field_reassigned_by_stmt(consecutive_statement, binding_name) {
152                     // interrupt and cancel lint if assign_rhs references the original binding
153                     if contains_name(binding_name, assign_rhs) {
154                         cancel_lint = true;
155                         break;
156                     }
157
158                     // if the field was previously assigned, replace the assignment, otherwise insert the assignment
159                     if let Some(prev) = assigned_fields
160                         .iter_mut()
161                         .find(|(field_name, _)| field_name == &field_ident.name)
162                     {
163                         *prev = (field_ident.name, assign_rhs);
164                     } else {
165                         assigned_fields.push((field_ident.name, assign_rhs));
166                     }
167
168                     // also set first instance of error for help message
169                     if first_assign.is_none() {
170                         first_assign = Some(consecutive_statement);
171                     }
172                 }
173                 // interrupt if no field was assigned, since we only want to look at consecutive statements
174                 else {
175                     break;
176                 }
177             }
178
179             // if there are incorrectly assigned fields, do a span_lint_and_note to suggest
180             // construction using `Ty { fields, ..Default::default() }`
181             if !assigned_fields.is_empty() && !cancel_lint {
182                 // if all fields of the struct are not assigned, add `.. Default::default()` to the suggestion.
183                 let ext_with_default = !variant
184                     .fields
185                     .iter()
186                     .all(|field| assigned_fields.iter().any(|(a, _)| a == &field.ident.name));
187
188                 let field_list = assigned_fields
189                     .into_iter()
190                     .map(|(field, rhs)| {
191                         // extract and store the assigned value for help message
192                         let value_snippet = snippet_with_macro_callsite(cx, rhs.span, "..");
193                         format!("{}: {}", field, value_snippet)
194                     })
195                     .collect::<Vec<String>>()
196                     .join(", ");
197
198                 let sugg = if ext_with_default {
199                     if field_list.is_empty() {
200                         format!("{}::default()", binding_type)
201                     } else {
202                         format!("{} {{ {}, ..Default::default() }}", binding_type, field_list)
203                     }
204                 } else {
205                     format!("{} {{ {} }}", binding_type, field_list)
206                 };
207
208                 // span lint once per statement that binds default
209                 span_lint_and_note(
210                     cx,
211                     FIELD_REASSIGN_WITH_DEFAULT,
212                     first_assign.unwrap().span,
213                     "field assignment outside of initializer for an instance created with Default::default()",
214                     Some(local.span),
215                     &format!(
216                         "consider initializing the variable with `{}` and removing relevant reassignments",
217                         sugg
218                     ),
219                 );
220                 self.reassigned_linted.insert(span);
221             }
222         }
223     }
224 }
225
226 /// Checks if the given expression is the `default` method belonging to the `Default` trait.
227 fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool {
228     if_chain! {
229         if let ExprKind::Call(ref fn_expr, _) = &expr.kind;
230         if let ExprKind::Path(qpath) = &fn_expr.kind;
231         if let Res::Def(_, def_id) = qpath_res(cx, qpath, fn_expr.hir_id);
232         then {
233             // right hand side of assignment is `Default::default`
234             match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD)
235         } else {
236             false
237         }
238     }
239 }
240
241 /// Returns the reassigned field and the assigning expression (right-hand side of assign).
242 fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Option<(Ident, &'tcx Expr<'tcx>)> {
243     if_chain! {
244         // only take assignments
245         if let StmtKind::Semi(ref later_expr) = this.kind;
246         if let ExprKind::Assign(ref assign_lhs, ref assign_rhs, _) = later_expr.kind;
247         // only take assignments to fields where the left-hand side field is a field of
248         // the same binding as the previous statement
249         if let ExprKind::Field(ref binding, field_ident) = assign_lhs.kind;
250         if let ExprKind::Path(QPath::Resolved(_, path)) = binding.kind;
251         if let Some(second_binding_name) = path.segments.last();
252         if second_binding_name.ident.name == binding_name;
253         then {
254             Some((field_ident, assign_rhs))
255         } else {
256             None
257         }
258     }
259 }