Skip to content

Error on recursive opaque ty in HIR typeck #139419

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

Merged
merged 2 commits into from
May 20, 2025

Conversation

compiler-errors
Copy link
Member

@compiler-errors compiler-errors commented Apr 5, 2025

"Non-trivially recursive" opaques are opaques whose hidden types are inferred to be equal to something other than themselves. For example, if we have a TAIT like type TAIT = impl Sized, if we infer the hidden type to be TAIT := (TAIT,), that would be a non-trivial recursive definition. We don't want to support opaques that are non-trivially recursive, since they will (almost!! -- see caveat below) always result in borrowck errors, and are generally a pain to deal with.

On the contrary, trivially recursive opaques may occur today because the old solver overagerly uses replace_opaque_types_with_inference_vars. This infer var can then later be constrained to be equal to the opaque itself. These cases will not necessarily result in borrow-checker errors, since other uses of the opaque may properly constrain the opaque. If there are no other uses we may instead fall back to () today.

The only weird case that we have to unfortunately deal with was discovered in #139406:

#![allow(unconditional_recursion)]

fn what1<T>(x: T) -> impl Sized {
    what1(x)
}

fn what2<T>(x: T) -> impl Sized {
    what2(what2(x))
}

fn print_return_type<T, U>(_: impl Fn(T) -> U) {
    println!("{}", std::any::type_name::<U>())
}

fn main() {
    print_return_type(what1::<i32>); // ()
    print_return_type(what2::<i32>); // i32
}

HIR typeck eagerly replaces the return type with an infer var, ending up with RPIT<T> = RPIT<RPIT<T>> in the storage. While we return this in the TypeckResults, it's never actually used anywhere.

MIR building then results in the following statement

let _0: impl RPIT<T> /* the return place */ = build<RPIT<T>>(_some_local);

Unlike HIR typeck MIR typeck now directly equates RPIT<T> with RPIT<RPIT<T>>. This does not try to define RPIT but instead relates its generic arguments

(
&ty::Alias(ty::Opaque, ty::AliasTy { def_id: a_def_id, .. }),
&ty::Alias(ty::Opaque, ty::AliasTy { def_id: b_def_id, .. }),
) if a_def_id == b_def_id => {
super_combine_tys(infcx, self, a, b)?;
}

This means we relate T with RPIT<T>, which results in a defining use RPIT<T> = T

I think it's pretty obvious that this is not desirable behavior, and according to the crater run there were no regressions, so let's break this so that we don't have any inference hazards in the new solver.

In the future what2 may end up compiling again by also falling back to (). However, that is not yet guaranteed and the transition to this state is made significantly harder by not temporarily breaking it on the way. It is also concerning to change the inferred hidden type like this without any notification to the user, even if likely not an issue in this concrete case.

@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 Apr 5, 2025
@compiler-errors
Copy link
Member Author

compiler-errors commented Apr 5, 2025

@bors try

@rust-timer

This comment was marked as off-topic.

@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Apr 5, 2025
bors added a commit to rust-lang-ci/rust that referenced this pull request Apr 5, 2025
…<try>

Just error on recursive opaque ty in HIR typeck

Opening just to gauge fallout.

r? `@ghost`
@bors
Copy link
Collaborator

bors commented Apr 5, 2025

⌛ Trying commit 99e7ce9 with merge f45c1ab...

@rust-log-analyzer

This comment has been minimized.

@bors
Copy link
Collaborator

bors commented Apr 5, 2025

☀️ Try build successful - checks-actions
Build commit: f45c1ab (f45c1ab62d4de6b63f69b4daa9d1f0b90c38a208)

@rust-timer

This comment has been minimized.

@rust-timer
Copy link
Collaborator

Finished benchmarking commit (f45c1ab): comparison URL.

Overall result: no relevant changes - no action needed

Benchmarking this pull request likely means that it is perf-sensitive, so we're automatically marking it as not fit for rolling up. While you can manually mark this PR as fit for rollup, we strongly recommend not doing so since this PR may lead to changes in compiler perf.

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

Instruction count

This benchmark run did not return any relevant results for this metric.

Max RSS (memory usage)

Results (secondary -2.2%)

This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-2.2% [-2.2%, -2.2%] 1
All ❌✅ (primary) - - 0

Cycles

This benchmark run did not return any relevant results for this metric.

Binary size

This benchmark run did not return any relevant results for this metric.

Bootstrap: 775.534s -> 775.663s (0.02%)
Artifact size: 365.93 MiB -> 365.92 MiB (-0.00%)

@rustbot rustbot removed the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Apr 5, 2025
@compiler-errors
Copy link
Member Author

@craterbot check

@craterbot
Copy link
Collaborator

👌 Experiment pr-139419 created and queued.
🤖 Automatically detected try build f45c1ab
🔍 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. labels Apr 5, 2025
@craterbot
Copy link
Collaborator

🚧 Experiment pr-139419 is now running

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

@craterbot
Copy link
Collaborator

🎉 Experiment pr-139419 is completed!
📊 3 regressed and 2 fixed (610052 total)
📰 Open the full report.

⚠️ If you notice any spurious failure please add them to the denylist!
ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more

@craterbot craterbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-crater Status: Waiting on a crater run to be completed. labels Apr 7, 2025
@lcnr
Copy link
Contributor

lcnr commented Apr 7, 2025

all spurious, can you think of a test case which actually compiled before this PR?

I would expect us to error for them already, just after rustc_borrowck

@lcnr
Copy link
Contributor

lcnr commented Apr 7, 2025

This is a breaking change, see #139406. We should very much break this, explaining what's going on in that issue

@compiler-errors compiler-errors changed the title Just error on recursive opaque ty in HIR typeck Error on recursive opaque ty in HIR typeck Apr 16, 2025
@rust-log-analyzer

This comment has been minimized.

@compiler-errors compiler-errors marked this pull request as ready for review April 28, 2025 20:45
@rustbot
Copy link
Collaborator

rustbot commented Apr 28, 2025

Some changes occurred in exhaustiveness checking

cc @Nadrieril

@compiler-errors compiler-errors removed the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Apr 28, 2025
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rfcbot rfcbot added final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. and removed proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. labels May 6, 2025
@rfcbot
Copy link
Collaborator

rfcbot commented May 6, 2025

🔔 This is now entering its final comment period, as per the review above. 🔔

@bors
Copy link
Collaborator

bors commented May 8, 2025

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

@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this PR / Issue. to-announce Announce this issue on triage meeting and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels May 16, 2025
@rfcbot
Copy link
Collaborator

rfcbot commented May 16, 2025

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

This will be merged soon.

@lcnr
Copy link
Contributor

lcnr commented May 20, 2025

r=me after rebase

@compiler-errors compiler-errors force-pushed the recursive-opaque branch 3 times, most recently from 34f7cbe to 56c206c Compare May 20, 2025 09:38
@rust-log-analyzer

This comment has been minimized.

@lcnr
Copy link
Contributor

lcnr commented May 20, 2025

@bors r+ rollup

@bors
Copy link
Collaborator

bors commented May 20, 2025

📌 Commit 47b9e37 has been approved by lcnr

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 May 20, 2025
bors added a commit to rust-lang-ci/rust that referenced this pull request May 20, 2025
…iaskrgr

Rollup of 7 pull requests

Successful merges:

 - rust-lang#139419 (Error on recursive opaque ty in HIR typeck)
 - rust-lang#141236 (Resolved issue with mismatched types triggering ICE in certain scenarios)
 - rust-lang#141253 (Warning added when dependency crate has async drop types, and the feature is disabled)
 - rust-lang#141269 (rustc-dev-guide subtree update)
 - rust-lang#141275 (`gather_locals`: only visit guard pattern guards when checking the guard)
 - rust-lang#141279 (`lower_to_hir` cleanups)
 - rust-lang#141285 (Add tick to `RePlaceholder` debug output)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit 7e3af74 into rust-lang:master May 20, 2025
6 checks passed
@rustbot rustbot added this to the 1.89.0 milestone May 20, 2025
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request May 20, 2025
Rollup merge of rust-lang#139419 - compiler-errors:recursive-opaque, r=lcnr

Error on recursive opaque ty in HIR typeck

"Non-trivially recursive" opaques are opaques whose hidden types are inferred to be equal to something other than themselves. For example, if we have a TAIT like `type TAIT = impl Sized`, if we infer the hidden type to be `TAIT := (TAIT,)`, that would be a non-trivial recursive definition. We don't want to support opaques that are non-trivially recursive, since they will (almost!! -- see caveat below) always result in borrowck errors, and are generally a pain to deal with.

On the contrary, trivially recursive opaques may occur today because the old solver overagerly uses `replace_opaque_types_with_inference_vars`. This infer var can then later be constrained to be equal to the opaque itself. These cases will not necessarily result in borrow-checker errors, since other uses of the opaque may properly constrain the opaque. If there are no other uses we may instead fall back to `()` today.

The only weird case that we have to unfortunately deal with was discovered in rust-lang#139406:

```rust
#![allow(unconditional_recursion)]

fn what1<T>(x: T) -> impl Sized {
    what1(x)
}

fn what2<T>(x: T) -> impl Sized {
    what2(what2(x))
}

fn print_return_type<T, U>(_: impl Fn(T) -> U) {
    println!("{}", std::any::type_name::<U>())
}

fn main() {
    print_return_type(what1::<i32>); // ()
    print_return_type(what2::<i32>); // i32
}
```

> HIR typeck eagerly replaces the return type with an infer var, ending up with `RPIT<T> = RPIT<RPIT<T>>` in the storage. While we return this in the `TypeckResults`, it's never actually used anywhere.
>
> MIR building then results in the following statement
> ```rust
> let _0: impl RPIT<T> /* the return place */ = build<RPIT<T>>(_some_local);
> ```
> Unlike HIR typeck MIR typeck now directly equates `RPIT<T>` with `RPIT<RPIT<T>>`. This does not try to define `RPIT` but instead relates its generic arguments https://github.com/rust-lang/rust/blob/b9856b6e400709392dd14599265b6fd52fc19f3e/compiler/rustc_infer/src/infer/relate/type_relating.rs#L185-L190
>
> This means we relate `T` with `RPIT<T>`, which results in a defining use `RPIT<T> = T`

I think it's pretty obvious that this is not desirable behavior, and according to the crater run there were no regressions, so let's break this so that we don't have any inference hazards in the new solver.

In the future `what2` may end up compiling again by also falling back to `()`. However, that is not yet guaranteed and the transition to this state is made significantly harder by not temporarily breaking it on the way. It is also concerning to change the inferred hidden type like this without any notification to the user, even if likely not an issue in this concrete case.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-types Relevant to the types team, which will review and decide on the PR/issue. to-announce Announce this issue on triage meeting
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants