| use rustc_data_structures::fx::FxHashMap; | |
| use rustc_errors::{Applicability, Diag, DiagCtxtHandle, Diagnostic, Level}; | |
| use rustc_hir as hir; | |
| use rustc_middle::ty; | |
| use rustc_middle::ty::TyCtxt; | |
| use rustc_session::{declare_lint, declare_lint_pass}; | |
| use rustc_span::def_id::DefId; | |
| use rustc_span::symbol::sym; | |
| use rustc_span::{Span, Symbol}; | |
| 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; | |
| } | |
| declare_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 hir::ImplItemImplKind::Trait { trait_item_def_id, .. } = impl_item.impl_kind else { | |
| return; | |
| }; | |
| if !trait_item_def_id.is_ok_and(|id| cx.tcx.is_diagnostic_item(sym::default_fn, id)) { | |
| return; | |
| } | |
| let hir::ImplItemKind::Fn(_sig, body_id) = impl_item.kind else { return }; | |
| let impl_id = cx.tcx.local_parent(impl_item.owner_id.def_id); | |
| if cx.tcx.is_automatically_derived(impl_id.to_def_id()) { | |
| // We don't care about what `#[derive(Default)]` produces in this lint. | |
| return; | |
| } | |
| let ty = cx.tcx.type_of(impl_id).instantiate_identity().skip_norm_wip(); | |
| 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 hir_id = cx.tcx.local_def_id_to_hir_id(impl_id); | |
| let span = cx.tcx.hir_span_with_body(hir_id); | |
| cx.tcx.emit_node_span_lint( | |
| DEFAULT_OVERRIDES_DEFAULT_FIELDS, | |
| hir_id, | |
| span, | |
| WrongDefaultImpl { tcx: cx.tcx, type_def_id, orig_fields, fields, impl_span: span }, | |
| ); | |
| } | |
| } | |
| struct WrongDefaultImpl<'a, 'tcx> { | |
| tcx: TyCtxt<'tcx>, | |
| type_def_id: DefId, | |
| orig_fields: FxHashMap<Symbol, &'a hir::FieldDef<'tcx>>, | |
| fields: &'a [hir::ExprField<'tcx>], | |
| impl_span: Span, | |
| } | |
| impl<'a, 'b, 'tcx> Diagnostic<'a, ()> for WrongDefaultImpl<'b, 'tcx> { | |
| fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> { | |
| let Self { tcx, type_def_id, orig_fields, fields, impl_span } = self; | |
| let mut diag = | |
| Diag::new(dcx, level, "`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 { | |
| diag.multipart_suggestion( | |
| "to avoid divergence in behavior between `Struct { .. }` and \ | |
| `<Struct as Default>::default()`, derive the `Default`", | |
| vec![ | |
| (tcx.def_span(type_def_id).shrink_to_lo(), "#[derive(Default)] ".to_string()), | |
| (impl_span, String::new()), | |
| ], | |
| Applicability::MachineApplicable, | |
| ); | |
| } else { | |
| diag.help( | |
| "use the default values in the `impl` with `Struct { mandatory_field, .. }` to \ | |
| avoid them diverging over time", | |
| ); | |
| } | |
| diag | |
| } | |
| } |