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

Correctly check auto traits on generator interiors #92449

Closed

Conversation

compiler-errors
Copy link
Member

@compiler-errors compiler-errors commented Dec 30, 2021

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 a ProjectionPredicate 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

@rustbot rustbot added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Dec 30, 2021
@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Dec 30, 2021
@compiler-errors
Copy link
Member Author

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. :)

@compiler-errors compiler-errors changed the title Check auto traits on generator interiors on non- Check auto traits on generator interiors Dec 30, 2021
@compiler-errors compiler-errors changed the title Check auto traits on generator interiors Correctly check auto traits on generator interiors Dec 30, 2021
@rust-log-analyzer

This comment has been minimized.

@Aaron1011
Copy link
Member

cc @rust-lang/wg-traits

assert_foo(gen);
//~^ ERROR not general enough
//~| ERROR not general enough
assert_foo(gen); // bad -- why does this pass?
Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Member Author

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.

@bors
Copy link
Contributor

bors commented Jan 10, 2022

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

@jackh726
Copy link
Member

@compiler-errors does this happen to fix #92096?

@compiler-errors
Copy link
Member Author

@jackh726 it does fix it, I'll add it to the list.

@jackh726
Copy link
Member

😃🎉 wow nice

@eholk
Copy link
Contributor

eholk commented Jan 10, 2022

I read over this PR since it touches generator_interior.rs and I've been working in that file a lot lately too. I'm not an expert in most of the areas this PR touches, but from at least a surface level it looks good. It's a minor change, but I appreciate that you make the use of inner more consistent throughout the code. That test case issue you pointed out is weird though, as I mentioned in the other comment.

@bors
Copy link
Contributor

bors commented Jan 11, 2022

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

@compiler-errors
Copy link
Member Author

I wonder if we could/need to limit this to purely structural impls for Send and Sync (and perhaps other auto traits with no implied lifetime obligations), because of the limitations that I pointed out in that weird test case.

I think that would still address all of these async+Future/Stream combinator cases without introducing unsoundness...

@jackh726
Copy link
Member

This is on my list of things to look at...just need to find time.

@nikomatsakis
Copy link
Contributor

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!

@compiler-errors
Copy link
Member Author

compiler-errors commented Jan 17, 2022

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.

@bors
Copy link
Contributor

bors commented Jan 18, 2022

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

Copy link
Contributor

@nikomatsakis nikomatsakis left a 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)]
Copy link
Contributor

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.

compiler/rustc_middle/src/ty/sty.rs Outdated Show resolved Hide resolved
compiler/rustc_middle/src/ty/sty.rs Outdated Show resolved Hide resolved
compiler/rustc_middle/src/ty/walk.rs Show resolved Hide resolved
compiler/rustc_middle/src/ty/walk.rs Show resolved Hide resolved
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)
Copy link
Contributor

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

Copy link
Member Author

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.
Copy link
Contributor

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"?

compiler/rustc_middle/src/ty/sty.rs Outdated Show resolved Hide resolved
@compiler-errors
Copy link
Member Author

compiler-errors commented Jan 28, 2022

One interesting case that this PR still doesn't work is when our generator has a BTreeMap in it, since the nodes inside of the BTreeMap have an unsafe impl Send which relates some regions we know nothing about during generator interior analysis.

@compiler-errors
Copy link
Member Author

rebase and add a few more comments.

Copy link
Contributor

@nikomatsakis nikomatsakis left a 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.
Copy link
Contributor

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.

Copy link
Member Author

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>> {
Copy link
Contributor

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.

Copy link
Member Author

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(
Copy link
Contributor

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.

@bors
Copy link
Contributor

bors commented Feb 8, 2022

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

@JohnCSimon JohnCSimon added 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 Mar 20, 2022
@Dylan-DPC
Copy link
Member

@compiler-errors what's the status on this? would be nice if you can resolve the conflicts as well

@compiler-errors
Copy link
Member Author

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.

@compiler-errors
Copy link
Member Author

I'm gonna mark this S-experimental for now...

@rustbot label -S-waiting-on-review +S-experimental

@rustbot rustbot added S-experimental Status: Ongoing experiment that does not require reviewing and won't be merged in its current state. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 24, 2022
@compiler-errors
Copy link
Member Author

Closing this since it needs to be reworked like crazy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-experimental Status: Ongoing experiment that does not require reviewing and won't be merged in its current state. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet