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