-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Conversation
This comment has been minimized.
This comment has been minimized.
src/librustc/traits/project.rs
Outdated
@@ -1104,27 +1104,41 @@ fn assemble_candidates_from_impls<'cx, 'gcx, 'tcx>( | |||
// an error when we confirm the candidate | |||
// (which will ultimately lead to `normalize_to_error` | |||
// being invoked). | |||
node_item.item.defaultness.has_value() | |||
false |
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.
This looks very wrong to me, since it sounds like it'd allow assuming defaults inside the trait. For some reason that doesn't happen (the tests pass).
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.
Inside the trait, you won't be having a VtableImpl
(which indicates a specific impl was selected), but a VtableParam
(because of an "implicit where-clause").
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.
However, this does not correctly handle the case where an associated type default is overridden in a derived impl:
#![feature(associated_type_defaults, specialization)]
trait Foo {
type Xyz = u32;
fn foobar() -> Self::Xyz;
}
// 1)
impl<T> Foo for T {
fn foobar() -> Self::Xyz { 0u32 }
}
// 2)
impl Foo for u32 {
type Xyz = &'static u32;
}
fn main() {
u32::foobar();
}
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.
However, according to the spec, the problem is somewhere else, because of
To permit this is not a problem because
Foo for Vec<T>
is not further specializable sinceBar
in the implementation has not been marked as default.
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.
However, according to the spec, the problem is somewhere else, because of
So the RFC says:
If an implementation does make the associated type available for further specialization, then other definitions in the implementation may not assume the given underlying specified type of the associated type and may only assume that it is
Self::TheAssociatedType
.
which the snippet above clearly violates... but the more interesting question is "how do we ban it?"...
...For the sake of separate compilation (i.e. imagine 2) is in a different crate), I think we need to say that 1) is legitimate but 2) is not. Why? Because it overrides type Xyz
which should not be up for specialization since it was not marked as default
in 1) (if it did then fn foobar
could not assume type Xyz
's definition). Moreover fn foobar
here assumes type Xyz
's definition.
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. If we go by the RFC, the default associated type type Xyz = u32;
should only be specializable by the first impl that matches, not by any specializing impls (this can be checked at 2)
time because of the orphan rules), so 2)
should give an E0520 (Xyz
specializes an item from a parent impl
, but that item is not marked default
).
I'll note that this contradicts the way associated fns work:
#![feature(specialization)]
trait Foo {
fn foobar() -> bool { false }
}
impl<T> Foo for T {
}
impl Foo for u32 {
fn foobar() -> bool { true }
}
fn main() {
println!("{:?}", u32::foobar()); // prints `true`
}
also, if we do that, we have to decide whether associated consts should work like associated types or like associated fns.
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.
also, if we do that, we have to decide whether associated consts should work like associated types or like associated fns.
The RFC applies to associated fns as well so how they work will need to be changed as well but not necessarily in this PR. The RFC says:
This applies generally to any item inside a trait. [...]
which includes fn
s and const
s also.
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.
This is an open question in the design of specialization (#31844):
Should default trait items be considered
default
(i.e. specializable)?
Specialization is unstable, so I don't think there's a real reason not to forbid respecialization of associated types and associated fns together. However, I might want to do this in an earlier PR, and assess crater impact.
That change that has a fair bit of breaking impact, so I'm for implementing it fairly quickly. However, I'll still be OK with opening an issue for the specialization rules change and linking it from the OP of #29661.
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.
If we continue on this question: can you have default
provided trait items that can be re-overriden?
#![feature(specialization)]
trait Foo {
default fn foobar() -> bool { false }
}
impl<T> Foo for T {
}
impl Foo for u32 {
fn foobar() -> bool { true } // OK
}
fn main() {
println!("{:?}", u32::foobar()); // prints `true`
}
I think either option would be worth writing clearly in a "changes to specialization" section in RFC 2532, along with examples.
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.
This is an open question in the design of specialization (#31844):
Should default trait items be considered
default
(i.e. specializable)?
This is a bit of an orthogonal question; the main question is instead whether re-overriding is allowed as you point out later. I think it flows from the reference of the RFC and the spirit of it that re-overriding is banned. Instead, the idea is that 1)
in both of your snippets "lock in" what they do not explicitly allow for further specialization (with default $item
in impls). Whether trait
items must be prefixed with default
to be specializable is not a particularly interesting question from this perspective and especially not from the POV of specialization groups as per in the "future possibilities" section of the RFC.
I think either option would be worth writing clearly in a "changes to specialization" section in RFC 2532, along with examples.
It's a good idea to write down interesting points and decisions somewhere be it in the RFC or in the tracking issue. I think we can link to this discussion in a bullet point in the tracking issue description and then consider amending the RFC with new information at a later point (when things have slowed down and the pieces are in place). These notes will be useful when I (or someone else, but most likely not...) writes the stabilization report.
let _: <Pub as PubTr>::AssocTy; | ||
//~^ ERROR type `priv_parent_substs::Priv` is private | ||
//~| ERROR type `priv_parent_substs::Priv` is private | ||
let _: <Pub as PubTr>::AssocTy; // TODO no longer an error?! |
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.
This also looks extremely wrong to me since the only difference to the code below is the omission of <_>
, but I can't imagine how this patch caused this, so it might be preexisting?
@@ -184,19 +184,19 @@ error[E0223]: ambiguous associated type | |||
--> $DIR/ufcs-partially-resolved.rs:36:12 | |||
| | |||
LL | let _: <u8 as Tr>::Y::NN; | |||
| ^^^^^^^^^^^^^^^^^ help: use fully-qualified syntax: `<<u8 as Tr>::Y as Trait>::NN` | |||
| ^^^^^^^^^^^^^^^^^ help: use fully-qualified syntax: `<u16 as Trait>::NN` |
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.
These suggestions are now worse due to the projection succeeding
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| ^^ expected associated type, found u8 | ||
| | ||
= note: expected type `fn() -> <A<T> as Tr>::Ty` | ||
found type `fn() -> u8` |
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 wonder if it would be feasible in a not-too-hacky a manner to tell the user that defaults are the cause of the failure (both when using specialization and not). Otherwise the user might be reasonably confused as to why they don't get to assume the default since they are unlikely to be aware of the type theoretical reasons.
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.
This could also be left as future work; if you prefer that, please leave a checkbox in the tracking issue :)
Also cc @estebank
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.
Yeah I'll do this. This is challenging enough as is ;)
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.
For that future work... One thing you might try in the unhappy path is to check whether there is a default that would have allowed it to succeed if not for our rules.
src/librustc_typeck/collect.rs
Outdated
|
||
if let Some(default_ty) = default { | ||
let default_ty = AstConv::ast_ty_to_ty(&ItemCtxt::new(tcx, def_id), default_ty); | ||
let preds = AstConv::compute_bounds( |
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.
Can you add a test to also make sure that bounds are respected when not written directly on the associated type itself?
trait Foo<T>
where
// Same bound as `type Bar: Clone` but written indirectly:
<Self as Foo<T>>::Bar: Clone,
{
type Bar = Vec<T>;
}
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.
Another case to consider is when you have multiple defaults:
trait Foo<T>
where
<Self as Foo<T>>::Bar: Clone,
{
type Bar = Vec<Self::Baz>;
type Baz = T;
}
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.
Yeah this doesn't work at all right now, this check needs to be implemented differently.
This gets particularly tricky when you have a where-clause like where Self::Ty: Con<Self>
on the trait. You can't really know if this holds for the defaulted type because Self
isn't yet known.
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.
In general, when faced with a situation "you can't really know yet" this should be treated as if it wasn't so. In other words, we want constructive proof that something holds. So in a situation with where Self::Ty: Con<Self>
, because Self
isn't yet known, the answer is to treat it as a type variable (which it is...) and then error.
I think the check should be something along the lines of substituting the associated types for the defaults and then try to prove the normalized where
clauses. It seems to me that rejecting where Self::Ty: Con<Self>,
should just "fall out".
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.
Just pushed a better implementation of the check and a comprehensive test
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.
Looks great!
Can you also add my last snippet to that so we check for scenarios involving more than one associated type?
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.
Done. Right now the behavior is to do a shallow replacement of associated types with their defaults, so <Self as Foo<T>>::Bar: Clone
is turned into Vec<Self::Baz>
, not Vec<T>
. I guess this needs to be fixed.
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.
Ah, I think the shallow substitution is actually beneficial here, since it forces the trait to work with any combination of defaults and non-defaults: If we did the deep substitution and had a T: Clone
bound, we'd prove that Vec<T>: Clone
and the trait would be accepted. If a user then implements it and overrides only Baz
, suddenly Vec<Self::Baz>: Clone
might no longer hold, and the user would be forced to also override Bar
. With the shallow substitution, the trait would have to add a Self::Baz: Clone
bound instead/in addition to the T: Clone
bound, which means that an implementation could replace either assoc. type without making others not work anymore.
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.
Interesting; it seems to me a shallow substitution is strictly more conservative than the deeper one, right? So we can relax it later if we want.
I think it would be good to demonstrate the difference in a special test file and then also leave a description of the distinction (with examples) on the tracking issue so that these interesting aspects aren't lost in comments here.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
I read over the code here -- but then I was looking back over the list of tests that I assembled earlier and I felt like I didn't see (e.g.) tests that were looking at the interaction of |
I believe that part is not included per PR OP:
|
OK, great, I thought that might be the case, given the code diffs I saw. |
📌 Commit 6cc268b has been approved by |
@jonas-schievink sorry for taking so unforgiveably long here ❤️ |
I'm so excited to finally see this land! Thanks so much @jonas-schievink for your hard work and thanks @nikomatsakis for diligently assembling all those test cases. |
☀️ Test successful - checks-azure |
📣 Toolstate changed by #61812! Tested on commit 3a0d106. 💔 clippy-driver on windows: test-pass → test-fail (cc @mcarton @oli-obk @Manishearth @flip1995 @yaahc @phansch @llogiq). |
Tested on commit rust-lang/rust@3a0d106. Direct link to PR: <rust-lang/rust#61812> 💔 clippy-driver on windows: test-pass → test-fail (cc @mcarton @oli-obk @Manishearth @flip1995 @yaahc @phansch @llogiq). 💔 clippy-driver on linux: test-pass → test-fail (cc @mcarton @oli-obk @Manishearth @flip1995 @yaahc @phansch @llogiq).
Rustup to rust-lang/rust#61812 changelog: none
Update cargo, clippy Closes #69601 ## cargo 16 commits in e57bd02999c9f40d52116e0beca7d1dccb0643de..bda50510d1daf6e9c53ad6ccf603da6e0fa8103f 2020-02-21 20:20:10 +0000 to 2020-03-02 18:05:34 +0000 - Fix rare failure in collision_export test. (rust-lang/cargo#7956) - Ignore broken Cargo.toml in git sources (rust-lang/cargo#7947) - Add more fingerprint mtime debug logging. (rust-lang/cargo#7952) - Fix plugin tests for latest nightly. (rust-lang/cargo#7955) - Simplified usage code of SipHasher (rust-lang/cargo#7945) - Add a special case for git config discovery inside tests (rust-lang/cargo#7944) - Fixes issue rust-lang/cargo#7543 (rust-lang/cargo#7946) - Filter out cfgs which should not be used during build (rust-lang/cargo#7943) - Provide extra context on a query failure. (rust-lang/cargo#7934) - Try to clarify `cargo metadata`'s relationship with the workspace. (rust-lang/cargo#7927) - Update libgit2 dependency (rust-lang/cargo#7939) - Fix link in comment (rust-lang/cargo#7936) - Enable `cargo doc --open` tests on macos. (rust-lang/cargo#7932) - build-std: remove sysroot probe (rust-lang/cargo#7931) - Try to clarify how feature flags work on the "current" package. (rust-lang/cargo#7928) - Add extra details in the new feature resolver doc comment. (rust-lang/cargo#7918) ## clippy 6 commits in fc5d0cc..8b7f7e6 2020-02-24 05:58:17 +0000 to 2020-03-02 20:00:31 +0000 - Rustup to #69469 (rust-lang/rust-clippy#5254) - Some rustups (rust-lang/rust-clippy#5247) - Update git2 to 0.12 (rust-lang/rust-clippy#5232) - Rustup to #61812 (rust-lang/rust-clippy#5231) - Add lint to improve floating-point expressions (rust-lang/rust-clippy#4897) - Do not run deploy action on other repos (rust-lang/rust-clippy#5222)
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:
cc #29661
Fixes #53907
Fixes #54182
Fixes #62211
Fixes #41868
Fixes #63593
Fixes #47385
Fixes #43924
Fixes #32350
Fixes #26681
Fixes #67187