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