Skip to content

trait_selection: Preserve eager normalization failures - #162618

Merged
rust-bors[bot] merged 1 commit into
rust-lang:mainfrom
Dnreikronos:trait_solver_preserve_normalization_failure
Oct 2, 2026
Merged

rust-bors[bot] merged 1 commit into
rust-lang:mainfrom
Dnreikronos:trait_solver_preserve_normalization_failure

Conversation

@Dnreikronos

@Dnreikronos Dnreikronos commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #160875

This started from #161407 and the Zulip discussion around eager normalization failures.

The nested inherent associated type case takes two paths. Eager normalization handles the inner alias first, then the outer alias fails the leak check. The old fallback throws that attempt away and retries the original aliases. That retry can succeed because the lifetime constraints come back through nested goals and the parent leak check does not see them.

My first take was to resolve the inference variable after fulfillment. That made the debug assertion go away, but it was only hiding the failed relation. I do not think we should return a value from a path that already failed the leak check.

normalize_with_universes now returns the failed obligation. Deep normalization passes that failure back instead of falling into recovery. Regular normalize stays infallible and keeps the fallback behavior because its callers already expect obligations.

The fallback still has to rebuild those obligations. For higher ranked aliases, ReplaceAliasWithInfer leaves the alias in the folded value, replaces bound vars with placeholders only for the Projection obligation, and does not put the fresh inference term in the returned type. I missed that part in my first version and dropped too much.

I think this is the cleanest split. Deep normalization gets the strict behavior it promises, while regular normalization still recovers in the same general way as before.

The UI tests cover the nested IAT case and the affected diagnostics.

@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 Sep 10, 2026
@rust-log-analyzer

This comment has been minimized.

@lcnr

lcnr commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

sgtm, this is now 2 unrelated changes, is it?

  • we change ReplaceAliasesWithInfer to keep hr aliases around
  • we change the inner normalize to return Err

I think we should do both. Generally try to only do changes in the same PR if they build on each other

@lcnr

lcnr commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

ah, with the ReplaceAliasesWithInfer. I think we still want to emit a Projection obligation, just not return an infer var 🤔

@Dnreikronos

Copy link
Copy Markdown
Contributor Author

sgtm, this is now 2 unrelated changes, is it?

  • we change ReplaceAliasesWithInfer to keep hr aliases around
  • we change the inner normalize to return Err

I think we should do both. Generally try to only do changes in the same PR if they build on each other

Yeah, I can split them. My thinking was that once we kept the eager failure, we already had an error to report, so I dropped the extra obligations for higher-ranked aliases in the fallback. That's why I bundled the two changes. I'd prefer to keep this one focused on normalize_with_universes returning Err and work through the visitor behavior separately.

ah, with the ReplaceAliasesWithInfer. I think we still want to emit a Projection obligation, just not return an infer var 🤔

yep, I removed too much there. I figured keeping the eager failure gave us the error we needed, so I dropped the higher-ranked projection obligations during recovery ;/. But that first failure doesn't cover every alias the visitor walks. I'll keep emitting the Projection obligation while leaving the alias in the returned type. I think handling that separately will make it easier to reason about, so I'll keep this PR focused on normalize_with_universes returning Err.
Sounds good for you?

@lcnr

lcnr commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

My thinking was that once we kept the eager failure, we already had an error to report, so I dropped the extra obligations for higher-ranked aliases in the fallback.

we entirely drop all failed obligations from the first normalizaiton attempt, don't we?

let mut replacer = ReplaceAliasWithInfer { at, obligations: Default::default(), universes };
let value = original_value.fold_with(&mut replacer);
Normalized { value, obligations: replacer.obligations }

so to make sure we actually error, ReplaceRegionsWithInfer needs to also add a Projection obligation which doesn't hold

@rust-log-analyzer

This comment has been minimized.

Return failed projection obligations from deep normalization instead of
retrying aliases through infallible fallback. Keep regular normalization
recovery by rebuilding projection obligations from the original aliases.
@Dnreikronos
Dnreikronos force-pushed the trait_solver_preserve_normalization_failure branch from a6771bf to 26be04b Compare September 19, 2026 15:31
@Dnreikronos

Copy link
Copy Markdown
Contributor Author

My thinking was that once we kept the eager failure, we already had an error to report, so I dropped the extra obligations for higher-ranked aliases in the fallback.

we entirely drop all failed obligations from the first normalizaiton attempt, don't we?

let mut replacer = ReplaceAliasWithInfer { at, obligations: Default::default(), universes };
let value = original_value.fold_with(&mut replacer);
Normalized { value, obligations: replacer.obligations }

so to make sure we actually error, ReplaceRegionsWithInfer needs to also add a Projection obligation which doesn't hold

Yep, I was mixing up two paths here.

For deep normalization we want to return the eager failure. For the infallible normalize path, though, the first failed attempt is dropped and the fallback has to recreate the obligations from the original aliases. I changed it back to work that way.

ReplaceAliasWithInfer now keeps a higher-ranked alias in the folded value, but it still replaces its bound vars with placeholders and registers a Projection obligation. The fresh inference term only belongs to that obligation. It never becomes the returned type. I did the same for const aliases.

I think this split makes more sense. Deep normalization gets a real error instead of hiding it, while regular normalization keeps its recovery behavior and lets fulfillment report the projection failure. It also gets rid of the duplicate diagnostic we were seeing.

@Dnreikronos
Dnreikronos marked this pull request as ready for review September 25, 2026 14:33
@Dnreikronos

Dnreikronos commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor Author

hey @lcnr, I did the split you asked for, so here's where this ended up

the visitor is back to what main does. in my first version I dropped the projection obligations for higher ranked aliases in the fallback, because I thought the eager failure was already enough to get an error. but like you said, the failed obligations from the first attempt get thrown away, so without that obligation nothing actually errors. so I put it back. ReplaceAliasWithInfer keeps the higher ranked alias in the returned type, swaps the bound vars for placeholders and registers the Projection obligation, and the fresh infer var only lives inside that obligation

the only real change left is normalize_with_universes returning a Result with the failed obligation. deeply_normalize just passes that error up instead of going into the fallback. plain normalize still can't fail, so when it gets an error it does the same fallback as before and lets fulfillment report it

why I went this way.. in the nested IAT case the eager attempt fails the leak check on the outer alias, and the old fallback retried the original aliases and got away with it because the lifetime stuff ends up in nested goals the parent leak check never sees. that's where the fn(?3t) came from. resolving vars after fulfillment (what I did in #161407) made the assert go away, but it was just hiding a relation that already failed, so I don't think we should return anything from that path

I think this is the cleanest split. deep normalization gets the strict behavior it says it has, and regular normalization keeps recovering like it always did. it also got rid of the duplicate diagnostic we had. one thing I'm not super happy with is the new error on the next solver, it says "type mismatch resolving" which is less clear than the old solver's "one type is more general than the other", but I'd rather not mess with diagnostics in this PR

also, earlier you said the visitor leaks placeholders. do you want a separate PR for that, or is keeping the Projection obligation enough for now?

r? lcnr
@rustbot ready

@rustbot

rustbot commented Sep 25, 2026

Copy link
Copy Markdown
Collaborator

Some changes occurred to the core trait solver

cc @rust-lang/initiative-trait-system-refactor

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Sep 25, 2026
@rustbot

rustbot commented Sep 25, 2026

Copy link
Copy Markdown
Collaborator

r? @adwinwhite

rustbot has assigned @adwinwhite.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: compiler
  • compiler expanded to 77 candidates
  • Random selection from 21 candidates

@lcnr

lcnr commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

@bors r+ rollup

@rust-bors

rust-bors Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

📌 Commit 26be04b has been approved by lcnr

It is now in the queue for this repository.

@rust-bors rust-bors Bot 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 Oct 1, 2026
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Oct 1, 2026
…normalization_failure, r=lcnr

trait_selection: Preserve eager normalization failures

Fixes rust-lang#160875

This started from rust-lang#161407 and the Zulip discussion around eager normalization failures.

The nested inherent associated type case takes two paths. Eager normalization handles the inner alias first, then the outer alias fails the leak check. The old fallback throws that attempt away and retries the original aliases. That retry can succeed because the lifetime constraints come back through nested goals and the parent leak check does not see them.

My first take was to resolve the inference variable after fulfillment. That made the debug assertion go away, but it was only hiding the failed relation. I do not think we should return a value from a path that already failed the leak check.

`normalize_with_universes` now returns the failed obligation. Deep normalization passes that failure back instead of falling into recovery. Regular `normalize` stays infallible and keeps the fallback behavior because its callers already expect obligations.

The fallback still has to rebuild those obligations. For higher ranked aliases, `ReplaceAliasWithInfer` leaves the alias in the folded value, replaces bound vars with placeholders only for the `Projection` obligation, and does not put the fresh inference term in the returned type. I missed that part in my first version and dropped too much.

I think this is the cleanest split. Deep normalization gets the strict behavior it promises, while regular normalization still recovers in the same general way as before.

The UI tests cover the nested IAT case and the affected diagnostics.
rust-bors Bot pushed a commit that referenced this pull request Oct 1, 2026
…uwer

Rollup of 9 pull requests

Successful merges:

 - #163461 (Improve diagnostic deduplication)
 - #159021 (windows-gnu: enable native TLS)
 - #161467 (wfcheck: name the item that discards an unused type parameter)
 - #162618 (trait_selection: Preserve eager normalization failures)
 - #163064 (Avoid computing overflowed goal chains for crate dependencies)
 - #163360 ([rustdoc] Correctly handle rustc_allow_incoherent_impl on primitive methods)
 - #163385 (GVN transmutes of Immediate::Uninit to Immediate::Uninit)
 - #163590 (Make `AllocatorNightly` less clever)
 - #163599 (Add union pattern reference change to relnotes)
rust-bors Bot pushed a commit that referenced this pull request Oct 1, 2026
…uwer

Rollup of 20 pull requests

Successful merges:

 - #163483 (Bump bootstrap compiler to 1.100.0 beta)
 - #161380 (only rerun const eval in next-solver if the const actually references opaques)
 - #162900 (Some refactorings around metadata encoding)
 - #163461 (Improve diagnostic deduplication)
 - #163580 (Provide better doc code example for `UnixDatagram::bind_addr` and `UnixListener::bind_addr`)
 - #163584 ([triagebot] Ping me for debugger visualizer changes)
 - #159021 (windows-gnu: enable native TLS)
 - #161467 (wfcheck: name the item that discards an unused type parameter)
 - #162618 (trait_selection: Preserve eager normalization failures)
 - #162904 (Fix ICE for ambiguous candidates on method probing)
 - #163064 (Avoid computing overflowed goal chains for crate dependencies)
 - #163281 (Add `f16` inline ASM support to `spirv.rs`)
 - #163314 (move `#[macro_export]` on declarative macro check to `rustc_attr_parsing`)
 - #163360 ([rustdoc] Correctly handle rustc_allow_incoherent_impl on primitive methods)
 - #163385 (GVN transmutes of Immediate::Uninit to Immediate::Uninit)
 - #163405 (Remove some #[linkage] options)
 - #163530 (`const impl PartialEq` for `f16b`)
 - #163581 (do not suggest precise capturing when the opaque span is in a macro expansion)
 - #163590 (Make `AllocatorNightly` less clever)
 - #163599 (Add union pattern reference change to relnotes)
rust-bors Bot pushed a commit that referenced this pull request Oct 1, 2026
…uwer

Rollup of 20 pull requests

Successful merges:

 - #163483 (Bump bootstrap compiler to 1.100.0 beta)
 - #161380 (only rerun const eval in next-solver if the const actually references opaques)
 - #162900 (Some refactorings around metadata encoding)
 - #163461 (Improve diagnostic deduplication)
 - #163580 (Provide better doc code example for `UnixDatagram::bind_addr` and `UnixListener::bind_addr`)
 - #163584 ([triagebot] Ping me for debugger visualizer changes)
 - #159021 (windows-gnu: enable native TLS)
 - #161467 (wfcheck: name the item that discards an unused type parameter)
 - #162618 (trait_selection: Preserve eager normalization failures)
 - #162904 (Fix ICE for ambiguous candidates on method probing)
 - #163064 (Avoid computing overflowed goal chains for crate dependencies)
 - #163281 (Add `f16` inline ASM support to `spirv.rs`)
 - #163314 (move `#[macro_export]` on declarative macro check to `rustc_attr_parsing`)
 - #163360 ([rustdoc] Correctly handle rustc_allow_incoherent_impl on primitive methods)
 - #163385 (GVN transmutes of Immediate::Uninit to Immediate::Uninit)
 - #163405 (Remove some #[linkage] options)
 - #163530 (`const impl PartialEq` for `f16b`)
 - #163581 (do not suggest precise capturing when the opaque span is in a macro expansion)
 - #163590 (Make `AllocatorNightly` less clever)
 - #163599 (Add union pattern reference change to relnotes)
rust-bors Bot pushed a commit that referenced this pull request Oct 2, 2026
…uwer

Rollup of 20 pull requests

Successful merges:

 - #163483 (Bump bootstrap compiler to 1.100.0 beta)
 - #161380 (only rerun const eval in next-solver if the const actually references opaques)
 - #162900 (Some refactorings around metadata encoding)
 - #163461 (Improve diagnostic deduplication)
 - #163580 (Provide better doc code example for `UnixDatagram::bind_addr` and `UnixListener::bind_addr`)
 - #163584 ([triagebot] Ping me for debugger visualizer changes)
 - #159021 (windows-gnu: enable native TLS)
 - #161467 (wfcheck: name the item that discards an unused type parameter)
 - #162618 (trait_selection: Preserve eager normalization failures)
 - #162904 (Fix ICE for ambiguous candidates on method probing)
 - #163064 (Avoid computing overflowed goal chains for crate dependencies)
 - #163281 (Add `f16` inline ASM support to `spirv.rs`)
 - #163314 (move `#[macro_export]` on declarative macro check to `rustc_attr_parsing`)
 - #163360 ([rustdoc] Correctly handle rustc_allow_incoherent_impl on primitive methods)
 - #163385 (GVN transmutes of Immediate::Uninit to Immediate::Uninit)
 - #163405 (Remove some #[linkage] options)
 - #163530 (`const impl PartialEq` for `f16b`)
 - #163581 (do not suggest precise capturing when the opaque span is in a macro expansion)
 - #163590 (Make `AllocatorNightly` less clever)
 - #163599 (Add union pattern reference change to relnotes)
@rust-bors
rust-bors Bot merged commit 2524bcd into rust-lang:main Oct 2, 2026
13 checks passed
@rustbot rustbot added this to the 1.101.0 milestone Oct 2, 2026
rust-bors Bot pushed a commit that referenced this pull request Oct 2, 2026
Rollup merge of #162618 - Dnreikronos:trait_solver_preserve_normalization_failure, r=lcnr

trait_selection: Preserve eager normalization failures

Fixes #160875

This started from #161407 and the Zulip discussion around eager normalization failures.

The nested inherent associated type case takes two paths. Eager normalization handles the inner alias first, then the outer alias fails the leak check. The old fallback throws that attempt away and retries the original aliases. That retry can succeed because the lifetime constraints come back through nested goals and the parent leak check does not see them.

My first take was to resolve the inference variable after fulfillment. That made the debug assertion go away, but it was only hiding the failed relation. I do not think we should return a value from a path that already failed the leak check.

`normalize_with_universes` now returns the failed obligation. Deep normalization passes that failure back instead of falling into recovery. Regular `normalize` stays infallible and keeps the fallback behavior because its callers already expect obligations.

The fallback still has to rebuild those obligations. For higher ranked aliases, `ReplaceAliasWithInfer` leaves the alias in the folded value, replaces bound vars with placeholders only for the `Projection` obligation, and does not put the fresh inference term in the returned type. I missed that part in my first version and dropped too much.

I think this is the cleanest split. Deep normalization gets the strict behavior it promises, while regular normalization still recovers in the same general way as before.

The UI tests cover the nested IAT case and the affected diagnostics.
flip1995 pushed a commit to flip1995/rust-clippy that referenced this pull request Oct 3, 2026
…uwer

Rollup of 20 pull requests

Successful merges:

 - rust-lang/rust#163483 (Bump bootstrap compiler to 1.100.0 beta)
 - rust-lang/rust#161380 (only rerun const eval in next-solver if the const actually references opaques)
 - rust-lang/rust#162900 (Some refactorings around metadata encoding)
 - rust-lang/rust#163461 (Improve diagnostic deduplication)
 - rust-lang/rust#163580 (Provide better doc code example for `UnixDatagram::bind_addr` and `UnixListener::bind_addr`)
 - rust-lang/rust#163584 ([triagebot] Ping me for debugger visualizer changes)
 - rust-lang/rust#159021 (windows-gnu: enable native TLS)
 - rust-lang/rust#161467 (wfcheck: name the item that discards an unused type parameter)
 - rust-lang/rust#162618 (trait_selection: Preserve eager normalization failures)
 - rust-lang/rust#162904 (Fix ICE for ambiguous candidates on method probing)
 - rust-lang/rust#163064 (Avoid computing overflowed goal chains for crate dependencies)
 - rust-lang/rust#163281 (Add `f16` inline ASM support to `spirv.rs`)
 - rust-lang/rust#163314 (move `#[macro_export]` on declarative macro check to `rustc_attr_parsing`)
 - rust-lang/rust#163360 ([rustdoc] Correctly handle rustc_allow_incoherent_impl on primitive methods)
 - rust-lang/rust#163385 (GVN transmutes of Immediate::Uninit to Immediate::Uninit)
 - rust-lang/rust#163405 (Remove some #[linkage] options)
 - rust-lang/rust#163530 (`const impl PartialEq` for `f16b`)
 - rust-lang/rust#163581 (do not suggest precise capturing when the opaque span is in a macro expansion)
 - rust-lang/rust#163590 (Make `AllocatorNightly` less clever)
 - rust-lang/rust#163599 (Add union pattern reference change to relnotes)
LorrensP-2158466 pushed a commit to LorrensP-2158466/miri that referenced this pull request Oct 6, 2026
…uwer

Rollup of 20 pull requests

Successful merges:

 - rust-lang/rust#163483 (Bump bootstrap compiler to 1.100.0 beta)
 - rust-lang/rust#161380 (only rerun const eval in next-solver if the const actually references opaques)
 - rust-lang/rust#162900 (Some refactorings around metadata encoding)
 - rust-lang/rust#163461 (Improve diagnostic deduplication)
 - rust-lang/rust#163580 (Provide better doc code example for `UnixDatagram::bind_addr` and `UnixListener::bind_addr`)
 - rust-lang/rust#163584 ([triagebot] Ping me for debugger visualizer changes)
 - rust-lang/rust#159021 (windows-gnu: enable native TLS)
 - rust-lang/rust#161467 (wfcheck: name the item that discards an unused type parameter)
 - rust-lang/rust#162618 (trait_selection: Preserve eager normalization failures)
 - rust-lang/rust#162904 (Fix ICE for ambiguous candidates on method probing)
 - rust-lang/rust#163064 (Avoid computing overflowed goal chains for crate dependencies)
 - rust-lang/rust#163281 (Add `f16` inline ASM support to `spirv.rs`)
 - rust-lang/rust#163314 (move `#[macro_export]` on declarative macro check to `rustc_attr_parsing`)
 - rust-lang/rust#163360 ([rustdoc] Correctly handle rustc_allow_incoherent_impl on primitive methods)
 - rust-lang/rust#163385 (GVN transmutes of Immediate::Uninit to Immediate::Uninit)
 - rust-lang/rust#163405 (Remove some #[linkage] options)
 - rust-lang/rust#163530 (`const impl PartialEq` for `f16b`)
 - rust-lang/rust#163581 (do not suggest precise capturing when the opaque span is in a macro expansion)
 - rust-lang/rust#163590 (Make `AllocatorNightly` less clever)
 - rust-lang/rust#163599 (Add union pattern reference change to relnotes)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

[ICE]: assertion failed: Binder == Binder

5 participants