Skip to content

move implied bounds computation out of borrowck - #160491

Open
lcnr wants to merge 6 commits into
rust-lang:mainfrom
lcnr:implied-bounds-opaque
Open

move implied bounds computation out of borrowck#160491
lcnr wants to merge 6 commits into
rust-lang:mainfrom
lcnr:implied-bounds-opaque

Conversation

@lcnr

@lcnr lcnr commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

View all comments

First 3 commits don't change behavior and I moved them into #160504.


@tiif did the initial implementation work in #152051. This ended up being more involved than I originally expected, so I ended up finishing this PR after spending a few days on it myself.

Computing implied bounds now happens in a new query mir_borrowck_implied_outlives_bounds which does two things differently from MIR borrowck:

  • we use param and placeholder regions instead of NLL vars
  • if we're a typeck root we don't reveal any opaque types

Using param and placeholder regions instead of NLL vars

This fixes #106569. We previously computed the implied bounds using ty::ReVar even for universal variables, which meant that resolving them can drop constraints in borrowck.

As explained in #106569 (comment)

what's happening here is that normalizing <Equal<'a, 'b> as Trait>::Ty requires 'a and 'b to be equal. We represent 'a and 'b as region inference variables right now.

This means the 'a == 'b constraint does not get returned as region outlives constraints, but we instead just unify the two existentials. The implied bound 'a == 'b then ends up as 'a == 'a which means in the caller, we can't reconstruct that it's actually about 'a and 'b.

Computing implied bounds now uses universal variables instead of ReVar, fixing this issue.

Do not reveal the hidden type of opaques for typeck roots

This fixes rust-lang/trait-system-refactor-initiative#159 with the new trait solver.

trait Extend<'a, 'b> {
    fn extend(self, _: &'a str) -> &'b str;
}
impl<'a, 'b> Extend<'a, 'b> for Option<&'b &'a ()> {
    fn extend(self, s: &'a str) -> &'b str {
        s
    }
}

fn boom<'a, 'b>() -> impl Extend<'a, 'b> {
    None::<&'b &'a ()>
}

fn main() {
    let y = boom().extend(&String::from("temporary"));
    println!("{}", y);
}

Computing the implied bounds for boom previously revealed the hidden type of impl Extend<'a, 'b> giving us a 'a: 'b implied bound. Calling boom cannot reveal the opaque type as it's outside of the defining scope, so the caller never has to prove that outlives requirement.

We do still reveal opaque types when computing the implied bounds for nested bodies! This is subtle and I nearly missed this. For nested bodies, they are only ever used inside of their parent function, which is able to define the same opaque types. We never check that e.g. a closure is well-formed outside of the parent body.

This means trying to compute implied bounds for closures without defining opaque types can result in incorrect errors, see tests/ui/traits/next-solver/opaques/implied-bounds-opaque-hidden-in-closure-sig.rs:

trait Trait {
    type Assoc;
}
impl Trait for () {
    type Assoc = ();
}

trait Func {
    type Output;
}
impl<F: FnOnce() -> R, R> Func for F {
    type Output = R;
}

struct RequiresWf<F>(F)
where
    F: Func,
    F::Output: Trait,
    <F::Output as Trait>::Assoc: 'static;

fn opaque() -> impl Sized {
    // Closure signature is `fn(RequiresWf<fn_def>)` and we need to
    // normalize the RPIT for the where-clauses of the argument to hold.
    (|_| ())(RequiresWf(opaque));
}

Implementation details and nuances

var_values

Returning implied bounds and canonicalization. Figuring out how to do so was quite challenging. The main question is how to link regions from the query to the correct regions in MIR borrowck. The way to do so is via var_values.

As we're using old style canonicalizing we keep early and late bound parameters around, so these don't have to be part of the var_values. We do need to link regions from the closure signature in the query to the regions in the signature used in MIR borrowck. We do this by going over the signature and collecting all regions we find in the var_values. The query uses placeholders for these while MIR borrowck uses external NLL vars for them.

Normalizing the signature and unconstrained region vars

Normalizing a function signature can result in unconstrained existential regions due to #136547. Types involving these regions can be relevant for implied bounds. Using such type outlives bounds relies on structural equality. If we separately normalize the signature two times, once in borrowck and once in the implied bounds query, we get different unconstrained region vars, breaking the gluon_salsa test.

To avoid this, mir_borrowck_implied_outlives_bounds normalizes the signature without revealing opaque types and returns its result to MIR borrowck. MIR borrowck now renormalizes this signature to also correctly normalize opaque types.

The bevy implied bounds hack

This PR keeps the current behavior of #119956 while somewhat changing the actual implementation.

We continue to consider constraints from computing implied bounds as implied bounds only for arguments whose type mentions bevy_ecs::ParamSet.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver) labels Aug 4, 2026
@lcnr

lcnr commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@bors try @rust-timer queue

@rust-timer

This comment has been minimized.

@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Aug 4, 2026
@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Aug 4, 2026
move implied bounds computation out of borrowck
@lcnr
lcnr force-pushed the implied-bounds-opaque branch 2 times, most recently from 48a8766 to 5c0c099 Compare August 4, 2026 09:44
@lcnr
lcnr force-pushed the implied-bounds-opaque branch 3 times, most recently from 6eb8247 to 1dd6c27 Compare August 4, 2026 10:01
@lcnr

lcnr commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@bors cancel

@bors try @rust-timer queue

@rust-timer

This comment has been minimized.

@rust-bors

rust-bors Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

❗ There is currently no auto build in progress on this PR.

Hint: There is a pending try build on this PR. Maybe you meant to cancel it? You can do that using @bors try cancel.

@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Aug 4, 2026
move implied bounds computation out of borrowck
@lcnr

lcnr commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@bors try @rust-timer queue

@rust-timer

This comment has been minimized.

@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Aug 4, 2026
move implied bounds computation out of borrowck
@lcnr
lcnr marked this pull request as ready for review August 4, 2026 12:17
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Aug 4, 2026
@rust-bors

rust-bors Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

☀️ Try build successful (CI)
Build commit: 615014f (615014f15745532730b9ce7a1d9309f8b4756f93)
Base parent: c9ff496 (c9ff496891c278ad660bc0ab85c1f0b72059464a)

@rust-timer

This comment has been minimized.

@lcnr
lcnr force-pushed the implied-bounds-opaque branch from 70041ae to 9151a02 Compare August 4, 2026 13:52
@lcnr

lcnr commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@craterbot check

@craterbot

Copy link
Copy Markdown
Collaborator

👌 Experiment pr-160491 created and queued.
🤖 Automatically detected try build 615014f
⚠️ Try build based on commit 4b5fc7f, but latest commit is 9151a02. Did you forget to make a new try build?
🔍 You can check out the queue and this experiment's details.

ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more

@craterbot craterbot added S-waiting-on-crater Status: Waiting on a crater run to be completed. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. S-waiting-on-perf Status: Waiting on a perf run to be completed. labels Aug 4, 2026
@rust-timer

Copy link
Copy Markdown
Collaborator

Finished benchmarking commit (615014f): comparison URL.

Overall result: ❌✅ regressions and improvements - please read:

Benchmarking means the PR may be perf-sensitive. It's automatically marked not fit for rolling up. Overriding is possible but disadvised: it risks changing compiler perf.

Next, please: If you can, justify the regressions found in this try perf run in writing along with @rustbot label: +perf-regression-triaged. If not, fix the regressions and do another perf run. Neutral or positive results will clear the label automatically.

@bors rollup=never rustc-perf
@rustbot label: -S-waiting-on-perf +perf-regression

Instruction count

Our most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.

mean range count
Regressions ❌
(primary)
0.4% [0.2%, 0.9%] 40
Regressions ❌
(secondary)
0.7% [0.1%, 1.4%] 27
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-0.5% [-0.9%, -0.2%] 12
All ❌✅ (primary) 0.4% [0.2%, 0.9%] 40

Max RSS (memory usage)

Results (primary 1.3%, secondary -0.5%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
2.0% [0.5%, 4.6%] 3
Regressions ❌
(secondary)
0.6% [0.6%, 0.6%] 1
Improvements ✅
(primary)
-0.7% [-0.7%, -0.7%] 1
Improvements ✅
(secondary)
-0.6% [-1.1%, -0.4%] 16
All ❌✅ (primary) 1.3% [-0.7%, 4.6%] 4

Cycles

Results (primary -0.1%, secondary -0.0%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
1.0% [0.5%, 1.4%] 11
Regressions ❌
(secondary)
1.1% [0.4%, 2.3%] 8
Improvements ✅
(primary)
-2.6% [-7.2%, -0.6%] 5
Improvements ✅
(secondary)
-1.6% [-3.2%, -0.5%] 6
All ❌✅ (primary) -0.1% [-7.2%, 1.4%] 16

Binary size

This perf run didn't have relevant results for this metric.

Bootstrap: 489.838s -> 489.061s (-0.16%)
Artifact size: 390.28 MiB -> 390.68 MiB (0.10%)

@rustbot rustbot added the perf-regression Performance regression. label Aug 4, 2026
@lcnr
lcnr force-pushed the implied-bounds-opaque branch 2 times, most recently from 9151a02 to 73c2b60 Compare August 6, 2026 11:29
@rust-log-analyzer

This comment has been minimized.

@lcnr
lcnr force-pushed the implied-bounds-opaque branch from 73c2b60 to 4dd4689 Compare August 6, 2026 12:20
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Aug 6, 2026
cleanup borrowck, improve c-variadic handling

The first commits of rust-lang#160491. Hopefully all of them make sense.

It feels intuitive to me that the `c-variadic` region should be just another late-bound region and tracking region correctly for rust-lang#160491 is otherwise a mess.

r? types
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Aug 6, 2026
cleanup borrowck, improve c-variadic handling

The first commits of rust-lang#160491. Hopefully all of them make sense.

It feels intuitive to me that the `c-variadic` region should be just another late-bound region and tracking region correctly for rust-lang#160491 is otherwise a mess.

r? types
jhpratt added a commit to jhpratt/rust that referenced this pull request Aug 7, 2026
cleanup borrowck, improve c-variadic handling

The first commits of rust-lang#160491. Hopefully all of them make sense.

It feels intuitive to me that the `c-variadic` region should be just another late-bound region and tracking region correctly for rust-lang#160491 is otherwise a mess.

r? types
jhpratt added a commit to jhpratt/rust that referenced this pull request Aug 7, 2026
cleanup borrowck, improve c-variadic handling

The first commits of rust-lang#160491. Hopefully all of them make sense.

It feels intuitive to me that the `c-variadic` region should be just another late-bound region and tracking region correctly for rust-lang#160491 is otherwise a mess.

r? types

@adwinwhite adwinwhite left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Finally get to understand the several related issues and the fix now (mostly) 😄

View changes since this review

constraints.push(c);
}
}
Err::<_, ErrorGuaranteed>(_) => {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why can we ignore errors here?

Comment on lines +39 to +41
// However, for nested bodies, we always check that they are well-formed in their
// parent body, so for these we do want to define opaque types. Not doing so can result
// in incorrect errors when normalizing implied bounds.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nested bodies are exempted because

  • To have implied bounds from hidden types, they need to have opaques in their signatures.
  • If they have opaques in the signatures, the call to them would add wf obligations for normalized inputs and output thus those implied bounds are still checked in the parent.

Is my understanding correct? 🤔

Comment on lines +122 to +131
let var_values = implied_bounds_query_var_values(tcx, &inputs_and_output, |r| match r.kind() {
ty::RePlaceholder(_) => true,
ty::ReEarlyParam(_)
| ty::ReLateParam(_)
| ty::ReBound(..)
| ty::ReStatic
| ty::ReError(_) => false,
ty::ReVar(..) | ty::ReErased => unreachable!(),
});
let input_values = CanonicalVarValues { var_values: tcx.mk_args(&var_values) };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Confused here 🤔
Placeholders are from erased regions and closure ty has erased regions in signature and upvars.
So these var_values are corresponding to local universals while we compute the original vars from external universals in compute_implied_bounds. Why so?

use crate::traits::query::NoSolution;
use crate::traits::{ObligationCtxt, wf};

impl<'tcx> super::QueryTypeOp<'tcx> for ImpliedOutlivesBounds<'tcx> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ImpliedOutlivesBounds is no longer a type op. Maybe we can cleanup the file path later?

}
}

pub fn query_compute_implied_outlives_bounds<'tcx>(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why don't we worry about the same implied bounds problem in wfcheck? Because mir borrowck would run first if opaques are involved? 🤔

JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Aug 7, 2026
cleanup borrowck, improve c-variadic handling

The first commits of rust-lang#160491. Hopefully all of them make sense.

It feels intuitive to me that the `c-variadic` region should be just another late-bound region and tracking region correctly for rust-lang#160491 is otherwise a mess.

r? types
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Aug 7, 2026
cleanup borrowck, improve c-variadic handling

The first commits of rust-lang#160491. Hopefully all of them make sense.

It feels intuitive to me that the `c-variadic` region should be just another late-bound region and tracking region correctly for rust-lang#160491 is otherwise a mess.

r? types
jhpratt added a commit to jhpratt/rust that referenced this pull request Aug 7, 2026
cleanup borrowck, improve c-variadic handling

The first commits of rust-lang#160491. Hopefully all of them make sense.

It feels intuitive to me that the `c-variadic` region should be just another late-bound region and tracking region correctly for rust-lang#160491 is otherwise a mess.

r? types
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Aug 7, 2026
cleanup borrowck, improve c-variadic handling

The first commits of rust-lang#160491. Hopefully all of them make sense.

It feels intuitive to me that the `c-variadic` region should be just another late-bound region and tracking region correctly for rust-lang#160491 is otherwise a mess.

r? types
@rust-bors

rust-bors Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

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

rust-timer added a commit that referenced this pull request Aug 8, 2026
Rollup merge of #160504 - lcnr:borrowck-cleanup, r=oli-obk

cleanup borrowck, improve c-variadic handling

The first commits of #160491. Hopefully all of them make sense.

It feels intuitive to me that the `c-variadic` region should be just another late-bound region and tracking region correctly for #160491 is otherwise a mess.

r? types
pull Bot pushed a commit to LeeeeeeM/miri that referenced this pull request Aug 8, 2026
cleanup borrowck, improve c-variadic handling

The first commits of rust-lang/rust#160491. Hopefully all of them make sense.

It feels intuitive to me that the `c-variadic` region should be just another late-bound region and tracking region correctly for rust-lang/rust#160491 is otherwise a mess.

r? types
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-crater Status: Waiting on a crater run to be completed. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

normalizing opaques while computing implied bounds implied bounds: lifetime equality lost after normalization

9 participants