-use crate::utils::{any_parent_is_automatically_derived, contains_name, match_def_path, paths, qpath_res, snippet};
+use crate::utils::{
+ any_parent_is_automatically_derived, contains_name, match_def_path, paths, snippet_with_macro_callsite,
+};
use crate::utils::{span_lint_and_note, span_lint_and_sugg};
use if_chain::if_chain;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def::Res;
use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
-use rustc_middle::ty::{self, Adt, Ty};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::{Ident, Symbol};
use rustc_span::Span;
}
fn check_block<'tcx>(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) {
- // find all binding statements like `let mut _ = T::default()` where `T::default()` is the
- // `default` method of the `Default` trait, and store statement index in current block being
- // checked and the name of the bound variable
- let binding_statements_using_default = enumerate_bindings_using_default(cx, block);
-
// start from the `let mut _ = _::default();` and look at all the following
// statements, see if they re-assign the fields of the binding
- for (stmt_idx, binding_name, binding_type, span) in binding_statements_using_default {
- // the last statement of a block cannot trigger the lint
- if stmt_idx == block.stmts.len() - 1 {
- break;
- }
+ let stmts_head = match block.stmts {
+ // Skip the last statement since there cannot possibly be any following statements that re-assign fields.
+ [head @ .., _] if !head.is_empty() => head,
+ _ => return,
+ };
+ for (stmt_idx, stmt) in stmts_head.iter().enumerate() {
+ // find all binding statements like `let mut _ = T::default()` where `T::default()` is the
+ // `default` method of the `Default` trait, and store statement index in current block being
+ // checked and the name of the bound variable
+ let (local, variant, binding_name, binding_type, span) = if_chain! {
+ // only take `let ...` statements
+ if let StmtKind::Local(local) = stmt.kind;
+ if let Some(expr) = local.init;
+ if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id);
+ if !in_external_macro(cx.tcx.sess, expr.span);
+ // only take bindings to identifiers
+ if let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind;
+ // only when assigning `... = Default::default()`
+ if is_expr_default(expr, cx);
+ let binding_type = cx.typeck_results().node_type(binding_id);
+ if let Some(adt) = binding_type.ty_adt_def();
+ if adt.is_struct();
+ let variant = adt.non_enum_variant();
+ if adt.did.is_local() || !variant.is_field_list_non_exhaustive();
+ let module_did = cx.tcx.parent_module(stmt.hir_id).to_def_id();
+ if variant
+ .fields
+ .iter()
+ .all(|field| field.vis.is_accessible_from(module_did, cx.tcx));
+ then {
+ (local, variant, ident.name, binding_type, expr.span)
+ } else {
+ continue;
+ }
+ };
// find all "later statement"'s where the fields of the binding set as
// Default::default() get reassigned, unless the reassignment refers to the original binding
let mut assigned_fields = Vec::new();
let mut cancel_lint = false;
for consecutive_statement in &block.stmts[stmt_idx + 1..] {
- // interrupt if the statement is a let binding (`Local`) that shadows the original
- // binding
- if stmt_shadows_binding(consecutive_statement, binding_name) {
- break;
- }
// find out if and which field was set by this `consecutive_statement`
- else if let Some((field_ident, assign_rhs)) =
- field_reassigned_by_stmt(consecutive_statement, binding_name)
- {
+ if let Some((field_ident, assign_rhs)) = field_reassigned_by_stmt(consecutive_statement, binding_name) {
// interrupt and cancel lint if assign_rhs references the original binding
if contains_name(binding_name, assign_rhs) {
cancel_lint = true;
first_assign = Some(consecutive_statement);
}
}
- // interrupt also if no field was assigned, since we only want to look at consecutive statements
+ // interrupt if no field was assigned, since we only want to look at consecutive statements
else {
break;
}
// if there are incorrectly assigned fields, do a span_lint_and_note to suggest
// construction using `Ty { fields, ..Default::default() }`
if !assigned_fields.is_empty() && !cancel_lint {
- // take the original assignment as span
- let stmt = &block.stmts[stmt_idx];
-
- if let StmtKind::Local(preceding_local) = &stmt.kind {
- // filter out fields like `= Default::default()`, because the FRU already covers them
- let assigned_fields = assigned_fields
- .into_iter()
- .filter(|(_, rhs)| !is_expr_default(rhs, cx))
- .collect::<Vec<(Symbol, &Expr<'_>)>>();
+ // if all fields of the struct are not assigned, add `.. Default::default()` to the suggestion.
+ let ext_with_default = !variant
+ .fields
+ .iter()
+ .all(|field| assigned_fields.iter().any(|(a, _)| a == &field.ident.name));
- // if all fields of the struct are not assigned, add `.. Default::default()` to the suggestion.
- let ext_with_default = !fields_of_type(binding_type)
- .iter()
- .all(|field| assigned_fields.iter().any(|(a, _)| a == &field.name));
+ let field_list = assigned_fields
+ .into_iter()
+ .map(|(field, rhs)| {
+ // extract and store the assigned value for help message
+ let value_snippet = snippet_with_macro_callsite(cx, rhs.span, "..");
+ format!("{}: {}", field, value_snippet)
+ })
+ .collect::<Vec<String>>()
+ .join(", ");
- let field_list = assigned_fields
- .into_iter()
- .map(|(field, rhs)| {
- // extract and store the assigned value for help message
- let value_snippet = snippet(cx, rhs.span, "..");
- format!("{}: {}", field, value_snippet)
- })
- .collect::<Vec<String>>()
- .join(", ");
-
- let sugg = if ext_with_default {
- if field_list.is_empty() {
- format!("{}::default()", binding_type)
- } else {
- format!("{} {{ {}, ..Default::default() }}", binding_type, field_list)
- }
+ let sugg = if ext_with_default {
+ if field_list.is_empty() {
+ format!("{}::default()", binding_type)
} else {
- format!("{} {{ {} }}", binding_type, field_list)
- };
+ format!("{} {{ {}, ..Default::default() }}", binding_type, field_list)
+ }
+ } else {
+ format!("{} {{ {} }}", binding_type, field_list)
+ };
- // span lint once per statement that binds default
- span_lint_and_note(
- cx,
- FIELD_REASSIGN_WITH_DEFAULT,
- first_assign.unwrap().span,
- "field assignment outside of initializer for an instance created with Default::default()",
- Some(preceding_local.span),
- &format!(
- "consider initializing the variable with `{}` and removing relevant reassignments",
- sugg
- ),
- );
- self.reassigned_linted.insert(span);
- }
+ // span lint once per statement that binds default
+ span_lint_and_note(
+ cx,
+ FIELD_REASSIGN_WITH_DEFAULT,
+ first_assign.unwrap().span,
+ "field assignment outside of initializer for an instance created with Default::default()",
+ Some(local.span),
+ &format!(
+ "consider initializing the variable with `{}` and removing relevant reassignments",
+ sugg
+ ),
+ );
+ self.reassigned_linted.insert(span);
}
}
}
if_chain! {
if let ExprKind::Call(ref fn_expr, _) = &expr.kind;
if let ExprKind::Path(qpath) = &fn_expr.kind;
- if let Res::Def(_, def_id) = qpath_res(cx, qpath, fn_expr.hir_id);
+ if let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id);
then {
// right hand side of assignment is `Default::default`
match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD)
}
}
-/// Returns the block indices, identifiers and types of bindings set as `Default::default()`, except
-/// for when the pattern type is a tuple.
-fn enumerate_bindings_using_default<'tcx>(
- cx: &LateContext<'tcx>,
- block: &Block<'tcx>,
-) -> Vec<(usize, Symbol, Ty<'tcx>, Span)> {
- block
- .stmts
- .iter()
- .enumerate()
- .filter_map(|(idx, stmt)| {
- if_chain! {
- // only take `let ...` statements
- if let StmtKind::Local(ref local) = stmt.kind;
- // only take bindings to identifiers
- if let PatKind::Binding(_, _, ident, _) = local.pat.kind;
- // that are not tuples
- let ty = cx.typeck_results().pat_ty(local.pat);
- if !matches!(ty.kind(), ty::Tuple(_));
- // only when assigning `... = Default::default()`
- if let Some(ref expr) = local.init;
- if is_expr_default(expr, cx);
- then {
- Some((idx, ident.name, ty, expr.span))
- } else {
- None
- }
- }
- })
- .collect()
-}
-
-fn stmt_shadows_binding(this: &Stmt<'_>, shadowed: Symbol) -> bool {
- if let StmtKind::Local(local) = &this.kind {
- if let PatKind::Binding(_, _, ident, _) = local.pat.kind {
- return ident.name == shadowed;
- }
- }
- false
-}
-
/// Returns the reassigned field and the assigning expression (right-hand side of assign).
fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Option<(Ident, &'tcx Expr<'tcx>)> {
if_chain! {
// only take assignments to fields where the left-hand side field is a field of
// the same binding as the previous statement
if let ExprKind::Field(ref binding, field_ident) = assign_lhs.kind;
- if let ExprKind::Path(ref qpath) = binding.kind;
- if let QPath::Resolved(_, path) = qpath;
+ if let ExprKind::Path(QPath::Resolved(_, path)) = binding.kind;
if let Some(second_binding_name) = path.segments.last();
if second_binding_name.ident.name == binding_name;
then {
}
}
}
-
-/// Returns the vec of fields for a struct and an empty vec for non-struct ADTs.
-fn fields_of_type(ty: Ty<'_>) -> Vec<Ident> {
- if let Adt(adt, _) = ty.kind() {
- if adt.is_struct() {
- let variant = &adt.non_enum_variant();
- return variant.fields.iter().map(|f| f.ident).collect();
- }
- }
- vec![]
-}