| use rustc_data_structures::fx::FxHashMap; |
| use rustc_errors::{Applicability, Diag}; |
| use rustc_hir as hir; |
| use rustc_hir::attrs::AttributeKind; |
| use rustc_hir::find_attr; |
| use rustc_middle::ty; |
| use rustc_middle::ty::TyCtxt; |
| use rustc_session::{declare_lint, impl_lint_pass}; |
| use rustc_span::Symbol; |
| use rustc_span::def_id::DefId; |
| use rustc_span::symbol::sym; |
| |
| use crate::{LateContext, LateLintPass}; |
| |
| declare_lint! { |
| /// The `default_overrides_default_fields` lint checks for manual `impl` blocks of the |
| /// `Default` trait of types with default field values. |
| /// |
| /// ### Example |
| /// |
| /// ```rust,compile_fail |
| /// #![feature(default_field_values)] |
| /// struct Foo { |
| /// x: i32 = 101, |
| /// y: NonDefault, |
| /// } |
| /// |
| /// struct NonDefault; |
| /// |
| /// #[deny(default_overrides_default_fields)] |
| /// impl Default for Foo { |
| /// fn default() -> Foo { |
| /// Foo { x: 100, y: NonDefault } |
| /// } |
| /// } |
| /// ``` |
| /// |
| /// {{produces}} |
| /// |
| /// ### Explanation |
| /// |
| /// Manually writing a `Default` implementation for a type that has |
| /// default field values runs the risk of diverging behavior between |
| /// `Type { .. }` and `<Type as Default>::default()`, which would be a |
| /// foot-gun for users of that type that would expect these to be |
| /// equivalent. If `Default` can't be derived due to some fields not |
| /// having a `Default` implementation, we encourage the use of `..` for |
| /// the fields that do have a default field value. |
| pub DEFAULT_OVERRIDES_DEFAULT_FIELDS, |
| Deny, |
| "detect `Default` impl that should use the type's default field values", |
| @feature_gate = default_field_values; |
| } |
| |
| #[derive(Default)] |
| pub(crate) struct DefaultCouldBeDerived; |
| |
| impl_lint_pass!(DefaultCouldBeDerived => [DEFAULT_OVERRIDES_DEFAULT_FIELDS]); |
| |
| impl<'tcx> LateLintPass<'tcx> for DefaultCouldBeDerived { |
| fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) { |
| // Look for manual implementations of `Default`. |
| let Some(default_def_id) = cx.tcx.get_diagnostic_item(sym::Default) else { return }; |
| let hir::ImplItemKind::Fn(_sig, body_id) = impl_item.kind else { return }; |
| let parent = cx.tcx.parent(impl_item.owner_id.to_def_id()); |
| if find_attr!(cx.tcx.get_all_attrs(parent), AttributeKind::AutomaticallyDerived(..)) { |
| // We don't care about what `#[derive(Default)]` produces in this lint. |
| return; |
| } |
| let Some(trait_ref) = cx.tcx.impl_trait_ref(parent) else { return }; |
| let trait_ref = trait_ref.instantiate_identity(); |
| if trait_ref.def_id != default_def_id { |
| return; |
| } |
| let ty = trait_ref.self_ty(); |
| let ty::Adt(def, _) = ty.kind() else { return }; |
| |
| // We now know we have a manually written definition of a `<Type as Default>::default()`. |
| |
| let type_def_id = def.did(); |
| let body = cx.tcx.hir_body(body_id); |
| |
| // FIXME: evaluate bodies with statements and evaluate bindings to see if they would be |
| // derivable. |
| let hir::ExprKind::Block(hir::Block { stmts: _, expr: Some(expr), .. }, None) = |
| body.value.kind |
| else { |
| return; |
| }; |
| |
| // Keep a mapping of field name to `hir::FieldDef` for every field in the type. We'll use |
| // these to check for things like checking whether it has a default or using its span for |
| // suggestions. |
| let orig_fields = match cx.tcx.hir_get_if_local(type_def_id) { |
| Some(hir::Node::Item(hir::Item { |
| kind: |
| hir::ItemKind::Struct( |
| _, |
| _generics, |
| hir::VariantData::Struct { fields, recovered: _ }, |
| ), |
| .. |
| })) => fields.iter().map(|f| (f.ident.name, f)).collect::<FxHashMap<_, _>>(), |
| _ => return, |
| }; |
| |
| // We check `fn default()` body is a single ADT literal and get all the fields that are |
| // being set. |
| let hir::ExprKind::Struct(_qpath, fields, tail) = expr.kind else { return }; |
| |
| // We have a struct literal |
| // |
| // struct Foo { |
| // field: Type, |
| // } |
| // |
| // impl Default for Foo { |
| // fn default() -> Foo { |
| // Foo { |
| // field: val, |
| // } |
| // } |
| // } |
| // |
| // We would suggest `#[derive(Default)]` if `field` has a default value, regardless of what |
| // it is; we don't want to encourage divergent behavior between `Default::default()` and |
| // `..`. |
| |
| if let hir::StructTailExpr::Base(_) = tail { |
| // This is *very* niche. We'd only get here if someone wrote |
| // impl Default for Ty { |
| // fn default() -> Ty { |
| // Ty { ..something() } |
| // } |
| // } |
| // where `something()` would have to be a call or path. |
| // We have nothing meaningful to do with this. |
| return; |
| } |
| |
| // At least one of the fields with a default value have been overridden in |
| // the `Default` implementation. We suggest removing it and relying on `..` |
| // instead. |
| let any_default_field_given = |
| fields.iter().any(|f| orig_fields.get(&f.ident.name).and_then(|f| f.default).is_some()); |
| |
| if !any_default_field_given { |
| // None of the default fields were actually provided explicitly, so the manual impl |
| // doesn't override them (the user used `..`), so there's no risk of divergent behavior. |
| return; |
| } |
| |
| let Some(local) = parent.as_local() else { return }; |
| let hir_id = cx.tcx.local_def_id_to_hir_id(local); |
| let hir::Node::Item(item) = cx.tcx.hir_node(hir_id) else { return }; |
| cx.tcx.node_span_lint(DEFAULT_OVERRIDES_DEFAULT_FIELDS, hir_id, item.span, |diag| { |
| mk_lint(cx.tcx, diag, type_def_id, parent, orig_fields, fields); |
| }); |
| } |
| } |
| |
| fn mk_lint( |
| tcx: TyCtxt<'_>, |
| diag: &mut Diag<'_, ()>, |
| type_def_id: DefId, |
| impl_def_id: DefId, |
| orig_fields: FxHashMap<Symbol, &hir::FieldDef<'_>>, |
| fields: &[hir::ExprField<'_>], |
| ) { |
| diag.primary_message("`Default` impl doesn't use the declared default field values"); |
| |
| // For each field in the struct expression |
| // - if the field in the type has a default value, it should be removed |
| // - elif the field is an expression that could be a default value, it should be used as the |
| // field's default value (FIXME: not done). |
| // - else, we wouldn't touch this field, it would remain in the manual impl |
| let mut removed_all_fields = true; |
| for field in fields { |
| if orig_fields.get(&field.ident.name).and_then(|f| f.default).is_some() { |
| diag.span_label(field.expr.span, "this field has a default value"); |
| } else { |
| removed_all_fields = false; |
| } |
| } |
| |
| if removed_all_fields { |
| let msg = "to avoid divergence in behavior between `Struct { .. }` and \ |
| `<Struct as Default>::default()`, derive the `Default`"; |
| if let Some(hir::Node::Item(impl_)) = tcx.hir_get_if_local(impl_def_id) { |
| diag.multipart_suggestion_verbose( |
| msg, |
| vec![ |
| (tcx.def_span(type_def_id).shrink_to_lo(), "#[derive(Default)] ".to_string()), |
| (impl_.span, String::new()), |
| ], |
| Applicability::MachineApplicable, |
| ); |
| } else { |
| diag.help(msg); |
| } |
| } else { |
| let msg = "use the default values in the `impl` with `Struct { mandatory_field, .. }` to \ |
| avoid them diverging over time"; |
| diag.help(msg); |
| } |
| } |