Skip to content

support non-defining uses of opaques in borrowck #145244

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

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

lcnr
Copy link
Contributor

@lcnr lcnr commented Aug 11, 2025

Reimplements the first part of #139587, but limited to only the new solver. To do so I also entirely rewrite the way we handle opaque types in borrowck even on stable. This should not impact behavior however.

We now support revealing uses during MIR borrowck with the new solver:

fn foo<'a>(x: &'a u32) -> impl Sized + use<'a> {
    let local = 1;
    foo::<'_>(&local);
    x
}

How do opaque types work right now

Whenever we use an opaque type during type checking, we remember this use in the opaque_type_storage of the InferCtxt.

Right now, we collect all member constraints at the end of MIR type check by looking at all uses from the opaque_type_storage. We then apply these constraints while computing the region values for each SCC. This does not add actual region constraints but directly updates the final region values.

This means we need to manually handle any constraints from member constraints for diagnostics. We do this by separately tracking applied_member_constraints in the RegionInferenceContext.

After we've finished computing the region values, it is now immutable and we check whether all member constraints hold. If not, we error.

We now map the hidden types of our defining uses to the defining scope. This assumes that all member constraints apply. To handle non-member regions, we simply map any region in the hidden type we fail to map to a choice region to 'erased

let concrete_type = fold_regions(infcx.tcx, concrete_type, |region, _| {
arg_regions
.iter()
.find(|&&(arg_vid, _)| self.eval_equal(region.as_var(), arg_vid))
.map(|&(_, arg_named)| arg_named)
.unwrap_or(infcx.tcx.lifetimes.re_erased)
});

How do we handle opaque types with this PR

MIR type check still works the same by populating the opaque_type_storage whenever we use an opaque type.

We now have a new step fn handle_opaque_type_uses which happens between MIR type check and compute_regions.

This step looks at all opaque type uses in the storage and first checks whether they are defining: are the arguments of the opaque_type_key unique region parameters. With the new solver we silently ignore any non-defining uses here. The old solver emits an errors.

fn compute_concrete_opaque_types: We then collect all member constraints for the defining uses and apply them just like we did before. However, we do it on a temporary region graph which is only used while computing the concrete opaque types. We then use this region graph to compute the concrete type which we then store in the root_cx.

fn apply_computed_concrete_opaque_types: Now that we know the final concrete type of each opaque type and have mapped them to the definition of the opaque. We iterate over all opaque type uses and equate their hidden type with the instantiated final concrete type. This is the step which actually mutates the region graph.

The actual region checking can now entirely ignores opaque types (outside of the ConstraintCategory from checking the opaque type uses).

Diagnostics issue (chill)

Because we now simply use type equality to "apply member constraints" we get ordinary OutlivesConstraints, even if the regions were already related to another.

This is generally not an issue, expect that it can hide the actual region constraints which resulted in the final value of the opaque. The constraints we get from checking against the final opaque type definition relies on constraints we used to compute that definition.

I mostly handle this by updating find_constraint_path_between_regions to first ignore member constraints in its search and only if that does not find a path, retry while considering member constraints.

Diagnostics issue (not chill)

A separate issue is that find_constraint_paths_between_regions currently looks up member constraints by their scc, not by region value:

// Member constraints can also give rise to `'r: 'x` edges that
// were not part of the graph initially, so watch out for those.
// (But they are extremely rare; this loop is very cold.)
for constraint in self.applied_member_constraints(self.constraint_sccs.scc(r)) {
let sub = constraint.min_choice;
let p_c = &self.member_constraints[constraint.member_constraint_index];
handle_trace(sub, Trace::FromMember(r, sub, p_c.definition_span));
}

This means that in the borrowck-4 test, the resulting constraint path is currently

('?2: '?5) due to Single(bb0[5]) (None) (Boring) (ConstraintSccIndex(1): ConstraintSccIndex(1)),
('?5: '?3) due to Single(bb0[6]) (None) (Boring) (ConstraintSccIndex(1): ConstraintSccIndex(1)),
('?3: '?0) due to All(src/main.rs:15:5: 15:6 (#0)) (None) (OpaqueType) (ConstraintSccIndex(1): ConstraintSccIndex(1))

Here '?3 is equal to '?4, but the reason why it's in the opaque is that it's related to '?4. With my PR this will be correctly tracked so we end up with

('?2: '?5) due to Single(bb0[5]) (None) (Boring) (ConstraintSccIndex(1): ConstraintSccIndex(1)),
('?5: '?3) due to Single(bb0[6]) (None) (Boring) (ConstraintSccIndex(1): ConstraintSccIndex(1)),
('?3: '?4) due to Single(bb0[6]) (None) (Assignment) (ConstraintSccIndex(1): ConstraintSccIndex(1)),
('?4: '?0) due to All(src/main.rs:15:5: 15:6 (#0)) (None) (OpaqueType) (ConstraintSccIndex(1): ConstraintSccIndex(1)),

This additional Assignment step then worsens the error message as we stop talking about the fact that the closures is returned from the function. Fixing this is hard. I've looked into this and it's making me sad :< Properly handling this requires some deeper changes to MIR borrowck diagnostics and that seems like too much for this PR. Given that this only impacts a single test, it seems acceptable to me.

r? @ghost

@rustbot rustbot added A-CI Area: Our Github Actions CI A-testsuite Area: The testsuite used to check the correctness of rustc S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-bootstrap Relevant to the bootstrap subteam: Rust's build system (x.py and src/bootstrap) T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-infra Relevant to the infrastructure team, which will review and decide on the PR/issue. labels Aug 11, 2025
@lcnr lcnr force-pushed the handle-opaque-types-before-region-inference branch from 0d7a748 to e276bbf Compare August 11, 2025 11:15
for error in errors {
guar = Some(match error {
DeferredOpaqueTypeError::InvalidOpaqueTypeArgs(err) => err.report(self.infcx),
DeferredOpaqueTypeError::LifetimeMismatchOpaqueParam(err) => {
self.infcx.dcx().emit_err(err)
}
DeferredOpaqueTypeError::UnexpectedHiddenRegion {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just moved from RegionErrorKind::UnexpectedHiddenRegion

@lcnr lcnr force-pushed the handle-opaque-types-before-region-inference branch 3 times, most recently from 14e5264 to 5536a31 Compare August 11, 2025 11:29
@rust-log-analyzer

This comment has been minimized.

@lcnr lcnr force-pushed the handle-opaque-types-before-region-inference branch from 5536a31 to 298629b Compare August 11, 2025 11:55
@rust-log-analyzer

This comment has been minimized.

@lcnr lcnr force-pushed the handle-opaque-types-before-region-inference branch from 298629b to 9af9a2c Compare August 11, 2025 12:17
@rust-log-analyzer

This comment has been minimized.

@lcnr lcnr changed the title Handle opaque types before region inference support revealing uses of opaques in borrowck Aug 11, 2025
// FIXME(-Znext-solver): enable this test
//@ revisions: current next
//@ ignore-compare-mode-next-solver (explicit revisions)
//@[next] compile-flags: -Znext-solver
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idk why I've added this FIXME to these 3 tests, only one of them actually failed with the new solver before.

@BoxyUwU BoxyUwU self-assigned this Aug 11, 2025
@lcnr lcnr changed the title support revealing uses of opaques in borrowck support non-defining uses of opaques in borrowck Aug 11, 2025
rcx.scc_values.add_region(member, min_choice_scc);
}

struct CollectMemberConstraintsVisitor<'a, 'b, 'tcx> {
Copy link
Contributor Author

@lcnr lcnr Aug 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

equiv to existing register_member_constraints

@lcnr lcnr force-pushed the handle-opaque-types-before-region-inference branch 3 times, most recently from 9a309c7 to 30579d4 Compare August 12, 2025 10:30
@BoxyUwU BoxyUwU added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-types Relevant to the types team, which will review and decide on the PR/issue. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-infra Relevant to the infrastructure team, which will review and decide on the PR/issue. A-CI Area: Our Github Actions CI labels Aug 16, 2025
@lcnr lcnr force-pushed the handle-opaque-types-before-region-inference branch from d992c6e to e095c14 Compare August 18, 2025 07:52
@rustbot

This comment has been minimized.

@lcnr lcnr force-pushed the handle-opaque-types-before-region-inference branch 3 times, most recently from 294a8b7 to 86063b3 Compare August 18, 2025 13:27
@lcnr
Copy link
Contributor Author

lcnr commented Aug 18, 2025

@bors r=BoxyUwU rollup=never

@bors
Copy link
Collaborator

bors commented Aug 18, 2025

📌 Commit 86063b3 has been approved by BoxyUwU

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Aug 18, 2025
bors added a commit that referenced this pull request Aug 20, 2025
…nce, r=BoxyUwU

support non-defining uses of opaques in borrowck

Reimplements the first part of #139587, but limited to only the new solver. To do so I also entirely rewrite the way we handle opaque types in borrowck even on stable. This should not impact behavior however.

We now support revealing uses during MIR borrowck with the new solver:
```rust
fn foo<'a>(x: &'a u32) -> impl Sized + use<'a> {
    let local = 1;
    foo::<'_>(&local);
    x
}
```

### How do opaque types work right now

Whenever we use an opaque type during type checking, we remember this use in the `opaque_type_storage` of the `InferCtxt`.

Right now, we collect all *member constraints* at the end of MIR type check by looking at all uses from the `opaque_type_storage`. We then apply these constraints while computing the region values for each SCC. This does not add actual region constraints but directly updates the final region values.

This means we need to manually handle any constraints from member constraints for diagnostics. We do this by separately tracking `applied_member_constraints` in the `RegionInferenceContext`.

After we've finished computing the region values, it is now immutable and we check whether all member constraints hold. If not, we error.

We now map the hidden types of our defining uses to the defining scope. This assumes that all member constraints apply. To handle non-member regions, we simply map any region in the hidden type we fail to map to a choice region to `'erased` https://github.com/rust-lang/rust/blob/b1b26b834d85e84b46aa8f8f3ce210a1627aa85f/compiler/rustc_borrowck/src/region_infer/opaque_types.rs#L126-L132

### How do we handle opaque types with this PR

MIR type check still works the same by populating the `opaque_type_storage` whenever we use an opaque type.

We now have a new step `fn handle_opaque_type_uses` which happens between MIR type check and `compute_regions`.

This step looks at all opaque type uses in the storage and first checks whether they are defining: are the arguments of the `opaque_type_key` unique region parameters. *With the new solver we silently ignore any *non-defining* uses here. The old solver emits an errors.*

`fn compute_concrete_opaque_types`: We then collect all member constraints for the defining uses and apply them just like we did before. However, we do it on a temporary region graph which is only used while computing the concrete opaque types. We then use this region graph to compute the concrete type which we then store in the `root_cx`.

`fn apply_computed_concrete_opaque_types`: Now that we know the final concrete type of each opaque type and have mapped them to the definition of the opaque. We iterate over all opaque type uses and equate their hidden type with the instantiated final concrete type. This is the step which actually mutates the region graph.

The actual region checking can now entirely ignores opaque types (outside of the `ConstraintCategory` from checking the opaque type uses).

### Diagnostics issue (chill)

Because we now simply use type equality to "apply member constraints" we get ordinary `OutlivesConstraint`s, even if the regions were already related to another.

This is generally not an issue, expect that it can *hide* the actual region constraints which resulted in the final value of the opaque. The constraints we get from checking against the final opaque type definition relies on constraints we used to compute that definition.

I mostly handle this by updating `find_constraint_path_between_regions` to first ignore member constraints in its search and only if that does not find a path, retry while considering member constraints.

### Diagnostics issue (not chill)

A separate issue is that `find_constraint_paths_between_regions` currently looks up member constraints by their **scc**, not by region value:
https://github.com/rust-lang/rust/blob/2c1ac85679678dfe5cce7ea8037735b0349ceaf3/compiler/rustc_borrowck/src/region_infer/mod.rs#L1768-L1775

This means that in the `borrowck-4` test, the resulting constraint path is currently
```
('?2: '?5) due to Single(bb0[5]) (None) (Boring) (ConstraintSccIndex(1): ConstraintSccIndex(1)),
('?5: '?3) due to Single(bb0[6]) (None) (Boring) (ConstraintSccIndex(1): ConstraintSccIndex(1)),
('?3: '?0) due to All(src/main.rs:15:5: 15:6 (#0)) (None) (OpaqueType) (ConstraintSccIndex(1): ConstraintSccIndex(1))
```
Here `'?3` is equal to `'?4`, but the reason why it's in the opaque is that it's related to `'?4`. With my PR this will be correctly tracked so we end up with
```
('?2: '?5) due to Single(bb0[5]) (None) (Boring) (ConstraintSccIndex(1): ConstraintSccIndex(1)),
('?5: '?3) due to Single(bb0[6]) (None) (Boring) (ConstraintSccIndex(1): ConstraintSccIndex(1)),
('?3: '?4) due to Single(bb0[6]) (None) (Assignment) (ConstraintSccIndex(1): ConstraintSccIndex(1)),
('?4: '?0) due to All(src/main.rs:15:5: 15:6 (#0)) (None) (OpaqueType) (ConstraintSccIndex(1): ConstraintSccIndex(1)),
```
This additional `Assignment` step then worsens the error message as we stop talking about the fact that the closures is returned from the function. Fixing this is hard. I've looked into this and it's making me sad :< Properly handling this requires some deeper changes to MIR borrowck diagnostics and that seems like too much for this PR. Given that this only impacts a single test, it seems acceptable to me.

r? `@ghost`
@bors
Copy link
Collaborator

bors commented Aug 20, 2025

⌛ Testing commit 86063b3 with merge c57053d...

@rust-log-analyzer

This comment has been minimized.

@bors
Copy link
Collaborator

bors commented Aug 20, 2025

💔 Test failed - checks-actions

@bors bors added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Aug 20, 2025
@lcnr lcnr force-pushed the handle-opaque-types-before-region-inference branch from 86063b3 to c2a0fa8 Compare August 20, 2025 09:10
@rustbot
Copy link
Collaborator

rustbot commented Aug 20, 2025

This PR was rebased onto a different master 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.

@lcnr
Copy link
Contributor Author

lcnr commented Aug 20, 2025

@bors r=BoxyUwU

@bors
Copy link
Collaborator

bors commented Aug 20, 2025

📌 Commit c2a0fa8 has been approved by BoxyUwU

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Aug 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
perf-regression Performance regression. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants