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