Skip to content
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

Closed

Conversation

leoyvens
Copy link
Contributor

@leoyvens leoyvens commented Nov 23, 2017

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 that T == Vec<U> then the default for T will be ignored while the default of U 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:

  1. Implement the heuristic for avoiding rolling back successes.
  2. Not working for defaults in type aliases.
  3. Fix interaction with specialization described below.
  4. Decide if the behaviour in 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.

@rust-highfive
Copy link
Collaborator

r? @petrochenkov

(rust_highfive has picked a reviewer for you, use r? to override)

@kennytm kennytm added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Nov 23, 2017
@eddyb
Copy link
Member

eddyb commented Nov 23, 2017

r? @nikomatsakis

@leoyvens leoyvens force-pushed the default-typaram-experimentation branch from 2e2a5ce to 5686890 Compare November 23, 2017 16:32
@leoyvens
Copy link
Contributor Author

@kennytm tidy thinks some .rs test files are binary files, what do?

@kennytm
Copy link
Member

kennytm commented Nov 23, 2017

@leodasvacas Run chmod a-x path/of/those/files.rs and then commit them again.

@bors
Copy link
Contributor

bors commented Nov 26, 2017

☔ The latest upstream changes (presumably #46203) made this pull request unmergeable. Please resolve the merge conflicts.

@leoyvens leoyvens force-pushed the default-typaram-experimentation branch 5 times, most recently from 8c685b6 to 3cf03eb Compare November 27, 2017 18:52
jroesch and others added 12 commits November 27, 2017 18:29
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.
@leoyvens
Copy link
Contributor Author

leoyvens commented Nov 28, 2017

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:

  1. Pick an impl and apply its default. The most specialized impl? The most general impl? What about lattice specialization, all of the most general candidate impls must agree?
  2. Apply the defaults of all candidate impls. Have specialized impls always inherit the default from the parent impls.

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.

@nikomatsakis
Copy link
Contributor

nikomatsakis commented Nov 29, 2017

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.
@leoyvens leoyvens force-pushed the default-typaram-experimentation branch from 34027c7 to 4753319 Compare November 29, 2017 19:32
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.
@leoyvens
Copy link
Contributor Author

leoyvens commented Dec 4, 2017

@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.

@shepmaster shepmaster added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Dec 9, 2017
@shepmaster
Copy link
Member

Ping from triage! It's been over 7 days since we heard from reviewer @nikomatsakis. Please assign a new reviewer @rust-lang/compiler.

@kennytm
Copy link
Member

kennytm commented Dec 20, 2017

Ping from triage @nikomatsakis! I see that you commented in #46714 (comment). Is there any update from that?

@leoyvens
Copy link
Contributor Author

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!

@bors
Copy link
Contributor

bors commented Dec 21, 2017

☔ The latest upstream changes (presumably #46862) made this pull request unmergeable. Please resolve the merge conflicts.

@alexcrichton alexcrichton added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jan 4, 2018
@alexcrichton
Copy link
Member

@leodasvacas looks like there may be some merge conflicts now?

@leoyvens
Copy link
Contributor Author

leoyvens commented Jan 4, 2018

@alexcrichton Thanks for the triage! This is S-blocked at the moment as Niko and me still need to schedule a chat.

@kennytm kennytm added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Jan 4, 2018
@alexcrichton alexcrichton added S-blocked Status: Blocked on something else such as an RFC or other implementation work. S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jan 4, 2018
@carols10cents carols10cents removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jan 9, 2018
@leoyvens
Copy link
Contributor Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-blocked Status: Blocked on something else such as an RFC or other implementation work. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.