-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement RFC 2532 – Associated Type Defaults #61812
Changes from all commits
a323ff2
a549bbd
37686ed
de447eb
1e3c020
d3be26d
c73ee98
f403882
5e9317a
ff5d11e
07ad64f
fead458
485111c
1f61f36
fd28614
c964520
3f03d95
f408794
fbcd136
c8da9ee
24ec364
f94eaea
708f053
a01846f
ec50190
232c1f3
9930e1f
4d4da92
af2931b
c605831
6cc268b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -425,10 +425,109 @@ fn check_trait(tcx: TyCtxt<'_>, item: &hir::Item<'_>) { | |
|
||
for_item(tcx, item).with_fcx(|fcx, _| { | ||
check_where_clauses(tcx, fcx, item.span, trait_def_id, None); | ||
check_associated_type_defaults(fcx, trait_def_id); | ||
|
||
vec![] | ||
}); | ||
} | ||
|
||
/// Checks all associated type defaults of trait `trait_def_id`. | ||
/// | ||
/// Assuming the defaults are used, check that all predicates (bounds on the | ||
/// assoc type and where clauses on the trait) hold. | ||
fn check_associated_type_defaults(fcx: &FnCtxt<'_, '_>, trait_def_id: DefId) { | ||
let tcx = fcx.tcx; | ||
let substs = InternalSubsts::identity_for_item(tcx, trait_def_id); | ||
|
||
// For all assoc. types with defaults, build a map from | ||
// `<Self as Trait<...>>::Assoc` to the default type. | ||
let map = tcx | ||
.associated_items(trait_def_id) | ||
.in_definition_order() | ||
.filter_map(|item| { | ||
if item.kind == ty::AssocKind::Type && item.defaultness.has_value() { | ||
// `<Self as Trait<...>>::Assoc` | ||
let proj = ty::ProjectionTy { substs, item_def_id: item.def_id }; | ||
let default_ty = tcx.type_of(item.def_id); | ||
debug!("assoc. type default mapping: {} -> {}", proj, default_ty); | ||
Some((proj, default_ty)) | ||
} else { | ||
None | ||
} | ||
}) | ||
.collect::<FxHashMap<_, _>>(); | ||
|
||
/// Replaces projections of associated types with their default types. | ||
/// | ||
/// This does a "shallow substitution", meaning that defaults that refer to | ||
/// other defaulted assoc. types will still refer to the projection | ||
/// afterwards, not to the other default. For example: | ||
/// | ||
/// ```compile_fail | ||
/// trait Tr { | ||
/// type A: Clone = Vec<Self::B>; | ||
/// type B = u8; | ||
/// } | ||
/// ``` | ||
/// | ||
/// This will end up replacing the bound `Self::A: Clone` with | ||
/// `Vec<Self::B>: Clone`, not with `Vec<u8>: Clone`. If we did a deep | ||
/// substitution and ended up with the latter, the trait would be accepted. | ||
/// If an `impl` then replaced `B` with something that isn't `Clone`, | ||
/// suddenly the default for `A` is no longer valid. The shallow | ||
/// substitution forces the trait to add a `B: Clone` bound to be accepted, | ||
/// which means that an `impl` can replace any default without breaking | ||
/// others. | ||
/// | ||
/// Note that this isn't needed for soundness: The defaults would still be | ||
/// checked in any impl that doesn't override them. | ||
struct DefaultNormalizer<'tcx> { | ||
tcx: TyCtxt<'tcx>, | ||
map: FxHashMap<ty::ProjectionTy<'tcx>, Ty<'tcx>>, | ||
} | ||
|
||
impl<'tcx> ty::fold::TypeFolder<'tcx> for DefaultNormalizer<'tcx> { | ||
fn tcx<'a>(&'a self) -> TyCtxt<'tcx> { | ||
self.tcx | ||
} | ||
|
||
fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { | ||
match t.kind { | ||
ty::Projection(proj_ty) => { | ||
if let Some(default) = self.map.get(&proj_ty) { | ||
default | ||
} else { | ||
t.super_fold_with(self) | ||
} | ||
} | ||
_ => t.super_fold_with(self), | ||
} | ||
} | ||
} | ||
|
||
// Now take all predicates defined on the trait, replace any mention of | ||
// the assoc. types with their default, and prove them. | ||
// We only consider predicates that directly mention the assoc. type. | ||
let mut norm = DefaultNormalizer { tcx, map }; | ||
let predicates = fcx.tcx.predicates_of(trait_def_id); | ||
for &(orig_pred, span) in predicates.predicates.iter() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a bit subtle, I'll like to think about it a bit. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. First, this check is not necessary for soundness - the trait predicates are always checked in the implementing impl. Might be worth commenting on that. Second, I find it hard to come with an interesting invariant this check guarantees. Might be worth thinking about that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
👍
The options are laid out briefly in https://github.com/rust-lang/rfcs/blob/master/text/2532-associated-type-defaults.md#1-when-do-suitability-of-defaults-need-to-be-proven but the pros and cons aren't really discussed. In #61812 (comment) the rationale for a shallow substitution is discussed and @jonas-schievink makes a compelling point from a semver perspective. In any case, this check is more conservative so it is a good starting point that we can relax later if we want to. |
||
let pred = orig_pred.fold_with(&mut norm); | ||
if pred != orig_pred { | ||
// Mentions one of the defaulted assoc. types | ||
debug!("default suitability check: proving predicate: {} -> {}", orig_pred, pred); | ||
let pred = fcx.normalize_associated_types_in(span, &pred); | ||
let cause = traits::ObligationCause::new( | ||
span, | ||
fcx.body_id, | ||
traits::ItemObligation(trait_def_id), | ||
); | ||
let obligation = traits::Obligation::new(cause, fcx.param_env, pred); | ||
|
||
fcx.register_predicate(obligation); | ||
} | ||
} | ||
} | ||
|
||
fn check_item_fn(tcx: TyCtxt<'_>, item: &hir::Item<'_>) { | ||
for_item(tcx, item).with_fcx(|fcx, tcx| { | ||
let def_id = fcx.tcx.hir().local_def_id(item.hir_id); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// build-fail | ||
|
||
// Cyclic assoc. const defaults don't error unless *used* | ||
trait Tr { | ||
const A: u8 = Self::B; | ||
//~^ ERROR cycle detected when const-evaluating + checking `Tr::A` | ||
|
||
const B: u8 = Self::A; | ||
} | ||
|
||
// This impl is *allowed* unless its assoc. consts are used | ||
impl Tr for () {} | ||
|
||
fn main() { | ||
// This triggers the cycle error | ||
assert_eq!(<() as Tr>::A, 0); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
error[E0391]: cycle detected when const-evaluating + checking `Tr::A` | ||
--> $DIR/defaults-cyclic-fail.rs:5:5 | ||
| | ||
LL | const A: u8 = Self::B; | ||
| ^^^^^^^^^^^^^^^^^^^^^^ | ||
| | ||
note: ...which requires const-evaluating `Tr::A`... | ||
--> $DIR/defaults-cyclic-fail.rs:5:19 | ||
| | ||
LL | const A: u8 = Self::B; | ||
| ^^^^^^^ | ||
note: ...which requires const-evaluating + checking `Tr::B`... | ||
--> $DIR/defaults-cyclic-fail.rs:8:5 | ||
| | ||
LL | const B: u8 = Self::A; | ||
| ^^^^^^^^^^^^^^^^^^^^^^ | ||
note: ...which requires const-evaluating `Tr::B`... | ||
--> $DIR/defaults-cyclic-fail.rs:8:19 | ||
| | ||
LL | const B: u8 = Self::A; | ||
| ^^^^^^^ | ||
= note: ...which again requires const-evaluating + checking `Tr::A`, completing the cycle | ||
note: cycle used when const-evaluating `main` | ||
--> $DIR/defaults-cyclic-fail.rs:16:16 | ||
| | ||
LL | assert_eq!(<() as Tr>::A, 0); | ||
| ^^^^^^^^^^^^^ | ||
|
||
error: aborting due to previous error | ||
|
||
For more information about this error, try `rustc --explain E0391`. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// run-pass | ||
Centril marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Cyclic assoc. const defaults don't error unless *used* | ||
trait Tr { | ||
const A: u8 = Self::B; | ||
const B: u8 = Self::A; | ||
} | ||
|
||
// This impl is *allowed* unless its assoc. consts are used, matching the | ||
// behavior without defaults. | ||
impl Tr for () {} | ||
|
||
// Overriding either constant breaks the cycle | ||
impl Tr for u8 { | ||
const A: u8 = 42; | ||
} | ||
|
||
impl Tr for u16 { | ||
const B: u8 = 0; | ||
} | ||
|
||
impl Tr for u32 { | ||
const A: u8 = 100; | ||
const B: u8 = 123; | ||
} | ||
|
||
fn main() { | ||
assert_eq!(<u8 as Tr>::A, 42); | ||
assert_eq!(<u8 as Tr>::B, 42); | ||
|
||
assert_eq!(<u16 as Tr>::A, 0); | ||
assert_eq!(<u16 as Tr>::B, 0); | ||
|
||
assert_eq!(<u32 as Tr>::A, 100); | ||
assert_eq!(<u32 as Tr>::B, 123); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// build-fail | ||
|
||
trait Tr { | ||
const A: u8 = 255; | ||
|
||
// This should not be a constant evaluation error (overflow). The value of | ||
// `Self::A` must not be assumed to hold inside the trait. | ||
const B: u8 = Self::A + 1; | ||
jonas-schievink marked this conversation as resolved.
Show resolved
Hide resolved
|
||
//~^ ERROR any use of this value will cause an error | ||
} | ||
|
||
// An impl that doesn't override any constant will NOT cause a const eval error | ||
// just because it's defined, but only if the bad constant is used anywhere. | ||
// This matches the behavior without defaults. | ||
impl Tr for () {} | ||
|
||
// An impl that overrides either constant with a suitable value will be fine. | ||
impl Tr for u8 { | ||
const A: u8 = 254; | ||
} | ||
|
||
impl Tr for u16 { | ||
const B: u8 = 0; | ||
} | ||
|
||
impl Tr for u32 { | ||
const A: u8 = 254; | ||
const B: u8 = 0; | ||
} | ||
|
||
fn main() { | ||
assert_eq!(<() as Tr>::A, 255); | ||
assert_eq!(<() as Tr>::B, 0); // causes the error above | ||
//~^ ERROR evaluation of constant expression failed | ||
//~| ERROR erroneous constant used | ||
|
||
assert_eq!(<u8 as Tr>::A, 254); | ||
assert_eq!(<u8 as Tr>::B, 255); | ||
|
||
assert_eq!(<u16 as Tr>::A, 255); | ||
assert_eq!(<u16 as Tr>::B, 0); | ||
|
||
assert_eq!(<u32 as Tr>::A, 254); | ||
assert_eq!(<u32 as Tr>::B, 0); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
error: any use of this value will cause an error | ||
--> $DIR/defaults-not-assumed-fail.rs:8:19 | ||
| | ||
LL | const B: u8 = Self::A + 1; | ||
| --------------^^^^^^^^^^^- | ||
| | | ||
| attempt to add with overflow | ||
| | ||
= note: `#[deny(const_err)]` on by default | ||
|
||
error[E0080]: evaluation of constant expression failed | ||
--> $DIR/defaults-not-assumed-fail.rs:33:5 | ||
| | ||
LL | assert_eq!(<() as Tr>::B, 0); // causes the error above | ||
| ^^^^^^^^^^^-------------^^^^^ | ||
| | | ||
| referenced constant has errors | ||
| | ||
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) | ||
|
||
error: erroneous constant used | ||
--> $DIR/defaults-not-assumed-fail.rs:33:5 | ||
| | ||
LL | assert_eq!(<() as Tr>::B, 0); // causes the error above | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ referenced constant has errors | ||
| | ||
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) | ||
|
||
error: aborting due to 3 previous errors | ||
|
||
For more information about this error, try `rustc --explain E0080`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this file be left alone with a note mentioning that it is no longer emitted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I can do that. Not sure what the proper process is here, but the code examples probably need to be
ignore
d then, if that even works on the error index files. e01ad6a also removed the file directly.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the common action is to have a title "Note: this error code is no longer emitted by the compiler." at the top and ignore the code samples: https://doc.rust-lang.org/error-index.html