Adding a specialized impl can break inference.

46edc82
Opened by Leonardo Yvens at 2024-04-19 14:10:39

Relevant to #31844. This is how it happens:

#![feature(specialization)]

trait B { fn b(&self) -> Self; }

// Impl 1
impl<T> B for Option<T> where T: Default
{
    default fn b(&self) -> Option<T> { Some(T::default()) }
}
// Removing one of the two concrete impls makes inference succeed.
// Impl 2
impl B for Option<String>
{
    fn b(&self) -> Self { Some("special".into()) }
}
// Impl 3
impl B for Option<i32>
{
    fn b(&self) -> Self { Some(0) }
}

fn main() { 
    // Cannot infer `T` in `Option<T>`.
   None.b();
}

This issue does not originate from specialization, since if we removed Impl 1 the same problem would occur. But with specialization if those impls were added in order, the story would be a bit confusing:

  1. With Impl 1, inference fails.
  2. Added Impl 2, yay inference succeeds.
  3. Added Impl 3, inference is back to failing.

The only fix would be to make inference fail in step 2. Even if it's not something we want fix it still seems worth noting somewhere that we are ok with specializaton breaking inference.

  1. Is this the same issue that's causing this error? https://github.com/rust-lang/rust/pull/51464#issuecomment-396008009

    There used to be a bunch of specific impls for SliceIndex for various types. That PR adds a default impl (so the old specific impls are now specializations of the default impl). The error in question is a test case that used to pass that now fails inference.

    Joshua Liebow-Feeser at 2018-06-10 04:01:20

  2. I'm running into this when playing around with stdlib. For a type that accepts either i32 or any other integer type, inference fails because the compiler sees that i32 is a valid option and automatically chooses that without actually trying to solve the inference.

    Jacob Pratt at 2021-04-01 08:13:37