-
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
Correctly check auto traits on generator interiors #92449
Correctly check auto traits on generator interiors #92449
Conversation
Maybe I'm totally off the mark with what I'm doing in this diff. I would love feedback and help towards getting a correct solution, if there are significant issues with my implementation. :) |
ac0b1b4
to
38827e5
Compare
This comment has been minimized.
This comment has been minimized.
38827e5
to
2d7a0bf
Compare
cc @rust-lang/wg-traits |
assert_foo(gen); | ||
//~^ ERROR not general enough | ||
//~| ERROR not general enough | ||
assert_foo(gen); // bad -- why does this pass? |
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.
So I was thinking about this test case recently.
I think why this doesn't fail is because we don't actually enforce any dropck... So there's nothing enforcing the lifetime needs to be shorter than 'static
.
Wonder if there's any way of hooking that information in during auto trait confirmation.
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.
If I'm understanding this right, this seems concerning. So the issue is that a
is !Foo
because of the No
field included in the struct. Then a
obviously lives across the yield
point since we explicitly drop it afterwards. This means a
needs to be saved in the generator, so the resulting generator should be !Foo
as well, but yet assert_foo(gen)
passes...
Oh, but then we explicitly impl Foo
for A
, which overrides the auto rules, right? However, the impl shouldn't apply because the Cell::new(&mut y)
field is not bound by 'static
since y
ends at the end of the generator.
I agree with you, it seems like this shouldn't pass, and it seems likely to permit undefined behavior if it does...
Maybe the compiler is doing something smart elsewhere by recognizing that a
is destructed at the end of the generator as well so it can automatically shorten the lifetimes? That seems like that'd be extremely difficult to do safely though, so that seems unlikely to me.
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.
Yeah, this is because of the fact we refresh the late bound regions with totally new inference variables. While stashing away all of the region constraints we collect in generator_interior is okay to enforce most borrowchecky things, we still can't enforce anything like requiring one local variable living for longer than another (or static) in the generator interior with my implementation as-is.
But yeah, this one's the case that I need someone who knows borrowck/dropck better to help with.
☔ The latest upstream changes (presumably #92719) made this pull request unmergeable. Please resolve the merge conflicts. |
@compiler-errors does this happen to fix #92096? |
@jackh726 it does fix it, I'll add it to the list. |
2d7a0bf
to
465c97f
Compare
😃🎉 wow nice |
I read over this PR since it touches |
☔ The latest upstream changes (presumably #92070) made this pull request unmergeable. Please resolve the merge conflicts. |
465c97f
to
4926cb3
Compare
I wonder if we could/need to limit this to purely structural impls for Send and Sync (and perhaps other I think that would still address all of these async+Future/Stream combinator cases without introducing unsoundness... |
This is on my list of things to look at...just need to find time. |
I have this on my list to review, sorry for being slow, I'm trying to free up more time for reviewing and the like! |
4926cb3
to
c44c2d6
Compare
No worries, and thanks for the followup comment @nikomatsakis. I understand you're probably quite busy after the holidays, so take your time getting around to review this PR. Looking forward to your review. 😃 That being said, I reworked this PR with an alternative idea that doesn't use fresh region variables, to address the unsoundness that @eholk and I talked about in previous review comments. NOTE: While I think this approach is far more solid, some of the issues I had originally claimed to fix are no longer passing. I have a pretty good understanding of why each of these tests continue to fail, though I probably need some help brainstorming how to modify this PR to make them pass. |
☔ The latest upstream changes (presumably #87648) made this pull request unmergeable. Please resolve the merge conflicts. |
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.
Didn't get all the way through yet -- first round of interim comments!
@@ -2308,3 +2308,10 @@ impl<'tcx> VarianceDiagInfo<'tcx> { | |||
} | |||
} | |||
} | |||
|
|||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] |
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.
Pre-existing, but we really need a comment here. It'd be good to explain the role of this type and each of its fields.
self.tcx().erase_late_bound_regions(*tys).iter().map(|t| (t, depth + 1)), | ||
ty::GeneratorWitness(interior) => stack.extend( | ||
self.tcx() | ||
.erase_late_bound_regions(*interior) |
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.
probably makes sense to "select" the types and erase only those -- but maybe we want to include the predicates here too? not obvious to me that we don't
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.
I actually rewrote this code in #93028, so this comment no longer applies I think. I'll rebase this PR.
|
||
let param_env = obligation.param_env; | ||
// Augment the param env with the projections that we know hold structurally | ||
// in the generator interiors' types. |
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.
What do you mean by "hold structurally"?
One interesting case that this PR still doesn't work is when our generator has a |
8e963ea
to
9e72ef4
Compare
rebase and add a few more comments. |
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.
OK, round two of review done. I now understand what you're doing at a high-level. Quite clever! I hadn't really considered this approach. I have to think about it and do some local experimentation. I expect to return to this PR on Wednesday for further review.
// Extract the projection types that we know hold in the interior types, so we can reapply them | ||
// later. This step is important because when we erase regions, we might no longer be able to | ||
// apply some projections (since they depend on region knowledge we erased). But since we know | ||
// they hold now, they should hold forever, so we'll add these projections to the param-env later. |
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.
It's not clear to me that this is true. The region relationships aren't really checked now, they are checked later, in MIR borrow check. I have to ponder whether I can make a failing test case, though, it may be that we wind up enforcing the resulting constraints in MIR borrow check as well. If not, I think we could fix it by "reproving" these predicates.
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.
it may be that we wind up enforcing the resulting constraints in MIR borrow check as well
Yeah that's what I thought would happen here. What I thought was: when we normalize the type, it registers obligations into the fcx with the local (non-erased) region variables. If we had a bad projection type, then we would have a borrowck error regardless, so it's fine to treat this as "solved" when we check the generator interior later on.
/// Bar<i32> where struct Bar<T> { x: T, y: u32 } -> [i32, u32] | ||
/// Zed<i32> where enum Zed { A(T), B(u32) } -> [i32, u32] | ||
/// ``` | ||
fn constituent_types_for_auto_trait(&self, t: Ty<'tcx>) -> Vec<Ty<'tcx>> { |
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.
This is a duplicate of logic in trait checking, which doesn't seem great.
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.
Yeah, it's mostly a duplicate. I just needed some way of walking thru the types inside of a type (and into an ADT's field). Maybe I can simplify this into just a type visitor that pushes more types onto a stack if it sees an ADT.
// or just make a new param_env from these predicates... | ||
// Some tests fail if we do the former, but doing the latter doesn't | ||
// sound like it works 100% either... | ||
let new_param_env = ty::ParamEnv::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.
This logic of being able to handle implication is one of the things that I wanted to add to the compiler via chalk, fwiw, but it may be possible to do it locally this way.
☔ The latest upstream changes (presumably #92007) made this pull request unmergeable. Please resolve the merge conflicts. |
@compiler-errors what's the status on this? would be nice if you can resolve the conflicts as well |
still waiting on review technically, though i'm not sure if it's the correct approach. i can rebase it though, it's been quite a while since i've touched this code. |
I'm gonna mark this S-experimental for now... @rustbot label -S-waiting-on-review +S-experimental |
Closing this since it needs to be reworked like crazy. |
Take 2 at Fixing #71723
Took a stab at fixing #71723, since it was annoying me while I was writing async code with
futures::stream
.(note: edited to reflect the new solution I wrote.)
The issue
The issue here is when a struct has a projection type in it. After we erase the regions within a generator-interior type, the projection type may no longer hold, since solving/normalizing that projection type may depend on region relations that are now erased from the type.
The solution
I created a helper type called
StructuralPredicateElaborator
that walks through the generator-interior types structurally (e.g. recurses on a type's constituents). Every time it sees a projection type, it turns that into aProjectionPredicate
that corresponds to the normalized type.We then later augment the param-env during auto trait confirmation with these projection types, so that we always normalize a projection type to the type that it would've normalized to inside of the generator.
Previous solution
In my previous solution, I was replacing the placeholders with new region variables. This ended up being unsound, because there was no way of enforcing dropck on the new variables we instantiated.
r? @nikomatsakis
Feel free to reassign, chose you because you currently own #71723.
Repo tracking some issue code: https://github.com/compiler-errors/pr-92449
Fixes #60658
Fixes #71723
Fixes #79648
Fixes #82921
Fixes #90696
That being said, this attempt number 2 does not fix:
#64552
#87425
#92415
#92096