rustc_lint/
default_could_be_derived.rs

1use rustc_data_structures::fx::FxHashMap;
2use rustc_errors::{Applicability, Diag};
3use rustc_hir as hir;
4use rustc_middle::ty;
5use rustc_middle::ty::TyCtxt;
6use rustc_session::{declare_lint, impl_lint_pass};
7use rustc_span::Symbol;
8use rustc_span::def_id::DefId;
9use rustc_span::symbol::sym;
10
11use crate::{LateContext, LateLintPass};
12
13declare_lint! {
14    /// The `default_overrides_default_fields` lint checks for manual `impl` blocks of the
15    /// `Default` trait of types with default field values.
16    ///
17    /// ### Example
18    ///
19    /// ```rust,compile_fail
20    /// #![feature(default_field_values)]
21    /// struct Foo {
22    ///     x: i32 = 101,
23    ///     y: NonDefault,
24    /// }
25    ///
26    /// struct NonDefault;
27    ///
28    /// #[deny(default_overrides_default_fields)]
29    /// impl Default for Foo {
30    ///     fn default() -> Foo {
31    ///         Foo { x: 100, y: NonDefault }
32    ///     }
33    /// }
34    /// ```
35    ///
36    /// {{produces}}
37    ///
38    /// ### Explanation
39    ///
40    /// Manually writing a `Default` implementation for a type that has
41    /// default field values runs the risk of diverging behavior between
42    /// `Type { .. }` and `<Type as Default>::default()`, which would be a
43    /// foot-gun for users of that type that would expect these to be
44    /// equivalent. If `Default` can't be derived due to some fields not
45    /// having a `Default` implementation, we encourage the use of `..` for
46    /// the fields that do have a default field value.
47    pub DEFAULT_OVERRIDES_DEFAULT_FIELDS,
48    Deny,
49    "detect `Default` impl that should use the type's default field values",
50    @feature_gate = default_field_values;
51}
52
53#[derive(Default)]
54pub(crate) struct DefaultCouldBeDerived;
55
56impl_lint_pass!(DefaultCouldBeDerived => [DEFAULT_OVERRIDES_DEFAULT_FIELDS]);
57
58impl<'tcx> LateLintPass<'tcx> for DefaultCouldBeDerived {
59    fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) {
60        // Look for manual implementations of `Default`.
61        let Some(default_def_id) = cx.tcx.get_diagnostic_item(sym::Default) else { return };
62        let hir::ImplItemKind::Fn(_sig, body_id) = impl_item.kind else { return };
63        let assoc = cx.tcx.associated_item(impl_item.owner_id);
64        let parent = assoc.container_id(cx.tcx);
65        if cx.tcx.has_attr(parent, sym::automatically_derived) {
66            // We don't care about what `#[derive(Default)]` produces in this lint.
67            return;
68        }
69        let Some(trait_ref) = cx.tcx.impl_trait_ref(parent) else { return };
70        let trait_ref = trait_ref.instantiate_identity();
71        if trait_ref.def_id != default_def_id {
72            return;
73        }
74        let ty = trait_ref.self_ty();
75        let ty::Adt(def, _) = ty.kind() else { return };
76
77        // We now know we have a manually written definition of a `<Type as Default>::default()`.
78
79        let hir = cx.tcx.hir();
80
81        let type_def_id = def.did();
82        let body = hir.body(body_id);
83
84        // FIXME: evaluate bodies with statements and evaluate bindings to see if they would be
85        // derivable.
86        let hir::ExprKind::Block(hir::Block { stmts: _, expr: Some(expr), .. }, None) =
87            body.value.kind
88        else {
89            return;
90        };
91
92        // Keep a mapping of field name to `hir::FieldDef` for every field in the type. We'll use
93        // these to check for things like checking whether it has a default or using its span for
94        // suggestions.
95        let orig_fields = match hir.get_if_local(type_def_id) {
96            Some(hir::Node::Item(hir::Item {
97                kind:
98                    hir::ItemKind::Struct(hir::VariantData::Struct { fields, recovered: _ }, _generics),
99                ..
100            })) => fields.iter().map(|f| (f.ident.name, f)).collect::<FxHashMap<_, _>>(),
101            _ => return,
102        };
103
104        // We check `fn default()` body is a single ADT literal and get all the fields that are
105        // being set.
106        let hir::ExprKind::Struct(_qpath, fields, tail) = expr.kind else { return };
107
108        // We have a struct literal
109        //
110        // struct Foo {
111        //     field: Type,
112        // }
113        //
114        // impl Default for Foo {
115        //     fn default() -> Foo {
116        //         Foo {
117        //             field: val,
118        //         }
119        //     }
120        // }
121        //
122        // We would suggest `#[derive(Default)]` if `field` has a default value, regardless of what
123        // it is; we don't want to encourage divergent behavior between `Default::default()` and
124        // `..`.
125
126        if let hir::StructTailExpr::Base(_) = tail {
127            // This is *very* niche. We'd only get here if someone wrote
128            // impl Default for Ty {
129            //     fn default() -> Ty {
130            //         Ty { ..something() }
131            //     }
132            // }
133            // where `something()` would have to be a call or path.
134            // We have nothing meaninful to do with this.
135            return;
136        }
137
138        // At least one of the fields with a default value have been overriden in
139        // the `Default` implementation. We suggest removing it and relying on `..`
140        // instead.
141        let any_default_field_given =
142            fields.iter().any(|f| orig_fields.get(&f.ident.name).and_then(|f| f.default).is_some());
143
144        if !any_default_field_given {
145            // None of the default fields were actually provided explicitly, so the manual impl
146            // doesn't override them (the user used `..`), so there's no risk of divergent behavior.
147            return;
148        }
149
150        let Some(local) = parent.as_local() else { return };
151        let hir_id = cx.tcx.local_def_id_to_hir_id(local);
152        let hir::Node::Item(item) = cx.tcx.hir_node(hir_id) else { return };
153        cx.tcx.node_span_lint(DEFAULT_OVERRIDES_DEFAULT_FIELDS, hir_id, item.span, |diag| {
154            mk_lint(cx.tcx, diag, type_def_id, parent, orig_fields, fields);
155        });
156    }
157}
158
159fn mk_lint(
160    tcx: TyCtxt<'_>,
161    diag: &mut Diag<'_, ()>,
162    type_def_id: DefId,
163    impl_def_id: DefId,
164    orig_fields: FxHashMap<Symbol, &hir::FieldDef<'_>>,
165    fields: &[hir::ExprField<'_>],
166) {
167    diag.primary_message("`Default` impl doesn't use the declared default field values");
168
169    // For each field in the struct expression
170    //   - if the field in the type has a default value, it should be removed
171    //   - elif the field is an expression that could be a default value, it should be used as the
172    //     field's default value (FIXME: not done).
173    //   - else, we wouldn't touch this field, it would remain in the manual impl
174    let mut removed_all_fields = true;
175    for field in fields {
176        if orig_fields.get(&field.ident.name).and_then(|f| f.default).is_some() {
177            diag.span_label(field.expr.span, "this field has a default value");
178        } else {
179            removed_all_fields = false;
180        }
181    }
182
183    if removed_all_fields {
184        let msg = "to avoid divergence in behavior between `Struct { .. }` and \
185                   `<Struct as Default>::default()`, derive the `Default`";
186        if let Some(hir::Node::Item(impl_)) = tcx.hir().get_if_local(impl_def_id) {
187            diag.multipart_suggestion_verbose(
188                msg,
189                vec![
190                    (tcx.def_span(type_def_id).shrink_to_lo(), "#[derive(Default)] ".to_string()),
191                    (impl_.span, String::new()),
192                ],
193                Applicability::MachineApplicable,
194            );
195        } else {
196            diag.help(msg);
197        }
198    } else {
199        let msg = "use the default values in the `impl` with `Struct { mandatory_field, .. }` to \
200                   avoid them diverging over time";
201        diag.help(msg);
202    }
203}