Skip to content

Specificity in contravariant implicit resolution #2974

@smarter

Description

@smarter

The Dotty resolution algorithm was recently ported to scalac by @milessabin in scala/scala#6037 (comment) where this came up:

trait Foo[-T]

trait Bar[-T] extends Foo[T]

object Test {
  implicit val fa: Foo[Any] = ???
  implicit val ba: Bar[Int] = ???

  def test: Unit = {
    implicitly[Foo[Int]]
  }
}

This fails because Bar[Int] is not considered more specific than Foo[Any], because the variance-flipping we do in isAsSpecificValueType is not thorough enough:

[log frontend] ==> isSubType Bar[Int] <:< Foo[Any]?
[log frontend]   ==> isSubType Bar[Int] <:< Foo?
[log frontend]     ==> isSubType Foo <:< Foo?
[log frontend]     <== isSubType Foo <:< Foo = true
[log frontend]   <== isSubType Bar[Int] <:< Foo = true
[log frontend]   ==> hasMatchingMember(Bar[Int] . Foo$$T :?  =+ Any), mbr:  =- Int?
[log frontend]     ==> isSubType  =- Int <:<  =+ Any?
[log frontend]       ==> isSubType Any <:< Int?
[log frontend]       <== isSubType Any <:< Int = false
[log frontend]     <== isSubType  =- Int <:<  =+ Any = false
[log frontend]   <== hasMatchingMember(Bar[Int] . Foo$$T :?  =+ Any), mbr:  =- Int = false
[log frontend] <== isSubType Bar[Int] <:< Foo[Any] = false

hasMatchingMember returns the original member T in Bar where the variance is not flipped (=- Int). We could add a special case in hasMatchingMember but then we might miss some other cases.
Personally, I've never really been convinced by the rationale for flipping only at the top-level, so I think the simplest solution would be to flip at all levels, then we could use a global mode.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions