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