Skip to content

Better closure requirement propagation V2#154271

Open
LorrensP-2158466 wants to merge 3 commits intorust-lang:mainfrom
LorrensP-2158466:fix-typetest-closure-prop
Open

Better closure requirement propagation V2#154271
LorrensP-2158466 wants to merge 3 commits intorust-lang:mainfrom
LorrensP-2158466:fix-typetest-closure-prop

Conversation

@LorrensP-2158466
Copy link
Copy Markdown
Contributor

@LorrensP-2158466 LorrensP-2158466 commented Mar 23, 2026

Fixes #154267
Unblocks #151510.

We only propagate T: 'ub requirements when 'ub actually outlives the lower bound of the type test. If none of the non-local upper bounds outlive the lower bound, then we propagate all of them.

r? @lcnr

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Mar 23, 2026
Comment thread compiler/rustc_borrowck/src/region_infer/mod.rs Outdated
@LorrensP-2158466 LorrensP-2158466 changed the title Implement fix in try_promote_type_test_subject + add a test. Better closure requirement propagation V2 Mar 24, 2026
Comment thread compiler/rustc_borrowck/src/region_infer/mod.rs Outdated
@lcnr
Copy link
Copy Markdown
Contributor

lcnr commented Apr 17, 2026

I do still have a feeling that I am not completely understanding what you think is the correct fix. How I understand the current issue: When type testing T: 'c, we don't actually know that we are already (or will) require T': a, so the only way to prove that T: 'c is that T should outlive either 'a or 'b, right?

Yeah, let me write down my thoughts here. There are actually multiple things here. We can prove T: 'local by propagating a constraint T: 'ext where we know/separately require 'ext: 'local

What we currently do on stable and what I didn't even think of, is that if we have 'ext1: 'local, 'ext2: 'local, 'ext2: 'ext1 we actually propagate both T: 'ext1 and T: 'ext2. We should pick the minimal external regions here to propagate the weakest constraint which still implies T: 'local. In this case that is T: 'ext1.

Separately, if we have 'ext1: 'local and 'ext2: 'local without any outlives relation between 'ext1 and 'ext2 we'd need to prove T: 'ext1 OR T: 'ext2 and there isn't an obvious better choice. We do not support propagating OR constraints, so we currently just propagate both.

Now, if we know that we'll separately propagate T: 'ext1 anyways, there is no need to also propagate T: 'ext2. The question is: how does propagating type tests know that one of the constraints in the OR is already required or guaranteed to hold.

We do have access to self.type_tests here, so you could go through the list of type tests and check whether these already contain some T: 'ext1 constraint.

Separately, we could handle type tests as a two step pass. First collect all type tests which have a single possible candidate, then go through all the ones with multiple options and check whether one of the options is already part of the list of definitely required type tests.

Could also do that separately/not handle that part for now 🤷

@LorrensP-2158466
Copy link
Copy Markdown
Contributor Author

Thanks for the explanation, i understand now.

Yeah, let me write down my thoughts here. There are actually multiple things here. We can prove T: 'local by propagating a constraint T: 'ext where we know/separately require 'ext: 'local

What we currently do on stable and what I didn't even think of, is that if we have 'ext1: 'local, 'ext2: 'local, 'ext2: 'ext1 we actually propagate both T: 'ext1 and T: 'ext2. We should pick the minimal external regions here to propagate the weakest constraint which still implies T: 'local. In this case that is T: 'ext1.

I am with you here, that's what this PR is currently doing right?

Separately, if we have 'ext1: 'local and 'ext2: 'local without any outlives relation between 'ext1 and 'ext2 we'd need to prove T: 'ext1 OR T: 'ext2 and there isn't an obvious better choice. We do not support propagating OR constraints, so we currently just propagate both.

Now, if we know that we'll separately propagate T: 'ext1 anyways, there is no need to also propagate T: 'ext2. The question is: how does propagating type tests know that one of the constraints in the OR is already required or guaranteed to hold.

Yes right, I was thinking in the same direction, this is the part where i am stuck because a clean solution didn't come to me. I only came up with the following myself:

We do have access to self.type_tests here, so you could go through the list of type tests and check whether these already contain some T: 'ext1 constraint.

But this seems a bit unnatural to me, i prefer the other one:

Separately, we could handle type tests as a two step pass. First collect all type tests which have a single possible candidate, then go through all the ones with multiple options and check whether one of the options is already part of the list of definitely required type tests.

I find that this equates to finding the "minimal required" type tests. Which we seem to resort to in these closure propagation pr's.

Could also do that separately/not handle that part for now 🤷

I will try both solutions anyway, you never know :). i'll create separate branches in my fork and create a channel on zullip to discuss them. If you want it done in another way, lmk!

@rustbot

This comment has been minimized.

@LorrensP-2158466 LorrensP-2158466 force-pushed the fix-typetest-closure-prop branch from 05a43d7 to 657862c Compare April 23, 2026 20:17
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.
@rust-log-analyzer

This comment has been minimized.

@LorrensP-2158466 LorrensP-2158466 force-pushed the fix-typetest-closure-prop branch from 657862c to d27d602 Compare April 23, 2026 20:33
@LorrensP-2158466
Copy link
Copy Markdown
Contributor Author

I will try both solutions anyway, you never know :). i'll create separate branches in my fork and create a channel on zullip to discuss them. If you want it done in another way, lmk!

Yeah, nevermind, the 2-phase way is much easier to implement and is more logical (at least to me :)). When trying to define the problem it reminded me of Unit Propagation. Basically given a boolean expression in conjunctive normal form (i.e boolean expressions with AND operators between them), try to simplify the expressions by "propagating unit expressions".

Filtering these OR requirements in conjunctive normal form can be reduced to this Unit Propagation problem, but it is simpler, the subexpressions only contain OR operators, so the full expression is true if at least one unit is true. Thus, Given your example:

  • we first propagate: T: 'ext1 OR T: 'ext2, call this R1
  • we then propagate: T: 'ext1, call this R2

Then the full requirement (expression) is R1 AND R2; we can remove R1 because the unit R2 is contained within it and if R2 is true, T2 will be true as well.

This can be done in 1 pass, as the code does. And it compiles your harder example successfully.

@rustbot ready

@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Apr 23, 2026

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.

@rust-log-analyzer

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

unnecessary closure constraint propagation V2

4 participants