Skip to content

Commit 3a0d106

Browse files
committed
Auto merge of #61812 - jonas-schievink:assoc-ty-defaults, r=nikomatsakis
Implement RFC 2532 – Associated Type Defaults This is a partial implementation that is still missing the changes to object types, since I ran into some trouble while implementing that. I'm opening this part already to get feedback on the implementation and the unexpected test fallout (see my comments below). The remaining changes can be done in a later PR. Blockers before this can land: * [x] Resolve unsoundness around interaction with specialization (#61812 (comment)) - #64564 cc #29661 Fixes #53907 Fixes #54182 Fixes #62211 Fixes #41868 Fixes #63593 Fixes #47385 Fixes #43924 Fixes #32350 Fixes #26681 Fixes #67187
2 parents 46f5aa9 + 6cc268b commit 3a0d106

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+2048
-154
lines changed

src/librustc_error_codes/error_codes/E0399.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
#### Note: this error code is no longer emitted by the compiler
2+
13
You implemented a trait, overriding one or more of its associated types but did
24
not reimplement its default methods.
35

46
Example of erroneous code:
57

6-
```compile_fail,E0399
8+
```
79
#![feature(associated_type_defaults)]
810
911
pub trait Foo {

src/librustc_infer/traits/project.rs

+29-14
Original file line numberDiff line numberDiff line change
@@ -1054,25 +1054,40 @@ fn assemble_candidates_from_impls<'cx, 'tcx>(
10541054
// an error when we confirm the candidate
10551055
// (which will ultimately lead to `normalize_to_error`
10561056
// being invoked).
1057-
node_item.item.defaultness.has_value()
1057+
false
10581058
} else {
1059+
// If we're looking at a trait *impl*, the item is
1060+
// specializable if the impl or the item are marked
1061+
// `default`.
10591062
node_item.item.defaultness.is_default()
10601063
|| super::util::impl_is_default(selcx.tcx(), node_item.node.def_id())
10611064
};
10621065

1063-
// Only reveal a specializable default if we're past type-checking
1064-
// and the obligations is monomorphic, otherwise passes such as
1065-
// transmute checking and polymorphic MIR optimizations could
1066-
// get a result which isn't correct for all monomorphizations.
1067-
if !is_default {
1068-
true
1069-
} else if obligation.param_env.reveal == Reveal::All {
1070-
// NOTE(eddyb) inference variables can resolve to parameters, so
1071-
// assume `poly_trait_ref` isn't monomorphic, if it contains any.
1072-
let poly_trait_ref = selcx.infcx().resolve_vars_if_possible(&poly_trait_ref);
1073-
!poly_trait_ref.needs_infer() && !poly_trait_ref.needs_subst()
1074-
} else {
1075-
false
1066+
match is_default {
1067+
// Non-specializable items are always projectable
1068+
false => true,
1069+
1070+
// Only reveal a specializable default if we're past type-checking
1071+
// and the obligation is monomorphic, otherwise passes such as
1072+
// transmute checking and polymorphic MIR optimizations could
1073+
// get a result which isn't correct for all monomorphizations.
1074+
true if obligation.param_env.reveal == Reveal::All => {
1075+
// NOTE(eddyb) inference variables can resolve to parameters, so
1076+
// assume `poly_trait_ref` isn't monomorphic, if it contains any.
1077+
let poly_trait_ref =
1078+
selcx.infcx().resolve_vars_if_possible(&poly_trait_ref);
1079+
!poly_trait_ref.needs_infer() && !poly_trait_ref.needs_subst()
1080+
}
1081+
1082+
true => {
1083+
debug!(
1084+
"assemble_candidates_from_impls: not eligible due to default: \
1085+
assoc_ty={} predicate={}",
1086+
selcx.tcx().def_path_str(node_item.item.def_id),
1087+
obligation.predicate,
1088+
);
1089+
false
1090+
}
10761091
}
10771092
}
10781093
super::VtableParam(..) => {

src/librustc_typeck/check/mod.rs

-21
Original file line numberDiff line numberDiff line change
@@ -1964,7 +1964,6 @@ fn check_impl_items_against_trait<'tcx>(
19641964

19651965
// Locate trait definition and items
19661966
let trait_def = tcx.trait_def(impl_trait_ref.def_id);
1967-
let mut overridden_associated_type = None;
19681967

19691968
let impl_items = || impl_item_refs.iter().map(|iiref| tcx.hir().impl_item(iiref.id));
19701969

@@ -2046,9 +2045,6 @@ fn check_impl_items_against_trait<'tcx>(
20462045
hir::ImplItemKind::OpaqueTy(..) | hir::ImplItemKind::TyAlias(_) => {
20472046
let opt_trait_span = tcx.hir().span_if_local(ty_trait_item.def_id);
20482047
if ty_trait_item.kind == ty::AssocKind::Type {
2049-
if ty_trait_item.defaultness.has_value() {
2050-
overridden_associated_type = Some(impl_item);
2051-
}
20522048
compare_ty_impl(
20532049
tcx,
20542050
&ty_impl_item,
@@ -2082,8 +2078,6 @@ fn check_impl_items_against_trait<'tcx>(
20822078

20832079
// Check for missing items from trait
20842080
let mut missing_items = Vec::new();
2085-
let mut invalidated_items = Vec::new();
2086-
let associated_type_overridden = overridden_associated_type.is_some();
20872081
for trait_item in tcx.associated_items(impl_trait_ref.def_id).in_definition_order() {
20882082
let is_implemented = trait_def
20892083
.ancestors(tcx, impl_id)
@@ -2094,28 +2088,13 @@ fn check_impl_items_against_trait<'tcx>(
20942088
if !is_implemented && !traits::impl_is_default(tcx, impl_id) {
20952089
if !trait_item.defaultness.has_value() {
20962090
missing_items.push(*trait_item);
2097-
} else if associated_type_overridden {
2098-
invalidated_items.push(trait_item.ident);
20992091
}
21002092
}
21012093
}
21022094

21032095
if !missing_items.is_empty() {
21042096
missing_items_err(tcx, impl_span, &missing_items, full_impl_span);
21052097
}
2106-
2107-
if !invalidated_items.is_empty() {
2108-
let invalidator = overridden_associated_type.unwrap();
2109-
struct_span_err!(
2110-
tcx.sess,
2111-
invalidator.span,
2112-
E0399,
2113-
"the following trait items need to be reimplemented as `{}` was overridden: `{}`",
2114-
invalidator.ident,
2115-
invalidated_items.iter().map(|name| name.to_string()).collect::<Vec<_>>().join("`, `")
2116-
)
2117-
.emit();
2118-
}
21192098
}
21202099

21212100
fn missing_items_err(

src/librustc_typeck/check/wfcheck.rs

+99
Original file line numberDiff line numberDiff line change
@@ -425,10 +425,109 @@ fn check_trait(tcx: TyCtxt<'_>, item: &hir::Item<'_>) {
425425

426426
for_item(tcx, item).with_fcx(|fcx, _| {
427427
check_where_clauses(tcx, fcx, item.span, trait_def_id, None);
428+
check_associated_type_defaults(fcx, trait_def_id);
429+
428430
vec![]
429431
});
430432
}
431433

434+
/// Checks all associated type defaults of trait `trait_def_id`.
435+
///
436+
/// Assuming the defaults are used, check that all predicates (bounds on the
437+
/// assoc type and where clauses on the trait) hold.
438+
fn check_associated_type_defaults(fcx: &FnCtxt<'_, '_>, trait_def_id: DefId) {
439+
let tcx = fcx.tcx;
440+
let substs = InternalSubsts::identity_for_item(tcx, trait_def_id);
441+
442+
// For all assoc. types with defaults, build a map from
443+
// `<Self as Trait<...>>::Assoc` to the default type.
444+
let map = tcx
445+
.associated_items(trait_def_id)
446+
.in_definition_order()
447+
.filter_map(|item| {
448+
if item.kind == ty::AssocKind::Type && item.defaultness.has_value() {
449+
// `<Self as Trait<...>>::Assoc`
450+
let proj = ty::ProjectionTy { substs, item_def_id: item.def_id };
451+
let default_ty = tcx.type_of(item.def_id);
452+
debug!("assoc. type default mapping: {} -> {}", proj, default_ty);
453+
Some((proj, default_ty))
454+
} else {
455+
None
456+
}
457+
})
458+
.collect::<FxHashMap<_, _>>();
459+
460+
/// Replaces projections of associated types with their default types.
461+
///
462+
/// This does a "shallow substitution", meaning that defaults that refer to
463+
/// other defaulted assoc. types will still refer to the projection
464+
/// afterwards, not to the other default. For example:
465+
///
466+
/// ```compile_fail
467+
/// trait Tr {
468+
/// type A: Clone = Vec<Self::B>;
469+
/// type B = u8;
470+
/// }
471+
/// ```
472+
///
473+
/// This will end up replacing the bound `Self::A: Clone` with
474+
/// `Vec<Self::B>: Clone`, not with `Vec<u8>: Clone`. If we did a deep
475+
/// substitution and ended up with the latter, the trait would be accepted.
476+
/// If an `impl` then replaced `B` with something that isn't `Clone`,
477+
/// suddenly the default for `A` is no longer valid. The shallow
478+
/// substitution forces the trait to add a `B: Clone` bound to be accepted,
479+
/// which means that an `impl` can replace any default without breaking
480+
/// others.
481+
///
482+
/// Note that this isn't needed for soundness: The defaults would still be
483+
/// checked in any impl that doesn't override them.
484+
struct DefaultNormalizer<'tcx> {
485+
tcx: TyCtxt<'tcx>,
486+
map: FxHashMap<ty::ProjectionTy<'tcx>, Ty<'tcx>>,
487+
}
488+
489+
impl<'tcx> ty::fold::TypeFolder<'tcx> for DefaultNormalizer<'tcx> {
490+
fn tcx<'a>(&'a self) -> TyCtxt<'tcx> {
491+
self.tcx
492+
}
493+
494+
fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> {
495+
match t.kind {
496+
ty::Projection(proj_ty) => {
497+
if let Some(default) = self.map.get(&proj_ty) {
498+
default
499+
} else {
500+
t.super_fold_with(self)
501+
}
502+
}
503+
_ => t.super_fold_with(self),
504+
}
505+
}
506+
}
507+
508+
// Now take all predicates defined on the trait, replace any mention of
509+
// the assoc. types with their default, and prove them.
510+
// We only consider predicates that directly mention the assoc. type.
511+
let mut norm = DefaultNormalizer { tcx, map };
512+
let predicates = fcx.tcx.predicates_of(trait_def_id);
513+
for &(orig_pred, span) in predicates.predicates.iter() {
514+
let pred = orig_pred.fold_with(&mut norm);
515+
if pred != orig_pred {
516+
// Mentions one of the defaulted assoc. types
517+
debug!("default suitability check: proving predicate: {} -> {}", orig_pred, pred);
518+
let pred = fcx.normalize_associated_types_in(span, &pred);
519+
let cause = traits::ObligationCause::new(
520+
span,
521+
fcx.body_id,
522+
traits::ItemObligation(trait_def_id),
523+
);
524+
let obligation = traits::Obligation::new(cause, fcx.param_env, pred);
525+
526+
fcx.register_predicate(obligation);
527+
}
528+
}
529+
}
530+
432531
fn check_item_fn(tcx: TyCtxt<'_>, item: &hir::Item<'_>) {
433532
for_item(tcx, item).with_fcx(|fcx, tcx| {
434533
let def_id = fcx.tcx.hir().local_def_id(item.hir_id);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// build-fail
2+
3+
// Cyclic assoc. const defaults don't error unless *used*
4+
trait Tr {
5+
const A: u8 = Self::B;
6+
//~^ ERROR cycle detected when const-evaluating + checking `Tr::A`
7+
8+
const B: u8 = Self::A;
9+
}
10+
11+
// This impl is *allowed* unless its assoc. consts are used
12+
impl Tr for () {}
13+
14+
fn main() {
15+
// This triggers the cycle error
16+
assert_eq!(<() as Tr>::A, 0);
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
error[E0391]: cycle detected when const-evaluating + checking `Tr::A`
2+
--> $DIR/defaults-cyclic-fail.rs:5:5
3+
|
4+
LL | const A: u8 = Self::B;
5+
| ^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
note: ...which requires const-evaluating `Tr::A`...
8+
--> $DIR/defaults-cyclic-fail.rs:5:19
9+
|
10+
LL | const A: u8 = Self::B;
11+
| ^^^^^^^
12+
note: ...which requires const-evaluating + checking `Tr::B`...
13+
--> $DIR/defaults-cyclic-fail.rs:8:5
14+
|
15+
LL | const B: u8 = Self::A;
16+
| ^^^^^^^^^^^^^^^^^^^^^^
17+
note: ...which requires const-evaluating `Tr::B`...
18+
--> $DIR/defaults-cyclic-fail.rs:8:19
19+
|
20+
LL | const B: u8 = Self::A;
21+
| ^^^^^^^
22+
= note: ...which again requires const-evaluating + checking `Tr::A`, completing the cycle
23+
note: cycle used when const-evaluating `main`
24+
--> $DIR/defaults-cyclic-fail.rs:16:16
25+
|
26+
LL | assert_eq!(<() as Tr>::A, 0);
27+
| ^^^^^^^^^^^^^
28+
29+
error: aborting due to previous error
30+
31+
For more information about this error, try `rustc --explain E0391`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// run-pass
2+
3+
// Cyclic assoc. const defaults don't error unless *used*
4+
trait Tr {
5+
const A: u8 = Self::B;
6+
const B: u8 = Self::A;
7+
}
8+
9+
// This impl is *allowed* unless its assoc. consts are used, matching the
10+
// behavior without defaults.
11+
impl Tr for () {}
12+
13+
// Overriding either constant breaks the cycle
14+
impl Tr for u8 {
15+
const A: u8 = 42;
16+
}
17+
18+
impl Tr for u16 {
19+
const B: u8 = 0;
20+
}
21+
22+
impl Tr for u32 {
23+
const A: u8 = 100;
24+
const B: u8 = 123;
25+
}
26+
27+
fn main() {
28+
assert_eq!(<u8 as Tr>::A, 42);
29+
assert_eq!(<u8 as Tr>::B, 42);
30+
31+
assert_eq!(<u16 as Tr>::A, 0);
32+
assert_eq!(<u16 as Tr>::B, 0);
33+
34+
assert_eq!(<u32 as Tr>::A, 100);
35+
assert_eq!(<u32 as Tr>::B, 123);
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// build-fail
2+
3+
trait Tr {
4+
const A: u8 = 255;
5+
6+
// This should not be a constant evaluation error (overflow). The value of
7+
// `Self::A` must not be assumed to hold inside the trait.
8+
const B: u8 = Self::A + 1;
9+
//~^ ERROR any use of this value will cause an error
10+
}
11+
12+
// An impl that doesn't override any constant will NOT cause a const eval error
13+
// just because it's defined, but only if the bad constant is used anywhere.
14+
// This matches the behavior without defaults.
15+
impl Tr for () {}
16+
17+
// An impl that overrides either constant with a suitable value will be fine.
18+
impl Tr for u8 {
19+
const A: u8 = 254;
20+
}
21+
22+
impl Tr for u16 {
23+
const B: u8 = 0;
24+
}
25+
26+
impl Tr for u32 {
27+
const A: u8 = 254;
28+
const B: u8 = 0;
29+
}
30+
31+
fn main() {
32+
assert_eq!(<() as Tr>::A, 255);
33+
assert_eq!(<() as Tr>::B, 0); // causes the error above
34+
//~^ ERROR evaluation of constant expression failed
35+
//~| ERROR erroneous constant used
36+
37+
assert_eq!(<u8 as Tr>::A, 254);
38+
assert_eq!(<u8 as Tr>::B, 255);
39+
40+
assert_eq!(<u16 as Tr>::A, 255);
41+
assert_eq!(<u16 as Tr>::B, 0);
42+
43+
assert_eq!(<u32 as Tr>::A, 254);
44+
assert_eq!(<u32 as Tr>::B, 0);
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
error: any use of this value will cause an error
2+
--> $DIR/defaults-not-assumed-fail.rs:8:19
3+
|
4+
LL | const B: u8 = Self::A + 1;
5+
| --------------^^^^^^^^^^^-
6+
| |
7+
| attempt to add with overflow
8+
|
9+
= note: `#[deny(const_err)]` on by default
10+
11+
error[E0080]: evaluation of constant expression failed
12+
--> $DIR/defaults-not-assumed-fail.rs:33:5
13+
|
14+
LL | assert_eq!(<() as Tr>::B, 0); // causes the error above
15+
| ^^^^^^^^^^^-------------^^^^^
16+
| |
17+
| referenced constant has errors
18+
|
19+
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
20+
21+
error: erroneous constant used
22+
--> $DIR/defaults-not-assumed-fail.rs:33:5
23+
|
24+
LL | assert_eq!(<() as Tr>::B, 0); // causes the error above
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ referenced constant has errors
26+
|
27+
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
28+
29+
error: aborting due to 3 previous errors
30+
31+
For more information about this error, try `rustc --explain E0080`.

0 commit comments

Comments
 (0)