]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/default.rs
New upstream version 1.63.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / default.rs
CommitLineData
cdc7bbd5
XL
1use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg};
2use clippy_utils::source::snippet_with_macro_callsite;
3c0e092e 3use clippy_utils::ty::{has_drop, is_copy};
5e7ed085 4use clippy_utils::{any_parent_is_automatically_derived, contains_name, get_parent_expr, match_def_path, paths};
f20569fa
XL
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};
f20569fa
XL
11use rustc_middle::ty;
12use rustc_session::{declare_tool_lint, impl_lint_pass};
13use rustc_span::symbol::{Ident, Symbol};
14use rustc_span::Span;
15
16declare_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
39declare_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)]
74pub struct Default {
75 // Spans linted by `field_reassign_with_default`.
76 reassigned_linted: FxHashSet<Span>,
77}
78
79impl_lint_pass!(Default => [DEFAULT_TRAIT_ACCESS, FIELD_REASSIGN_WITH_DEFAULT]);
80
5099ac24 81impl<'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.
262fn 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).
277fn 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 }`
297fn 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}