-
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
Default Type Parameter Fallback: Revival #46206
Default Type Parameter Fallback: Revival #46206
Conversation
(rust_highfive has picked a reviewer for you, use r? to override) |
2e2a5ce
to
5686890
Compare
@kennytm tidy thinks some |
@leodasvacas Run |
b45679e
to
d9edb46
Compare
☔ The latest upstream changes (presumably #46203) made this pull request unmergeable. Please resolve the merge conflicts. |
8c685b6
to
3cf03eb
Compare
The previous implementation was not very clear, this commit attempts to clarify and clean up the code that applies all defaults.
Thanks to @eddyb for bringing this bug to my attention.
infer/mod.rs, infer/type_variable.rs and error.rs were manually rebased due to being moved from librustc/middle/infer/ to librustc/infer
Had screwed up the rebase of `check/mod.rs`. The error handling code is commented out because I don't know how to rebase it.
I've tested the interaction with specialization, and the following interaction is concerning: #![feature(default_type_parameter_fallback)]
#![feature(specialization)]
use std::fmt::Debug;
trait B { fn b(&self) -> Self; }
impl<T=String> B for Option<T> where T: Default
{
default fn b(&self) -> Option<T> {
Some(T::default())
}
}
// When there specialized but generic impls, their defaults
// are ignored no matter what they are.
// This code does not compile because `x` in main fails to infer.
// However if we commented out this impl, `x` would be inferred to `String`.
impl<T=String> B for Option<T> where T: Default + Debug
{
fn b(&self) -> Option<T> { Some(T::default()) }
}
fn main() {
let x = None;
let y = x.b();
} This is bad because even though it's normal for new trait impls to break inference, in this case we are completely ignoring the defaults, which means fixing this to consider the defaults is not backwards compatible, so it goes in the "todo before stabilization" list. Options:
I'm not familiar with trait selection so I don't know what is feasible here. Edit: I think the solution would be to use the default of the most general candidate impl, or impls in the case of lattices. |
Sorry for not commenting. It was thanksgiving, and now I've been traveling. I haven't had a chance to look or think deeply about this proposal, but I feel some level of skepticism. This isn't specific to the PR in particular. Rather, I've become skeptical on the idea of defaulted type parameters in general, and also because I feel like we've been approaching their design in a bit of a ... haphazard way. I'd like to see an RFC (or at least "proto RFC") that clearly lays out the motivation for such changes, enumerating the use cases we are trying to fix and those we are not, and also lays out the possible dangers for "incoherence" and describes how they are addressed. That said, I don't want to have pure stop energy here, I also like the idea (in general) of iterating in tree, so I'll try to give this PR the time it deserves in a day or two! |
I don't know if it's what we want but it's how it works now. If we want to keep it then we should forbid writing the default in the impl.
34027c7
to
4753319
Compare
Makes associated types in defaults less verbose.
If you propagate the origin, you propagate the default. Fixes fallback for field access. Also small refactoring in combine.
…context" Try fallback right in `structurally_resolve_type_or_else` right before we hit "the type of this value must be known in this context". This gets casts working, and probably other things not tested for. Also remove some dead code.
It seems we have pretty much the same logic in three different places, so it was fixed in three different places. Took the opportunity to leave FIXMEs regarding elision of defaulted params.
@nikomatsakis Thanks for the comment! I'll write that pre-RFC, but it may take some time. Also it's easier to discuss an RFC with an implementation available on nightly. It's seems better to focus on having a good story for adding a new type parameter with a default rather than on adding defaults to existing parameters. This makes the feature simpler and more powerful, and we don't really lose any use cases. I'll flesh out the full design in an RFC. This PR remains entirely relevant anyways, it's a small adjustment to the algorithm. |
Ping from triage! It's been over 7 days since we heard from reviewer @nikomatsakis. Please assign a new reviewer @rust-lang/compiler. |
Ping from triage @nikomatsakis! I see that you commented in #46714 (comment). Is there any update from that? |
It's not clear whether this needs an RFC, but some structured rationale would help this PR. Niko and me will schedule a chat to see how to move this forward. Thanks triage guys for the pings! |
☔ The latest upstream changes (presumably #46862) made this pull request unmergeable. Please resolve the merge conflicts. |
@leodasvacas looks like there may be some merge conflicts now? |
@alexcrichton Thanks for the triage! This is S-blocked at the moment as Niko and me still need to schedule a chat. |
After meeting with Niko we've decided it's best not to land anything until we get a new design through as an RFC. So closing until that happens. |
See #27336 for background. This is about using type parameter defaults to inform inference. This was once implemented in #26870. It turns out there were issues with the design, the implementation was finally removed in #40570. But this feature is awesome and we should not let it die.
As discussed in the tracking issue, we want adding defaults to type parameters to be backwards compatible, but the original design did not take this into account. @eddyb suggests that we future-proof this by not applying a fallback where a conflict could possibly occur. That would make defaults on fns and impls pretty much useless. So I suggested that we invert the situation and give priority to the defaults on fns and impls, taking care to be future-proof. For details of how this is done see the comments and implementation in
fn apply_user_type_parameter_fallback
. The tests show what works and what doesn't under this design. Numeric and diverging fallbacks take precedence over user supplied fallbacks.Does this need an RFC? Does the RFC block accepting the PR? Should it be an ammendment to RFC 213?
A gotcha:
As commented in the test
trait_impl.rs
, if a type var is partially inferred then it's default will not be considered. For example if we know thatT == Vec<U>
then the default forT
will be ignored while the default ofU
may apply. This avoids conflicting defaults and is consistent with not applying user fallbacks for numerics, a numeric var can be seen as having been partially inferred.Error messages:
If fallback fails, we preserve the error message that would occur if no fallback was present, so "type annotations needed" or "the type of this value must be known in this context". A custom error message would be complicated to craft and would not be useful as most of the time the only thing the user can do is annotate a type.
To preserve error messages I need to rollback any constraints made when trying to solve defaults. Currently everything is rolled back on any failure, throwing away any successes along with it. This is bad but we can easily implement a heuristic that solves the easy cases and bundles only the hard cases together, making pathological cases rare. So I'm satisfied with this strategy.
Todo:
trait_def_rules_over_impl.rs
is expected. If so, forbid writing defaults in implementations of trait methods.The first commits are a rebase an old branch by @jroesch and @nikomatsakis. I ended up not salvaging much, but some refactorings were useful.
Disclaimer: I'm learning as I go, I'm not really familiar with any of this stuff.