]> git.lizzy.rs Git - rust.git/blobdiff - clippy_lints/src/default.rs
Merge remote-tracking branch 'upstream/master' into rustup
[rust.git] / clippy_lints / src / default.rs
index 612c5355338a140f1ed371a01779fee6a4fbf20a..6fa1378b8c73d66971f75e93e899136a1aa93ab4 100644 (file)
@@ -1,4 +1,6 @@
-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;
@@ -6,7 +8,8 @@
 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;
@@ -103,18 +106,43 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
     }
 
     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
@@ -122,15 +150,8 @@ fn check_block<'tcx>(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) {
             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;
@@ -152,7 +173,7 @@ fn check_block<'tcx>(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) {
                         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;
                 }
@@ -161,55 +182,45 @@ fn check_block<'tcx>(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) {
             // 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);
             }
         }
     }
@@ -220,7 +231,7 @@ fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool
     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)
@@ -230,47 +241,6 @@ fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool
     }
 }
 
-/// 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! {
@@ -280,8 +250,7 @@ fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Op
         // 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 {
@@ -291,14 +260,3 @@ fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Op
         }
     }
 }
-
-/// 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![]
-}