-
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
impl Trait Lifetime Handling #45701
impl Trait Lifetime Handling #45701
Conversation
r? @pnkfelix (rust_highfive has picked a reviewer for you, use r? to override) |
Well, I guess I did write some of the core commits. Maybe I shouldn't review... |
r? @eddyb |
b3d0405
to
9ea3b2f
Compare
review ping @eddyb - pinging you in IRC too! |
7667bbc
to
0d12267
Compare
Ping @eddyb |
src/librustc/hir/mod.rs
Outdated
@@ -1450,8 +1456,7 @@ pub enum Ty_ { | |||
/// where `Bound` is a trait or a lifetime. | |||
TyTraitObject(HirVec<PolyTraitRef>, Lifetime), | |||
/// An `impl Bound1 + Bound2 + Bound3` type | |||
/// where `Bound` is a trait or a lifetime. | |||
TyImplTrait(TyParamBounds), | |||
TyImplTrait(ExistTy, HirVec<Lifetime>), |
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.
Why are the lifetimes separate? Shouldn't they go in the TyParamBounds
?
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.
Nope. These are the lifetimes that are in the reference to the abstract type, not its definition. That is, consider the desugaring of fn foo<'a>() -> impl Trait<'a>
. You get:
abstract type Foo<'b>: Trait<'b>;
// ^^ this `'b` is what appears in the `ExistTy`
// ^^ and this is the `TyParamBounds`, note that
// the lifetime here will reference the lifetime
// `'b` defined in the `abstract type`
fn foo<'a>() -> Foo<'a> { .. }
// ^^ this `'a` is the one that appears in this `HirVec<Lifetime>
(Clearly, we need some more comments here.)
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 lifetimes are resolved outside of the generics, so they pick up whatever is in the environment-- see here. Later, those lifetimes are "applied" to the generics here.
In comparison, the lifetimes in the TyParamBounds
are resolved to the Generics
(here).
You can think about a TyImplTrait
as an abstract type Foo<'a, 'b>: MyTrait + 'a + 'b;
paired with the lifetime parameters applied to it, 'a
and 'b
. You can also think about this list as containing all of the type parameters, but they don't have to be separately applied since they're just picked up from the parent generics.
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.
r=me with a comment for this added.
// Exclude '_, 'static, and elided lifetimes (there should be no elided lifetimes) | ||
if let hir::LifetimeName::Name(lifetime_name) = lifetime.name { | ||
if !self.currently_bound_lifetimes.contains(&lifetime_name) && | ||
!self.already_defined_lifetimes.contains(&lifetime_name) |
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.
At this point I think that we should move named lifetime resolution (mapping a NodeId
of an use to a DefId
of a definition without computing the internal representation or doing elision and most of the checks) to rustc_resolve
. But you probably don't want (even) more delays, so I think we can have this for now.
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.
Definitely a separate PR. It does seem like it would be a more natural fit with how other name resolution works, though it will make stuff like what we are doing here more annoying. The "HIR-rewriting" works much more smoothly since name resolution hasn't happened yet.
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.
How so? All of this seems harder to do with ad-hoc name resolution. But sure, we can leave it for later.
I've added some comments and some more (passing) tests. However, I also came up with a test that currently ICEs: fn closure_hrtb() -> impl for<'a> Fn(&'a u32)
-> impl Debug + 'a {
|x| x
} The failure is this assertion. |
So, this is a bit trickier than I initially thought. I actually think we should (for now, at least) be forbidding this sort of construction. It effectively desugars into:
However, we don't currently have a very clean way of talking about a type variable instantiated "inside" binders. That is, the type variable we create to infer what That said, it does seem like this sort of thing might be desired in the wild. |
How would you suggest banning this? Issue an error? "cannot use |
☔ The latest upstream changes (presumably #45918) made this pull request unmergeable. Please resolve the merge conflicts. |
krate.trait_items.contains_key(&parent_trait_id)) | ||
{ | ||
span_err!(self.sess, lifetime.span, E0657, | ||
"`impl Trait` can only capture lifetimes bound at the fn level"); |
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.
seems good. One nit with the message is that we could capture e.g.
impl<'a> Foo<'a> {
fn bar(&self) -> impl Trait<'a> { .. }
}
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.
"fn or impl level"?
00e6136
to
bd0f2c7
Compare
After this change, impl Trait existentials are desugared to a new `abstract type` definition paired with a set of lifetimes to apply. In-scope generics are included as parents of the `abstract type` generics. Parent regions are replaced with static, and parent regions referenced in the `impl Trait` type are duplicated at the end of the `abstract type`'s generics.
bd0f2c7
to
bc4810d
Compare
Failure looks unrelated:
|
⌛ Testing commit bc4810d with merge 07904bb68415bbe57a39c5c5eabf1150e0159c9c... |
💔 Test failed - status-travis |
Legit, please break clippy (edit
|
@kennytm Done. |
@bors r=eddyb |
📌 Commit 450bbc5 has been approved by |
impl Trait Lifetime Handling This PR implements the updated strategy for handling `impl Trait` lifetimes, as described in [RFC 1951](https://github.com/rust-lang/rfcs/blob/master/text/1951-expand-impl-trait.md) (cc #42183). With this PR, the `impl Trait` desugaring works as follows: ```rust fn foo<T, 'a, 'b, 'c>(...) -> impl Foo<'a, 'b> { ... } // desugars to exists type MyFoo<ParentT, 'parent_a, 'parent_b, 'parent_c, 'a, 'b>: Foo<'a, 'b>; fn foo<T, 'a, 'b, 'c>(...) -> MyFoo<T, 'static, 'static, 'static, 'a, 'b> { ... } ``` All of the in-scope (parent) generics are listed as parent generics of the anonymous type, with parent regions being replaced by `'static`. Parent regions referenced in the `impl Trait` return type are duplicated into the anonymous type's generics and mapped appropriately. One case came up that wasn't specified in the RFC: it's possible to write a return type that contains multiple regions, neither of which outlives the other. In that case, it's not clear what the required lifetime of the output type should be, so we generate an error. There's one remaining FIXME in one of the tests: `-> impl Foo<'a, 'b> + 'c` should be able to outlive both `'a` and `'b`, but not `'c`. Currently, it can't outlive any of them. @nikomatsakis and I have discussed this, and there are some complex interactions here if we ever allow `impl<'a, 'b> SomeTrait for AnonType<'a, 'b> { ... }`, so the plan is to hold off on this until we've got a better idea of what the interactions are here. cc #34511. Fixes #44727.
☀️ Test successful - status-appveyor, status-travis |
Additional uses of this item were added to these files in rust-lang#45701 and rust-lang#46479
Additional uses of this item were added to these files in rust-lang#45701 and rust-lang#46479
Additional uses of this item were added to these files in rust-lang#45701 and rust-lang#46479
This PR implements the updated strategy for handling
impl Trait
lifetimes, as described in RFC 1951 (cc #42183).With this PR, the
impl Trait
desugaring works as follows:All of the in-scope (parent) generics are listed as parent generics of the anonymous type, with parent regions being replaced by
'static
. Parent regions referenced in theimpl Trait
return type are duplicated into the anonymous type's generics and mapped appropriately.One case came up that wasn't specified in the RFC: it's possible to write a return type that contains multiple regions, neither of which outlives the other. In that case, it's not clear what the required lifetime of the output type should be, so we generate an error.
There's one remaining FIXME in one of the tests:
-> impl Foo<'a, 'b> + 'c
should be able to outlive both'a
and'b
, but not'c
. Currently, it can't outlive any of them. @nikomatsakis and I have discussed this, and there are some complex interactions here if we ever allowimpl<'a, 'b> SomeTrait for AnonType<'a, 'b> { ... }
, so the plan is to hold off on this until we've got a better idea of what the interactions are here.cc #34511.
Fixes #44727.