scala / bug

Scala 2 bug reports only. Please, no questions — proper bug reports only.
https://scala-lang.org
230 stars 21 forks source link

Implicit search for covariant/contravariant type argument(s) doesn't try all options #12841

Closed tribbloid closed 5 months ago

tribbloid commented 10 months ago

Reproduction steps

Scala version: 2.13.11

scala>
object ExtractingTypeArg {

  trait Vec[+T <: AnyRef]

  trait ArgOf[V <: Vec[_]] {

    type TT
  }

  object ArgOf {

    implicit def impl1[V <: Vec[_], T0 <: AnyRef](
        implicit
        ev: V <:< Vec[T0]
    ): ArgOf[V] { type TT = T0 } =
      new ArgOf[V] {
        override type TT = T0
      } // this is written by GitHub copilot

    //    implicit def impl2[T0 <: AnyRef, V <: Vec[T0]]: Extractor[V] { type TT = T0 } =
    //      new Extractor[V] {
    //        override type TT = T0
    //      } // this is written by me
  }

  trait StringVec extends Vec[String]

  val strEx = summon[ArgOf[Vec[String]]]
//  val strEx = summon[ArgOf[StringVec]]
  //  implicitly[intEx.TT =:= Int] // disabled temporarily for not explaining type reduction

  val x: String = ??? : strEx.TT
}

Works smoothly in Scala 3.3.x

Breaks in Scala 2:

[Error] ... : type mismatch;
 found   : xxx.ExtractingTypeArg.strEx.TT
    (which expands to)  Object
 required: String

Problem

Should behave similarly to Scala 3. (which expands to) Object indicates that implicit search is not using the tightest bound.

Wondering if -Xsource:3 would help tho

tribbloid commented 10 months ago

Scala 3.3.x doesn't even need this because of match type, which makes it even more important in Scala 2

joroKr21 commented 10 months ago

You expect the constraint from V <:< Vec[T0] to help you infer T0 but that doesn't work in Scala 2. After all T0 = Object is a totally valid solution here. So it's a type inference difference between Scala 2 and Scala 3. Also there is no summon in Scala 2.

You can make something else work in Scala 2:

object ExtractingTypeArg {
  trait Vec[+T <: AnyRef]
  trait ArgOf[V <: Vec[_]] {
    type TT
  }

  object ArgOf {
    def apply[V <: Vec[_]](implicit ev: ArgOf[_ >: V]): ev.type = ev

    implicit def impl1[T <: AnyRef]: ArgOf[Vec[T]] { type TT = T } =
      new ArgOf[Vec[T]] {
        override type TT = T
      }
  }

  trait StringVec extends Vec[String]

  val strEx1 = ArgOf[Vec[String]]
  val strEx2 = ArgOf[StringVec]
  val x: String = ??? : strEx1.TT
  val y: String = ??? : strEx2.TT
}

Here we infer first V then T separately.

tribbloid commented 10 months ago

Oh my apologies, I backported summon from Scala 3 to get the narrow dependent result type.

This maybe a false alarm (Scala 3 was only specific about this rule for match type), but the same argument can be applied to your 2-stage search as well. Let me try to reproduce your solution and validate if it covers all cases ...

SethTisue commented 5 months ago

(provisionally closing since "it's a type inference difference between Scala 2 and Scala 3" seems to be the last definite point that was made)

SethTisue commented 4 months ago

see my remarks on https://github.com/scala/bug/issues/12952 about filing quality bug reports