Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upImplement `impl Trait` in return type position by anonymization. #35091
Conversation
rust-highfive
assigned
nikomatsakis
Jul 28, 2016
This comment has been minimized.
This comment has been minimized.
|
(rust_highfive has picked a reviewer for you, use r? to override) |
eddyb
force-pushed the
eddyb:impl-trait
branch
from
1a2bd4f
to
8d9a6f7
Jul 28, 2016
This comment has been minimized.
This comment has been minimized.
Will the |
This comment has been minimized.
This comment has been minimized.
|
@Ryman Not unless the type is normalized with |
eddyb
referenced this pull request
Jul 31, 2016
Open
Tracking issue for `impl Trait` (RFC 1522, RFC 1951, RFC 2071) #34511
This comment has been minimized.
This comment has been minimized.
|
|
eddyb
force-pushed the
eddyb:impl-trait
branch
5 times, most recently
from
506c6ff
to
6a3ee28
Aug 1, 2016
nikomatsakis
reviewed
Aug 5, 2016
| @@ -174,6 +174,7 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> { | |||
| ty::TyEnum(..) | // OutlivesNominalType | |||
| ty::TyStruct(..) | // OutlivesNominalType | |||
| ty::TyBox(..) | // OutlivesNominalType (ish) | |||
| ty::TyAnon(..) | // OutlivesNominalType (ish) | |||
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Aug 5, 2016
Contributor
This comment seems wrong. Perhaps "OutlivesProjectionComponents (ish)" -- really this merits a revising of the RFC 1214 rules to include OutlivesAnonType or something, but that's ok.
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Aug 5, 2016
Contributor
Probably this code is doing the right thing, but it's a bit more conservative than it might otherwise be. Really there are choices here just like for projections. Consider for example if you had trait Foo<'a,'b>: 'a, then you could in theory derive that impl Foo<'a,'b>: 'a even if 'b: 'a doesn't necessarily hold. Something similar could occur with trait Foo<'a, T>: 'a, where impl Foo<'a, &'b T>: 'a might hold even if 'b: 'a does not hold.
It seems in a way like an odd rule, since the type name that the type system gives includes lifetimes (like 'b) that are not necessarily in scope, but the same thing can occur with projections -- the point is that, after monomorphization, the normalized form of that type is known to outlive 'a, even if all components pre-normalization do not.
/me has that itchy feeling of wanting to revisit a richer, formalized version of this, to try and have a good reference. But nothing here makes me too scared yet. =)
nikomatsakis
reviewed
Aug 5, 2016
| @@ -907,6 +907,27 @@ impl<'tcx> fmt::Display for ty::TypeVariants<'tcx> { | |||
| } | |||
| TyTrait(ref data) => write!(f, "{}", data), | |||
| ty::TyProjection(ref data) => write!(f, "{}", data), | |||
| ty::TyAnon(def_id, substs) => { | |||
| ty::tls::with(|tcx| { | |||
| // Grab the "TraitA + TraitB" from `impl TraitA + TraitB`, | |||
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Aug 5, 2016
Contributor
As an aside, I suspect that the grammar ought to be impl (TraitA + TraitB) -- at least, the precedence of impl TraitA + TraitB looks surprising to me. Though maybe I could learn to like it.
I'm imagining things like F: Fn() -> impl Trait + Send <-- how should this be parsed?
This comment has been minimized.
This comment has been minimized.
eddyb
Aug 5, 2016
Author
Member
The +Trait is eagerly parsed, so F: Fn() -> (impl Trait + Send). However, impl Trait in a bound seems unlikely to me, and definitely not supported at this moment.
The precedence is this way for convenience - unlike trait objects, there's a prefix (impl), which solves all ambiguity save for what you just mentioned.
I suppose -> impl Fn() -> impl Trait + Send parses, as -> impl Fn() -> (impl Trait + Send).
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Aug 7, 2016
Contributor
The precedence is this way for convenience - unlike trait objects, there's a prefix (impl), which solves all ambiguity save for what you just mentioned.
I don't think there was an more-or-less "ambiguity" before, it's just that we had a weird set of precedence rules. I think the reason I find the precedence of impl relative to + surprising that, in general, we typically give keyword operators the same precedence as other unary operators and it usually higher than binary operators. Hence box 3 + 2 parses as (box 3) + 2. (Admittedly, though, I have complained about this in the past.)
In any case, I continue to find it inconsistent that the Send is associated with the trait in impl Trait + Send but with the type parameter F in F: Fn() Foo -> Trait + Send. (Even if I agree with your argument that one would not necessarily combine them... yet...)
Seems like maybe we should raise it on the tracking issue?
nikomatsakis
reviewed
Aug 5, 2016
| pub struct DeferredObligation<'tcx> { | ||
| pub predicate: ty::PolyTraitPredicate<'tcx>, | ||
| pub cause: ObligationCause<'tcx> | ||
| } |
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Aug 5, 2016
Contributor
I haven't read deeply here but I'm a bit dubious about the deferred obligations strategy. Even if we implement things this way, we may want to consider enforcing some kind of inference DAG on uses of impl Trait to give us room to consider later (I guess stabilization makes this less of a concern, actually, never mind).
Anyway, I'm thinking about situations like this one:
trait Foo {
type X;
}
impl<T> Foo for T {
default type X = ();
}
impl<T: Send> Foo for T {
type X = u32;
}Now if you have an obligation like <Y as Foo>::X = u32, where Y is an anonymous type, we have to figure out how to resolve it, and to do that we kind of need to know whether Y: Send.
In principle, we could probably defer both obligations (and maybe you do, as I said, I didn't finish reading this commit yet) but I do wonder if there will be other situations (method dispatch?) where it is very difficult to suspend.
This comment has been minimized.
This comment has been minimized.
eddyb
Aug 5, 2016
Author
Member
AFAIK, even <() as Foo>::X = u32 won't type-check at this moment, so such speculations don't even come into play at this moment - although something like the lattice rule may add them.
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Aug 7, 2016
Contributor
AFAIK, even <() as Foo>::X = u32 won't type-check at this moment
Actually, it does: https://is.gd/36phAF
What does not work is something like normalizing <Rc<()> as Foo>::X to (), because we wanted to preserve the ability to specialize Foo for Rc in the future.
This comment has been minimized.
This comment has been minimized.
eddyb
Aug 7, 2016
Author
Member
Ah, right, I got it mixed up. Still, we don't pick the default, we just don't normalize, so doing the same for impl Trait would be just conservative, not wrong.
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Aug 10, 2016
Contributor
doing the same for impl Trait would be just conservative, not wrong.
I think what you mean is: it won't be unsound? As in, we won't normalize to the wrong thing? If so, I agree.
eddyb
force-pushed the
eddyb:impl-trait
branch
from
6a3ee28
to
6b420b7
Aug 6, 2016
This comment has been minimized.
This comment has been minimized.
|
|
eddyb
force-pushed the
eddyb:impl-trait
branch
from
6b420b7
to
bc131a8
Aug 6, 2016
This comment has been minimized.
This comment has been minimized.
|
I don't like the "capture all lifetimes in the |
This comment has been minimized.
This comment has been minimized.
|
|
nikomatsakis
reviewed
Aug 10, 2016
| /// scope, that anonymized types will close over. This property is | ||
| /// controlled by the region scope because it's fine-grained enough | ||
| /// to allow restriction of anonymized types to the syntactical extent | ||
| /// of a function's return type. |
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Aug 10, 2016
Contributor
Nit: I can't make heads or tails of this comment :) An example would be great
This comment has been minimized.
This comment has been minimized.
eddyb
Aug 10, 2016
•
Author
Member
Are you talking about allowing impl Trait, or the "generics in scope"? The latter would be easy to provide an example from (<'a, T> for fn foo<'a, T>(...) -> impl Trait), but controlling whether impl Trait is legal in a certain position is harder to explain.
For it to make more sense, rscope would have to become one of several properties that get propagated recursively.
At the same time, the ItemContext in typeck::collect may be a better choice now for holding the "generics in scope", as it's the only AstConv implementation which allows impl Trait, but it's still not that uniform (only the return type of a function or inherent impl method supports it, not just any type in the signature of those items).
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Aug 10, 2016
Contributor
@eddyb after I read below I figured out what the comment meant. I think something like this would have helped me:
If this scope allows anonymized types, return the generics in scope that anonymized types will close over. For example, if you have a function like:
fn foo<'a, T>() -> impl Trait { ... }
then, for the scope that is used when handling the return type, this function would return 'a and T. This property is controlled by the region scope because it's fine-grained enough to allow restriction of anonymized types to the syntactical extentof a function's return type.
This comment has been minimized.
This comment has been minimized.
|
I went through the RFC and made a minimal list of test cases I think would like to see:
|
This comment has been minimized.
This comment has been minimized.
|
Er, I posted this comment earlier, but in the wrong place: @eddyb ok so as discussed on IRC, I am in favor of landing this PR, on the assumption that we will address some of the thornier issues later. One thing I think is missing is negative tests. I'd like to see at least some tests that if you try to return incompatible types that will result in an error. Probably some (more?) tests targeting "Auto trait" inference -- and the limits, e.g. around specialization -- are warranted as well. One thing I have done in the past -- but didn't do yet for this PR -- is to make a list, based on the RFC, not the code, of things that might need to be tested, and then compare the code against that list. I'm happy to go through and do it, or you can and post it somewhere. :) (In case it wasn't clear, the code itself seems good to me! Nice work as usual.) |
eddyb
force-pushed the
eddyb:impl-trait
branch
3 times, most recently
from
c32f829
to
c0b7f97
Aug 10, 2016
This comment has been minimized.
This comment has been minimized.
|
@bors r+ |
This comment has been minimized.
This comment has been minimized.
|
|
eddyb
referenced this pull request
Aug 11, 2016
Merged
Remove the ParamSpace separation from formal and actual generics in rustc. #35605
This comment has been minimized.
This comment has been minimized.
|
|
eddyb
added some commits
Jun 30, 2016
eddyb
force-pushed the
eddyb:impl-trait
branch
from
c0b7f97
to
23f0494
Aug 12, 2016
This comment has been minimized.
This comment has been minimized.
|
@bors r=nikomatsakis |
This comment has been minimized.
This comment has been minimized.
|
|
eddyb commentedJul 28, 2016
•
edited
This is the first step towards implementing
impl Trait(cc #34511).impl Traittypes are only allowed in function and inherent method return types, and capture all named lifetime and type parameters, being invariant over them.No lifetimes that are not explicitly named lifetime parameters are allowed to escape from the function body.
The exposed traits are only those listed explicitly, i.e.
FooandCloneinimpl Foo + Clone, with the exception of "auto traits" (likeSendorSync) which "leak" the actual contents.The implementation strategy is anonymization, i.e.:
$0and$1are resolved (toiter::Map<vec::Iter<T>, closure>and the closure, respectively) and assigned toAandB, after checking the body offoo.AandBare never resolved for user-facing type equality (typeck), but always for the low-level representation and specialization (trans).The "auto traits" exception is implemented by collecting bounds like
impl Trait: Sendthat have failed for the obscureimpl Traittype (i.e.AorBabove), pretending they succeeded within the function and trying them again after type-checking the whole crate, by replacingimpl Traitwith the real type.While passing around values which have explicit lifetime parameters (of the function with
-> impl Trait) in their type should work, regionck appears to assign inference variables in way too many cases, and never properly resolving them to either explicit lifetime parameters, or'static.We might not be able to handle lifetime parameters in
impl Traitwithout changes to lifetime inference, but type parameters can have arbitrary lifetimes in them from the caller, so most type-generic usecases (or not generic at all) should not run into this problem.cc @rust-lang/lang