WF checks on closure arguments and improved type-test promotion. - #151510
LorrensP-2158466 wants to merge 3 commits into
Conversation
| error: lifetime may not live long enough | ||
| --> $DIR/check-wf-of-closure-args.rs:13:41 | ||
| | | ||
| LL | let _: for<'x> fn(MyTy<&'x str>) = |x| wf(x); | ||
| | ^ | ||
| | | | ||
| | has type `MyTy<&'1 str>` | ||
| | requires that `'1` must outlive `'static` | ||
|
|
||
| error: aborting due to 2 previous errors |
There was a problem hiding this comment.
I find that the error message is a bit worse now, compared to the old:
error[E0521]: borrowed data escapes outside of closure
--> src/lib.rs:10:44
|
10 | let _: for<'x> fn(MyTy<&'x str>) = |x| wf(x); // FAIL
| - ^^^^^
| | |
| | `x` escapes the closure body here
| | argument requires that `'1` must outlive `'static`
| `x` is a reference that is only valid in the closure body
| has type `MyTy<&'1 str>`
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
CI x86_64-gnu-tools failed with an error I don't understand: |
615d765 to
734d270
Compare
|
@bors try |
This comment has been minimized.
This comment has been minimized.
WF checks on closure arguments.
|
@craterbot check |
|
👌 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
This comment has been minimized.
This comment has been minimized.
|
🚧 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
|
🎉 Experiment
Footnotes
|
|
Why was the |
|
@LorrensP-2158466 can you look into how to fix |
|
@lcnr Will do, both! |
This comment was marked as outdated.
This comment was marked as outdated.
It does :(
it does indeed. |
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment has been minimized.
This comment has been minimized.
993b2c9 to
fe2cc8c
Compare
This comment has been minimized.
This comment has been minimized.
|
force-pushed because i had to rebase. r? @oli-obk Some things that have changed (i'll update the description once we have something concrete here): WF checksThe first commit is still the introduction of WF checks on closure argument and return types, but now with all the "machinery" of collecting any errors produced by this WF checks and emitting them as FCW's instead. As discussed in #t-types/nominated > #151510: WF checks on closure arguments and improved type-t…. The gist of it is: we run borrowck twice, once with the WF checks enabled, if there are errors, we run it again without them enabled. We can then diff the errors and act accordingly. Oh no, large diff alertBecause we run borrowck twice, the improved type-test closure promotionThe second and third commit are still the same as the previous state of this pr and the work of #154271. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…m as FCW. Runs borrowck in 2 modes to capture errors emitted due to WF checks on closure arg and return types, these are then emitted as FCWs. We first run borrowck in the Yes mode and remember the emitted errors, then we run it again with WF checks turned off. We take the diff off these errors and report them as FCW, the rest are the same and emitted as is. + add tests of issue + bless/change other tests
Fix in question: We only propagate `T: 'ub` requirements when 'ub actually outlives the lower bounds of the type test. If none of the non-local upper bounds outlive it, then we propagate all of them. + filter redundant requirements through unit propagation & bless test
fe2cc8c to
1114dc9
Compare
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
|
Had to rebase due to conflicts and failing tests with |
|
@rfcbot fcp cancel (not a breaking change anymore, though has an FCW, also gonna wait for next solver to be enabled on stable before merging this) |
|
@oli-obk proposal cancelled. |
| options: ConsumerOptions, | ||
| ) -> FxHashMap<LocalDefId, BodyWithBorrowckFacts<'_>> { | ||
| let tainted_by_errors = Default::default(); | ||
| // FIXME: run with `WfCheckClosures::FCW` as well? |
There was a problem hiding this comment.
nah. custom driver users explicitly using this can just get the new experience 😆
There was a problem hiding this comment.
alright, thats clear 😆
| } | ||
|
|
||
| Some(_) => root_cx.finalize(), | ||
| None => unreachable!("`WfCheckClosures::Yes` always returns `Some(errors)`"), |
There was a problem hiding this comment.
I think at that point you can just not return an Option, but just always return the errors list in both modes and avoid the match here
| fcw_reason: FutureIncompatibilityReason, | ||
| current_edition: Edition, | ||
| ) { | ||
| self.level = Level::Warning(None); |
There was a problem hiding this comment.
assert that it was an error and mention so in the doc comment
|
☔ The latest upstream changes (presumably #163609) made this pull request unmergeable. Please resolve the merge conflicts by rebasing. |
View all comments
Fixes #104478.
Fixes #154267.
Enables WF checks on the arguments of a closure and improves type-test promotion.
The now removed function
ascribe_user_type_skip_wfmentioned that skipping WF was done due to backwards compatibility reasons.FCP below explains the type-test changes.
FCP: WF checks on closure arguments and better type-test propagation
Summary
On stable, we only perform WF-checks on closure arguments when that argument is actually used. This causes non WF code to still compile.
This was necessary as properly checking them for WF caused breakage due to a weakness of closure requirement propagation #104477, see #104478. As we've fixed #104477 in #148329 and in this PR, the remaining breakage is now all intended.
This change cause compilation errors when a used type is not WF. This is an intentional breakaging change.
Example
Consider the example from the first linked issue of this PR.
Currently, on stable, this fails only because of the second closure:
We don't emit an error for the first closure because the argument is not used in the closure body, even though it is also not WF.
With this PR we now error for both closures:
Improved Type Test Promotion
When only enabling WF-checks on closure arguments, a regression was observed when compiling
jobsteal: See the crater triage by @theemathas #151510 (comment).This regression and possible others can be resolved by immproving type-test promotion, this is the second part of the PR. We've already changed closure requirement propagation for region outlive constraints, see #148329 for background here.
For a constraint like
T: 'a,Tis promoted first, replacing all free regions inTwith'staticor equal region parameters, producing a constraint subject, free of closure-local regions - failing if any region has no such replacement.'ais promoted to the caller's context, we then find non-local universal regions that'aoutlives. On stable, we propagateT: 'ubfor all non-local upper bounds of'a.These constraints are too conservative. We find some universal regions
'urin the SCC of'a, then for each of those'urwe find their non-local upper bounds and propagateT: 'ub. Though, we actually need only oneT: 'ubto proveT: 'ur. However, the compiler does not support OR constraints (e.g.T: 'ub1ORT: 'ub2), so we require them all.The behavior of this PR
There are 2 changes made in this PR relevant to the current behaviour on stable: We minimize the amount of universal regions found for
'aand we minimize the amount of OR requirements we propagate to the closure creator.Example for Universal Regions Minization
Consider this program:
It currently fails to compile and gives the following error:
The closure tries to prove
T: 'a, but it can't, so it tries to promote it to the creator.On stable, fn try_promote_type_test finds all universal regions in the constraint graph for the lower bound
'a:These are
'aand'c, then for each of those regions, we find their non-local upper bounds'uband propagateT: 'ub:'a, this is just'a->T: 'a'c, these are'aand'b->T: 'aORT: 'bT: 'aholds in the parent. We also propagateT: 'bon stable, resulting in an error.This P first minimizes the regions returned by
self.scc_values.universal_regions_outlived_by(r_scc)by removing any region already outlived by another region in the set. For the example above we would get['a, 'c], which we would be minimized to['a], because'c: 'a. Thus never propagatingT: 'b.The reason we can do this "minimization" is somewhat trivial. If we need to prove
T: '1andT: '2, but'1: '2holds, there is no reason to proveT: '2as well.Example for OR Requirement minimization
Say we introduce another type test on the first example:
This
T: 'ctype-test now introduces an implicit OR requirement: the non-local upper bounds of'care['a, 'b], thus propagatingT: 'a OR T: 'bas two standalone requirements, both of which should hold. Even though our example shows thatconstrainalready requiresT: 'a, makingT: 'bredundant.This PR changes the way these requirements are propagated, essentially propagating a list of requirements for each univeral regions. This list is seen as a collection of OR requirements and if we have multiple such lists, we require all of them to hold. In our above example this would result in 2 OR requirements:
R1:T: 'afrom type testT: 'a.R2:T: 'a OR T: 'bfrom type testT: 'c.With full requirement
R1 AND R2represented by[R1, R2]. We then loop over these OR requirements and collect all "unit" requirements (R1 above). We then loop again over the OR requirements and remove every OR requirement which contains at least one unit requirement. For our example, this would result in removingR2, because it containsR1.Note that we can be even smarter during this removal process, say we had following OR requirements:
R1:T: 'aR2:T: 'b OR T: 'cWith full requirement
R1 AND R2and assumption'a: 'b. This assumption would allow us to removeR2, becauseT: 'aimpliesT: 'bthrough the assumption.Reference
The specifics of WF checking is not documented in the reference.
We currently don't document borrowck in the reference. Once we do this, this is PR requires a localized change to how we propagate requirements from nested bodies.
Coverage
See the tests added in this PR for things which now (don't) compile.
Outstanding bugs WF
There is a related unsoundness in #151637, which is not fixed by this PR. This is due to closures using implied bounds only involving external regions and needs to be fixed separately. See the WIP work in #153027.
Outstanding bugs type-test Promotion
We sometimes still propagate multiple constraints even though a single one would be sufficient, potentially causing unnecessary errors.
To avoid any such errors here, we'd probably have to support OR constraints. There may also be some future improvements to closure constraint propagation, but 🤷 we want to avoid introducing non-determinstic or surprising behavior here.
Breaking changes
This change is intentionally breaking: (TODO after crater).
History
WF checks on closure arguments was left as future work in #101947 and tracked in #104478.
Propagating closure requirements was originally implemented by @nikomatsakis in #46509 and then improved by @matthewjasper in #58347.
It was then improved by @LorrensP-2158466 in #148329.
Open items
Rustc Dev Guide should be updated: type-outlive constraints.
r? @lcnr