Skip to content

Commit 887b119

Browse files
committed
Rework logic to make it work x-crate and remove special cases
Store a mapping betweein the `DefId` for an `impl Default for Ty` and the `DefId` of either a Unit variant/struct or an fn with no arguments that is called within `<Ty as Default>::default()`. When linting `impl`s, if it is for `Default`, we evaluate the contents of their `fn default()`. If it is *only* an ADT literal for `Self` and every field is either a "known to be defaulted" value (`0` or `false`), an explicit `Default::default()` call or a call or path to the same "equivalent" `DefId` from that field's type's `Default::default()` implementation.
1 parent 53be63c commit 887b119

File tree

10 files changed

+253
-88
lines changed

10 files changed

+253
-88
lines changed

Diff for: compiler/rustc_lint/src/default_could_be_derived.rs

+72-86
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use rustc_ast::LitKind;
22
use rustc_errors::Applicability;
33
use rustc_hir as hir;
44
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
5-
use rustc_middle::ty::TyCtxt;
5+
use rustc_middle::ty::Ty;
66
use rustc_session::{declare_lint, declare_lint_pass};
77
use rustc_span::symbol::{kw, sym};
88

@@ -135,6 +135,8 @@ impl<'tcx> LateLintPass<'tcx> for DefaultCouldBeDerived {
135135
// We have a manual `impl Default for Ty {}` item, where `Ty` has no type parameters.
136136

137137
for assoc in data.items {
138+
// We look for the user's `fn default() -> Self` associated fn of the `Default` impl.
139+
138140
let hir::AssocItemKind::Fn { has_self: false } = assoc.kind else { continue };
139141
if assoc.ident.name != kw::Default {
140142
continue;
@@ -148,6 +150,8 @@ impl<'tcx> LateLintPass<'tcx> for DefaultCouldBeDerived {
148150
continue;
149151
};
150152

153+
// We check `fn default()` body is a single ADT literal and all the fields are being
154+
// set to something equivalent to the corresponding types' `Default::default()`.
151155
match expr.kind {
152156
hir::ExprKind::Path(hir::QPath::Resolved(_, path))
153157
if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), def_id) =
@@ -211,9 +215,10 @@ impl<'tcx> LateLintPass<'tcx> for DefaultCouldBeDerived {
211215
//
212216
// We suggest #[derive(Default)] if
213217
// - `val` is `Default::default()`
218+
// - `val` matches the `Default::default()` body for that type
214219
// - `val` is `0`
215220
// - `val` is `false`
216-
if fields.iter().all(|f| check_expr(cx.tcx, f.expr.kind)) {
221+
if fields.iter().all(|f| check_expr(cx, f.expr)) {
217222
cx.tcx.node_span_lint(
218223
DEFAULT_COULD_BE_DERIVED,
219224
item.hir_id(),
@@ -241,7 +246,7 @@ impl<'tcx> LateLintPass<'tcx> for DefaultCouldBeDerived {
241246
path.res
242247
{
243248
let type_def_id = cx.tcx.parent(ctor_def_id); // From Ctor to struct
244-
if args.iter().all(|expr| check_expr(cx.tcx, expr.kind)) {
249+
if args.iter().all(|expr| check_expr(cx, expr)) {
245250
// We have a struct literal
246251
//
247252
// struct Foo(Type);
@@ -254,6 +259,7 @@ impl<'tcx> LateLintPass<'tcx> for DefaultCouldBeDerived {
254259
//
255260
// We suggest #[derive(Default)] if
256261
// - `val` is `Default::default()`
262+
// - `val` matches the `Default::default()` body for that type
257263
// - `val` is `0`
258264
// - `val` is `false`
259265
cx.tcx.node_span_lint(
@@ -319,97 +325,77 @@ impl<'tcx> LateLintPass<'tcx> for DefaultCouldBeDerived {
319325
}
320326
}
321327

322-
fn check_expr(tcx: TyCtxt<'_>, kind: hir::ExprKind<'_>) -> bool {
323-
let Some(default_def_id) = tcx.get_diagnostic_item(sym::Default) else {
328+
fn check_path<'tcx>(
329+
cx: &LateContext<'tcx>,
330+
path: &hir::QPath<'_>,
331+
hir_id: hir::HirId,
332+
ty: Ty<'tcx>,
333+
) -> bool {
334+
let Some(default_def_id) = cx.tcx.get_diagnostic_item(sym::Default) else {
324335
return false;
325336
};
326-
match kind {
337+
let res = cx.qpath_res(&path, hir_id);
338+
let Some(def_id) = res.opt_def_id() else { return false };
339+
if cx.tcx.is_diagnostic_item(sym::default_fn, def_id) {
340+
// We have `field: Default::default(),`. This is what the derive would do already.
341+
return true;
342+
}
343+
// For every `Default` impl for this type (there should be a single one), we see if it
344+
// has a "canonical" `DefId` for a fn call with no arguments, or a path. If it does, we
345+
// check that `DefId` with the `DefId` of this field's value if it is also a call/path.
346+
// If there's a match, it means that the contents of that type's `Default` impl are the
347+
// same to what the user wrote on *their* `Default` impl for this field.
348+
let mut equivalents = vec![];
349+
cx.tcx.for_each_relevant_impl(default_def_id, ty, |impl_def_id| {
350+
let equivalent = match impl_def_id.as_local() {
351+
None => cx.tcx.get_default_impl_equivalent(impl_def_id),
352+
Some(local) => {
353+
let def_kind = cx.tcx.def_kind(impl_def_id);
354+
cx.tcx.get_default_equivalent(def_kind, local)
355+
}
356+
};
357+
if let Some(did) = equivalent {
358+
equivalents.push(did);
359+
}
360+
});
361+
for did in equivalents {
362+
if did == def_id {
363+
return true;
364+
}
365+
}
366+
false
367+
}
368+
369+
fn check_expr(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
370+
match expr.kind {
327371
hir::ExprKind::Lit(spanned_lit) => match spanned_lit.node {
328372
LitKind::Int(val, _) if val == 0 => true, // field: 0,
329373
LitKind::Bool(false) => true, // field: false,
330374
_ => false,
331375
},
332-
hir::ExprKind::Call(expr, [])
333-
if let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = expr.kind
334-
&& let Some(def_id) = path.res.opt_def_id()
335-
&& tcx.is_diagnostic_item(sym::default_fn, def_id) =>
336-
{
337-
// field: Default::default(),
338-
true
376+
hir::ExprKind::Call(hir::Expr { kind: hir::ExprKind::Path(path), hir_id, .. }, []) => {
377+
// `field: foo(),` or `field: Ty::assoc(),`
378+
let Some(ty) = cx
379+
.tcx
380+
.has_typeck_results(expr.hir_id.owner.def_id)
381+
.then(|| cx.tcx.typeck(expr.hir_id.owner.def_id))
382+
.and_then(|typeck| typeck.expr_ty_adjusted_opt(expr))
383+
else {
384+
return false;
385+
};
386+
check_path(cx, &path, *hir_id, ty)
339387
}
340-
hir::ExprKind::Path(hir::QPath::Resolved(_, path))
341-
if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), ctor_def_id) =
342-
path.res =>
343-
{
344-
// FIXME: We should use a better check where we explore existing
345-
// `impl Default for def_id` of the found type when `def_id` is not
346-
// local and see compare them against what we have here. For now,
347-
// we special case `Option::None` and only check unit variants of
348-
// local `Default` impls.
349-
let var_def_id = tcx.parent(ctor_def_id); // From Ctor to variant
350-
351-
// We explicitly check for `Option::<T>::None`. If `Option` was
352-
// local, it would be accounted by the logic further down, but
353-
// because the analysis uses purely the HIR, that doesn't work
354-
// accross crates.
355-
//
356-
// field: None,
357-
let mut found = tcx.is_lang_item(var_def_id, hir::LangItem::OptionNone);
358-
359-
// Look at the local `impl Default for ty` of the field's `ty`.
360-
let ty_def_id = tcx.parent(var_def_id); // From variant to enum
361-
let ty = tcx.type_of(ty_def_id).instantiate_identity();
362-
tcx.for_each_relevant_impl(default_def_id, ty, |impl_did| {
363-
let hir = tcx.hir();
364-
let Some(hir::Node::Item(impl_item)) = hir.get_if_local(impl_did) else {
365-
return;
366-
};
367-
let hir::ItemKind::Impl(impl_item) = impl_item.kind else {
368-
return;
369-
};
370-
for assoc in impl_item.items {
371-
let hir::AssocItemKind::Fn { has_self: false } = assoc.kind else {
372-
continue;
373-
};
374-
if assoc.ident.name != kw::Default {
375-
continue;
376-
}
377-
let assoc = hir.impl_item(assoc.id);
378-
let hir::ImplItemKind::Fn(_ty, body) = assoc.kind else {
379-
continue;
380-
};
381-
let body = hir.body(body);
382-
let hir::ExprKind::Block(hir::Block { stmts: [], expr: Some(expr), .. }, None) =
383-
body.value.kind
384-
else {
385-
continue;
386-
};
387-
// Look at a specific implementation of `Default::default()`
388-
// for their content and see if they are requivalent to what
389-
// the user wrote in their manual `impl` for a given field.
390-
match expr.kind {
391-
hir::ExprKind::Path(hir::QPath::Resolved(_, path))
392-
if let Res::Def(
393-
DefKind::Ctor(CtorOf::Variant, CtorKind::Const),
394-
orig_def_id,
395-
) = path.res =>
396-
{
397-
// We found
398-
//
399-
// field: Foo::Unit,
400-
//
401-
// and
402-
//
403-
// impl Default for Foo {
404-
// fn default() -> Foo { Foo::Unit }
405-
// }
406-
found |= orig_def_id == ctor_def_id
407-
}
408-
_ => {}
409-
}
410-
}
411-
});
412-
found
388+
hir::ExprKind::Path(path) => {
389+
// `field: qualified::Path,` or `field: <Ty as Trait>::Assoc,`
390+
let Some(ty) = cx
391+
.tcx
392+
.has_typeck_results(expr.hir_id.owner.def_id)
393+
.then(|| cx.tcx.typeck(expr.hir_id.owner.def_id))
394+
.and_then(|typeck| typeck.expr_ty_adjusted_opt(expr))
395+
else {
396+
return false;
397+
};
398+
check_path(cx, &path, expr.hir_id, ty)
413399
}
414400
_ => false,
415401
}

Diff for: compiler/rustc_metadata/src/rmeta/decoder.rs

+6
Original file line numberDiff line numberDiff line change
@@ -1174,6 +1174,12 @@ impl<'a> CrateMetadataRef<'a> {
11741174
self.root.tables.default_fields.get(self, id).map(|d| d.decode(self))
11751175
}
11761176

1177+
/// For a given `impl Default for Ty`, return the "equivalence" for it, namely the `DefId` of
1178+
/// a path or function with no arguments that get's called from `<Ty as Default>::default()`.
1179+
fn get_default_impl_equivalent(self, id: DefIndex) -> Option<DefId> {
1180+
self.root.tables.default_impl_equivalent.get(self, id).map(|d| d.decode(self))
1181+
}
1182+
11771183
fn get_trait_item_def_id(self, id: DefIndex) -> Option<DefId> {
11781184
self.root.tables.trait_item_def_id.get(self, id).map(|d| d.decode_from_cdata(self))
11791185
}

Diff for: compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs

+2
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,8 @@ provide! { tcx, def_id, other, cdata,
423423
syms
424424
}
425425

426+
get_default_impl_equivalent => { cdata.get_default_impl_equivalent(def_id.index) }
427+
426428
crate_extern_paths => { cdata.source().paths().cloned().collect() }
427429
expn_that_defined => { cdata.get_expn_that_defined(def_id.index, tcx.sess) }
428430
is_doc_hidden => { cdata.get_attr_flags(def_id.index).contains(AttrFlags::IS_DOC_HIDDEN) }

Diff for: compiler/rustc_metadata/src/rmeta/encoder.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1571,6 +1571,10 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
15711571
let table = tcx.associated_types_for_impl_traits_in_associated_fn(def_id);
15721572
record_defaulted_array!(self.tables.associated_types_for_impl_traits_in_associated_fn[def_id] <- table);
15731573
}
1574+
1575+
if let Some(path_def_id) = tcx.get_default_equivalent(def_kind, local_id) {
1576+
record!(self.tables.default_impl_equivalent[def_id] <- path_def_id);
1577+
}
15741578
}
15751579

15761580
for (def_id, impls) in &tcx.crate_inherent_impls(()).0.inherent_impls {

Diff for: compiler/rustc_metadata/src/rmeta/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,7 @@ define_tables! {
451451
trait_item_def_id: Table<DefIndex, RawDefId>,
452452
expn_that_defined: Table<DefIndex, LazyValue<ExpnId>>,
453453
default_fields: Table<DefIndex, LazyValue<DefId>>,
454+
default_impl_equivalent: Table<DefIndex, LazyValue<DefId>>,
454455
params_in_repr: Table<DefIndex, LazyValue<BitSet<u32>>>,
455456
repr_options: Table<DefIndex, LazyValue<ReprOptions>>,
456457
// `def_keys` and `def_path_hashes` represent a lazy version of a

Diff for: compiler/rustc_middle/src/query/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -1803,6 +1803,12 @@ rustc_queries! {
18031803
desc { "looking up lifetime defaults for generic parameter `{}`", tcx.def_path_str(def_id) }
18041804
separate_provide_extern
18051805
}
1806+
1807+
query get_default_impl_equivalent(def_id: DefId) -> Option<DefId> {
1808+
desc { |tcx| "looking up the unit variant or fn call with no arguments equivalence for the `Default::default()` of `{}`", tcx.def_path_str(def_id) }
1809+
separate_provide_extern
1810+
}
1811+
18061812
query late_bound_vars_map(owner_id: hir::OwnerId)
18071813
-> &'tcx SortedMap<ItemLocalId, Vec<ty::BoundVariableKind>> {
18081814
desc { |tcx| "looking up late bound vars inside `{}`", tcx.def_path_str(owner_id) }

Diff for: compiler/rustc_middle/src/ty/context.rs

+53-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use rustc_errors::{
3030
Applicability, Diag, DiagCtxtHandle, ErrorGuaranteed, LintDiagnostic, MultiSpan,
3131
};
3232
use rustc_hir as hir;
33-
use rustc_hir::def::{CtorKind, DefKind};
33+
use rustc_hir::def::{CtorKind, DefKind, Res};
3434
use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE, LocalDefId};
3535
use rustc_hir::definitions::Definitions;
3636
use rustc_hir::intravisit::Visitor;
@@ -3206,6 +3206,58 @@ impl<'tcx> TyCtxt<'tcx> {
32063206
}
32073207
}
32083208

3209+
fn get_expr_equivalent(self, expr: &hir::Expr<'_>) -> Option<DefId> {
3210+
match &expr.kind {
3211+
hir::ExprKind::Path(qpath) => {
3212+
if self.has_typeck_results(expr.hir_id.owner.def_id) {
3213+
let res = self.typeck(expr.hir_id.owner.def_id).qpath_res(&qpath, expr.hir_id);
3214+
return res.opt_def_id();
3215+
}
3216+
}
3217+
hir::ExprKind::Call(hir::Expr { kind: hir::ExprKind::Path(qpath), hir_id, .. }, []) => {
3218+
if self.has_typeck_results(expr.hir_id.owner.def_id) {
3219+
let res = self.typeck(expr.hir_id.owner.def_id).qpath_res(&qpath, *hir_id);
3220+
return res.opt_def_id();
3221+
}
3222+
}
3223+
_ => {}
3224+
}
3225+
None
3226+
}
3227+
3228+
/// Given an `impl Default for Ty` item, return the `DefId` of the single path or fn call within
3229+
/// the `<Ty as Default::default()` function. This is used to compare between a type's
3230+
/// `Default::defaut()` implementation and what a user has used in their manual `Default` impl
3231+
/// for a type they control that has a field of type `Ty`.
3232+
pub fn get_default_equivalent(self, def_kind: DefKind, local_id: LocalDefId) -> Option<DefId> {
3233+
if (DefKind::Impl { of_trait: true }) == def_kind
3234+
&& let hir::Node::Item(item) = self.hir_node_by_def_id(local_id)
3235+
&& let hir::ItemKind::Impl(data) = item.kind
3236+
&& let Some(trait_ref) = data.of_trait
3237+
&& let Res::Def(DefKind::Trait, trait_def_id) = trait_ref.path.res
3238+
&& let Some(default_def_id) = self.get_diagnostic_item(sym::Default)
3239+
&& Some(trait_def_id) == Some(default_def_id)
3240+
{
3241+
let hir = self.hir();
3242+
for assoc in data.items {
3243+
let hir::AssocItemKind::Fn { has_self: false } = assoc.kind else { continue };
3244+
if assoc.ident.name != kw::Default {
3245+
continue;
3246+
}
3247+
let assoc = hir.impl_item(assoc.id);
3248+
let hir::ImplItemKind::Fn(_ty, body) = assoc.kind else { continue };
3249+
let body = hir.body(body);
3250+
let hir::ExprKind::Block(hir::Block { stmts: [], expr: Some(expr), .. }, None) =
3251+
body.value.kind
3252+
else {
3253+
continue;
3254+
};
3255+
return self.get_expr_equivalent(&expr);
3256+
}
3257+
}
3258+
None
3259+
}
3260+
32093261
/// Whether this is a trait implementation that has `#[diagnostic::do_not_recommend]`
32103262
pub fn do_not_recommend_impl(self, def_id: DefId) -> bool {
32113263
self.get_diagnostic_attr(def_id, sym::do_not_recommend).is_some()

Diff for: tests/ui/structs/manual-default-impl-could-be-derived.fixed

+30
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,32 @@
5959
}
6060

6161

62+
// Account for fn calls that are not assoc fns, still check that they match between what the user
63+
// wrote and the Default impl.
64+
#[derive(Default)] struct J {
65+
x: K,
66+
}
67+
68+
69+
struct K;
70+
71+
impl Default for K { // *could* be derived, but it isn't lintable because of the `foo()` call
72+
fn default() -> Self {
73+
foo()
74+
}
75+
}
76+
77+
fn foo() -> K {
78+
K
79+
}
80+
81+
// Verify that cross-crate tracking of "equivalences" keeps working.
82+
#[derive(PartialEq, Debug)]
83+
#[derive(Default)] struct L {
84+
x: Vec<i32>,
85+
}
86+
87+
6288
fn main() {
6389
let _ = A::default();
6490
let _ = B::default();
@@ -68,4 +94,8 @@ fn main() {
6894
let _ = F::<i32>::default();
6995
let _ = G::default();
7096
assert_eq!(H::default(), H { .. });
97+
let _ = I::default();
98+
let _ = J::default();
99+
let _ = K::default();
100+
let _ = L::default();
71101
}

0 commit comments

Comments
 (0)