-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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_overcaptures
: Don't worry about uncaptured contravariant lifetimes if they outlive a captured lifetime
#129028
Conversation
r? @Nadrieril rustbot has assigned @Nadrieril. Use |
r? @lcnr |
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.
ignore the review comments for now. I am still a bit confused by the general approach, gonna try to walk through it in a separate comment
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.
The idea is: this lint only works on edition < 2021 because it uses the fact that uncaptured lifetimes are bivariant in the opaque type.
So walking the whole signature, including the opaque type correctly gets the variance of all used lifetimes. The lint currently triggers for all currently uncaptured lifetimes which are not outlived by a captured lifetime.
The idea is now that we also don't lint for contravariant lifetimes of the function. In inputs, these are covariant for the caller, so the caller can arbitrarily shorten their lifetimes before passing them into the function. While for contravariant lifetimes in the return type1, the caller can simply rely on subtyping of the return type to lengthen them back.
Now, the reason these contravariant lifetimes have to outlive any of the captured region is that otherwise shortening them may result in new errors because it reduces the lifetime of the opaque type itself1.
If so, is that in issue even if we have an explicit outlives bound on the opaque?
@compiler-errors did I understand this correctly?
Footnotes
I personally don't care for the cases where users explicitly specify the lifetimes used in their function. I can't imagine that there will be many (if any) cases where it cases issues. |
This lint only matters on edition < 2021, for the record. It's an edition migration lint after all, so in edition 2024 everything is captured already.
The lint on nightly triggers for all uncaptured lifetimes, regardless if they are longer than a captured lifetime.
The idea now is that we don't lint for contravariant lifetimes that are also longer (i.e. they outlive) than a captured lifetime. But yes, you're exactly right.
Very much correct. We know that any type-outlives obligation that is being put on the opaque already has to prove that all the captured lifetimes outlive the goal lifetime, so if we put another lifetime into there that already outlives one of those captured lifetimes, then it's still trivially provable.
Well, probably not; if we have an explicit outlives bound on the opaque then capturing another lifetime shouldn't matter for the purposes of liveness computation. I guess we could always suggest overcapturing a lifetime if there's an explicit outlives bound, but things will still fail in exactly the cases that #116733 did not fix. |
@rustbot author |
a73852b
to
a08aa18
Compare
@rustbot ready |
// Then visit the signature to walk through all the binders (incl. the late-bound | ||
// vars on the function itself, which we need to count too). | ||
sig.visit_with(&mut VisitOpaqueTypes { | ||
tcx, | ||
parent_def_id, | ||
in_scope_parameters, | ||
seen: Default::default(), | ||
// Lazily compute these two, since they're likely a bit expensive. | ||
variances: LazyCell::new(|| { |
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.
did you actually test whether computing them eagerly is expensive?
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.
Kinda don't think it's necessary to test, and I feel like I'd rather not generate these for every item for every edition anyways
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 on the impl, does this need any team signoff?
@rustbot author |
No I don't think so. I've discussed this optimization with @traviscross too and he seemed enthusiastic. |
then r=me after rebase |
Er, not optimization; refinement of the lint. cool, can do. Ty for review. |
a08aa18
to
f29b818
Compare
This comment has been minimized.
This comment has been minimized.
…a captured lifetime
f29b818
to
c1d0410
Compare
@bors r=lcnr |
`impl_trait_overcaptures`: Don't worry about uncaptured contravariant lifetimes if they outlive a captured lifetime **NOTE:** Review only the first commit carefully. The second one is just moving stuff around, so you can turn whitespace off for that one. This PR relaxes the `impl_trait_overcaptures` lint to not fire in cases like: ```rust struct Ctxt<'tcx>(&'tcx ()); impl<'tcx> Ctxt<'tcx> { fn compute(&self) -> impl Sized + '_ { } } ``` Specifically, the lint will not fire if **all** overcaptured regions (i.e. those which will be captured in edition 2024, which are not captured today) **satisfy**: * The region is contravariant (or bivariant) in the function signature * The region outlives some other region which is captured by the opaque ### The idea behind this Why is this OK? My reasoning is that since the region is contravariant in the function signature, we know that it can be shortened arbitrarily at the call site. And specifically, we know it can be shortened to be equal to one of the regions that it outlives -- that's why we need to prove that it outlives some other region that *is* captured. We could technically relax this further, but there would be (IMO somewhat easy) cases to make this a false negative in real code. For example, if the region is invariant, then we can hit issues like: ```rust struct Ctxt<'tcx>(&'tcx mut &'tcx mut ()); impl<'tcx> Ctxt<'tcx> { fn compute(&self) -> impl Sized + use<'_, 'tcx> { } // We use `use<'_, 'tcx>` to show what happens in edition 2024 } fn test<'a, 'b>(x: &'a Ctxt<'b>, y: &'a Ctxt<'a>) { let results = [x.compute(), y.compute()]; //~^ ERROR lifetime may not live long enough // Since both opaques now capture `'tcx`, this enforces that `'a == 'b`. } ``` ### Is this actually totally fine? There's one case where users might still hit issues, and it's if we turbofish lifetimes directly: ```rust struct Ctxt<'tcx>(&'tcx ()); impl<'tcx> Ctxt<'tcx> { fn compute(&self) -> impl Sized + use<'_, 'tcx> { } } fn test<'a, 'b>(x: &'a Ctxt<'b>, y: &'a Ctxt<'a>) { let results = [Ctxt::<'b>::compute(x), Ctxt::<'a>::compute(y)]; //~^ ERROR lifetime may not live long enough // Since both opaques now capture `'tcx`, this enforces that `'a == 'b`. // Note that we don't shorten `'b` to `'a` since we turbofished it. } ``` ### Well... we should still warn? I kinda don't care about this case, though I guess we could possibly downgrade the lint to something like `IMPL_TRAIT_OVERCAPTURES_STRICT` instead of suppressing it altogether. Thoughts? If we were to do this, then I'd probably also opt to include the invariant case in `IMPL_TRAIT_OVERCAPTURES_STRICT` and move it out of `IMPL_TRAIT_OVERCAPTURES`.
…iaskrgr Rollup of 8 pull requests Successful merges: - rust-lang#128820 (fix: get llvm type of global val) - rust-lang#129028 (`impl_trait_overcaptures`: Don't worry about uncaptured contravariant lifetimes if they outlive a captured lifetime) - rust-lang#129471 ([rustdoc] Sort impl associated items by kinds and then by appearance) - rust-lang#129706 (Rename dump of coroutine by-move-body to be more consistent, fix ICE in dump_mir) - rust-lang#129720 (Simplify DestProp memory management) - rust-lang#129796 (Unify scraped examples with other code examples) - rust-lang#129938 (Elaborate on deriving vs implementing `Copy`) - rust-lang#129973 (run_make_support: rename `Command::stdin` to `stdin_buf` and add `std{in,out,err}` config helpers) r? `@ghost` `@rustbot` modify labels: rollup
…iaskrgr Rollup of 8 pull requests Successful merges: - rust-lang#128820 (fix: get llvm type of global val) - rust-lang#129028 (`impl_trait_overcaptures`: Don't worry about uncaptured contravariant lifetimes if they outlive a captured lifetime) - rust-lang#129471 ([rustdoc] Sort impl associated items by kinds and then by appearance) - rust-lang#129706 (Rename dump of coroutine by-move-body to be more consistent, fix ICE in dump_mir) - rust-lang#129720 (Simplify DestProp memory management) - rust-lang#129796 (Unify scraped examples with other code examples) - rust-lang#129938 (Elaborate on deriving vs implementing `Copy`) - rust-lang#129973 (run_make_support: rename `Command::stdin` to `stdin_buf` and add `std{in,out,err}` config helpers) r? `@ghost` `@rustbot` modify labels: rollup
Rollup merge of rust-lang#129028 - compiler-errors:contra, r=lcnr `impl_trait_overcaptures`: Don't worry about uncaptured contravariant lifetimes if they outlive a captured lifetime **NOTE:** Review only the first commit carefully. The second one is just moving stuff around, so you can turn whitespace off for that one. This PR relaxes the `impl_trait_overcaptures` lint to not fire in cases like: ```rust struct Ctxt<'tcx>(&'tcx ()); impl<'tcx> Ctxt<'tcx> { fn compute(&self) -> impl Sized + '_ { } } ``` Specifically, the lint will not fire if **all** overcaptured regions (i.e. those which will be captured in edition 2024, which are not captured today) **satisfy**: * The region is contravariant (or bivariant) in the function signature * The region outlives some other region which is captured by the opaque ### The idea behind this Why is this OK? My reasoning is that since the region is contravariant in the function signature, we know that it can be shortened arbitrarily at the call site. And specifically, we know it can be shortened to be equal to one of the regions that it outlives -- that's why we need to prove that it outlives some other region that *is* captured. We could technically relax this further, but there would be (IMO somewhat easy) cases to make this a false negative in real code. For example, if the region is invariant, then we can hit issues like: ```rust struct Ctxt<'tcx>(&'tcx mut &'tcx mut ()); impl<'tcx> Ctxt<'tcx> { fn compute(&self) -> impl Sized + use<'_, 'tcx> { } // We use `use<'_, 'tcx>` to show what happens in edition 2024 } fn test<'a, 'b>(x: &'a Ctxt<'b>, y: &'a Ctxt<'a>) { let results = [x.compute(), y.compute()]; //~^ ERROR lifetime may not live long enough // Since both opaques now capture `'tcx`, this enforces that `'a == 'b`. } ``` ### Is this actually totally fine? There's one case where users might still hit issues, and it's if we turbofish lifetimes directly: ```rust struct Ctxt<'tcx>(&'tcx ()); impl<'tcx> Ctxt<'tcx> { fn compute(&self) -> impl Sized + use<'_, 'tcx> { } } fn test<'a, 'b>(x: &'a Ctxt<'b>, y: &'a Ctxt<'a>) { let results = [Ctxt::<'b>::compute(x), Ctxt::<'a>::compute(y)]; //~^ ERROR lifetime may not live long enough // Since both opaques now capture `'tcx`, this enforces that `'a == 'b`. // Note that we don't shorten `'b` to `'a` since we turbofished it. } ``` ### Well... we should still warn? I kinda don't care about this case, though I guess we could possibly downgrade the lint to something like `IMPL_TRAIT_OVERCAPTURES_STRICT` instead of suppressing it altogether. Thoughts? If we were to do this, then I'd probably also opt to include the invariant case in `IMPL_TRAIT_OVERCAPTURES_STRICT` and move it out of `IMPL_TRAIT_OVERCAPTURES`.
NOTE: Review only the first commit carefully. The second one is just moving stuff around, so you can turn whitespace off for that one.
This PR relaxes the
impl_trait_overcaptures
lint to not fire in cases like:Specifically, the lint will not fire if all overcaptured regions (i.e. those which will be captured in edition 2024, which are not captured today) satisfy:
The idea behind this
Why is this OK? My reasoning is that since the region is contravariant in the function signature, we know that it can be shortened arbitrarily at the call site. And specifically, we know it can be shortened to be equal to one of the regions that it outlives -- that's why we need to prove that it outlives some other region that is captured.
We could technically relax this further, but there would be (IMO somewhat easy) cases to make this a false negative in real code. For example, if the region is invariant, then we can hit issues like:
Is this actually totally fine?
There's one case where users might still hit issues, and it's if we turbofish lifetimes directly:
Well... we should still warn?
I kinda don't care about this case, though I guess we could possibly downgrade the lint to something like
IMPL_TRAIT_OVERCAPTURES_STRICT
instead of suppressing it altogether. Thoughts? If we were to do this, then I'd probably also opt to include the invariant case inIMPL_TRAIT_OVERCAPTURES_STRICT
and move it out ofIMPL_TRAIT_OVERCAPTURES
.