rust-lang / rust-lang/rust

Hidden type of "discarded" and thus unconstrained RPIT(IT) opaque types gets inferred to be the unit type `()`

Open
#134,311 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-impl-trait A-inference C-bug fixed-by-next-solver P-low T-types
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

This only affects RPIT(IT), not TAIT or ATPIT.
Fixing this issue would be a breaking change.
This exploits the issue #132212 (syntactically rejecting impl-Trait inside non-final path segments & inside fn ptr types is futile).


All subsequent snippets assume the following definitions:

type DiscardT<T> = <T as Discard>::Output;
trait Discard { type Output; }
impl<T: ?Sized> Discard for T { type Output = Point; } // you can substitute `Point` with `()` everywhere, this is just for clarity
struct Point;
trait NobodyImplsThis {}

RPIT check-pass reproducer (playground):

fn demo0() -> DiscardT<impl ?Sized> { Point }

RPIT check-fail reproducer (playground):

fn demo1() -> DiscardT<impl NobodyImplsThis + ?Sized> { Point }
//~^ ERROR the trait bound `(): NobodyImplsThis` is not satisfied

I expected both reproducers to get rejected by the compiler due to uninferable types or unconstrained opaque types (with an error roughly of the form item does not constrain demoN::{opaque#0}, but has it in its signature (mirroring TAIT/ATPIT)). The opaque types should be unconstrained because DiscardT<{opaque}> normalizes to Point which does not contain any opaque types.

However, in both cases the hidden type gets inferred to be (). See also the compiler log for the first reproducer:

rustc_hir_analysis::check::check::check_opaque_meets_bounds def_id=DefId(0:13 ~ oi[08b9]::demo0::{opaque#0}), span=oi.rs:6:24: 6:35 (#4), origin=FnReturn { parent: DefId(0:12 ~ oi[08b9]::demo0), in_trait_or_impl: None }
   rustc_middle::ty::sty::new_opaque def_id=DefId(0:13 ~ oi[08b9]::demo0::{opaque#0}), args=[]
   rustc_infer::infer::opaque_types::register_hidden_type opaque_type_key=OpaqueTypeKey { def_id: DefId(0:13 ~ oi[08b9]::demo0::{opaque#0}), args: [] }, span=oi.rs:6:24: 6:35 (#4), param_env=ParamEnv { caller_bounds: [], reveal: UserFacing }, hidden_ty=()
     rustc_infer::infer::opaque_types::table::register key=OpaqueTypeKey { def_id: DefId(0:13 ~ oi[08b9]::demo0::{opaque#0}), args: [] }, hidden_type=OpaqueHiddenType { span: oi.rs:6:24: 6:35 (#4), ty: () }

It's odd that some sort of type inference fallback occurs (in case you're wondering: In Rust 2024, it also falls back to (), not ! (since we don't have anything diverging here)).

Related: The fixed issue #96460 (PhantomData<{opaque}>).
Ranking this P-low because it's pretty artificial and not unsound.

For completeness, RPITIT reproducers
trait Demo2 { fn f() -> DiscardT<impl ?Sized> { Point } }
trait Demo3 { fn f() -> DiscardT<impl NobodyImplsThis + ?Sized> { Point } }
//~^ ERROR the trait bound `(): NobodyImplsThis` is not satisfied

For comparison, the TAIT and ATPIT analogues result in an error (though I'm not sure if that's an interesting comparison to draw).

TAIT/ATPIT
#![feature(type_alias_impl_trait, impl_trait_in_assoc_type)]

type Weak0 = impl ?Sized;
fn demo4() -> DiscardT<Weak0> { Point }
//~^ ERROR item does not constrain `Weak0::{opaque#0}`, but has it in its signature

type Weak1 = DiscardT<impl ?Sized>;
fn demo5() -> Weak1 { Point }
//~^ ERROR item does not constrain `Weak1::{opaque#0}`, but has it in its signature

trait Demo6 {
    type Proj: ?Sized;
    fn f() -> DiscardT<Self::Proj>;
}
impl Demo6 for i32 {
    type Proj = impl ?Sized;
    fn f() -> DiscardT<Self::Proj> { Point }
    //~^ ERROR item does not constrain `<i32 as Demo6>::Proj::{opaque#0}`, but has it in its signature
}

trait Demo7 { 
    type Proj;
    fn f() -> Self::Proj;
}
impl Demo7 for i32 {
    type Proj = DiscardT<impl ?Sized>;
    fn f() -> Self::Proj { Point }
    //~^ ERROR item does not constrain `<i32 as Demo7>::Proj::{opaque#0}`, but has it in its signature
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by compiling the RPIT and RPITIT reproducers in the issue and compare their inferred hidden type with the TAIT/ATPIT diagnostics. Trace the opaque-type registration shown in the compiler log, especially register_hidden_type, and confirm that the fix rejects unconstrained opaque types instead of inferring them as ().

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.