Skip to content

Commit 856f30d

Browse files
committed
Downwards comparisons for implicit search and overloading resolution
This is a backport of the following Dotty change to scalac, scala/scala3@8954026 As in the Dotty implementation the specificity comparison which is used for overload resolution and implicit selection is now performed as-if all contravariant positions in type constructors were covariant. Currently this breaks test/files/run/t2030.scala in exactly the same way as in Dotty as reported here, scala/scala3#1246 (comment) and in this PR it has been changed to a neg test. However, test/files/run/t2030.scala passes when this PR is combined with, scala#6139 so if/when that is merged it can be switched back to a pos test. Fixes scala/bug#2509. Fixes scala/bug#7768.
1 parent 160233b commit 856f30d

File tree

21 files changed

+492
-2
lines changed

21 files changed

+492
-2
lines changed

spec/06-expressions.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1472,11 +1472,21 @@ question: given
14721472
- A member of any other type is always as specific as a parameterized method or a polymorphic method.
14731473
- Given two members of types $T$ and $U$ which are neither parameterized nor polymorphic method types,
14741474
the member of type $T$ is as specific as the member of type $U$ if
1475-
the existential dual of $T$ conforms to the existential dual of $U$.
1475+
the existential dual of $T$ is _as specific as_ the existential dual of $U$.
14761476
Here, the existential dual of a polymorphic type
14771477
`[$a_1$ >: $L_1$ <: $U_1 , \ldots , a_n$ >: $L_n$ <: $U_n$]$T$` is
14781478
`$T$ forSome { type $a_1$ >: $L_1$ <: $U_1$ $, \ldots ,$ type $a_n$ >: $L_n$ <: $U_n$}`.
14791479
The existential dual of every other type is the type itself.
1480+
A type $T$ is _as specific as_ a type $U$ if,
1481+
- the outermost type constructor of $T$ is unparameterized, or parameterized but not contravariant,
1482+
and $T$ conforms to $U$, or
1483+
- the outermost type constructor of $T$ is parameterized and contravariant, and $T$ when compared as if
1484+
the outermost occurrence of its outermost type constructor was covariant conforms to $U$.
1485+
For example, writing $T$ is _as specific as_ $U$ as `$T$ <:s $U$` and given `class Cmp[-X]`
1486+
then `Cmp[T] <:s Cmp[U]` if `T <: U`. On the other hand, nested occurrences of the outermost type
1487+
constructor are not affected, so `T <: U` would imply that `List[Cmp[U]] <:s List[Cmp[T]]` as usual.
1488+
This relation models closely what happens for methods in that `(T1)T2 <:s (U1)U2` iff
1489+
`T1 => T2 <:s U1 => U2`.
14801490

14811491
The _relative weight_ of an alternative $A$ over an alternative $B$ is a
14821492
number from 0 to 2, defined as the sum of

src/compiler/scala/tools/nsc/typechecker/Infer.scala

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -806,13 +806,35 @@ trait Infer extends Checkable {
806806
case _ => onRight
807807
}
808808
}
809+
809810
private def isAsSpecificValueType(tpe1: Type, tpe2: Type, undef1: List[Symbol], undef2: List[Symbol]): Boolean = tpe1 match {
810811
case PolyType(tparams1, rtpe1) =>
811812
isAsSpecificValueType(rtpe1, tpe2, undef1 ::: tparams1, undef2)
812813
case _ =>
813814
tpe2 match {
814815
case PolyType(tparams2, rtpe2) => isAsSpecificValueType(tpe1, rtpe2, undef1, undef2 ::: tparams2)
815-
case _ => existentialAbstraction(undef1, tpe1) <:< existentialAbstraction(undef2, tpe2)
816+
case _ =>
817+
818+
val e1 = existentialAbstraction(undef1, tpe1)
819+
val e2 = existentialAbstraction(undef2, tpe2)
820+
821+
// Backport of fix for https://github.com/scala/bug/issues/2509
822+
// from Dotty https://github.com/lampepfl/dotty/commit/89540268e6c49fb92b9ca61249e46bb59981bf5a
823+
//
824+
// Note that as of https://github.com/lampepfl/dotty/commit/b9f3084205bc9fcbd2a5181d3f0e539e2a20253a
825+
// Dotty flips variances throughout, not just at the top level. We follow that behaviour here.
826+
val flip = new TypeMap(trackVariance = true) {
827+
def apply(tp: Type): Type = tp match {
828+
case TypeRef(pre, sym, args) if variance > 0 && sym.typeParams.exists(_.isContravariant) =>
829+
mapOver(TypeRef(pre, sym.flipped, args))
830+
case _ =>
831+
mapOver(tp)
832+
}
833+
}
834+
835+
val bt = e1.baseType(e2.typeSymbol)
836+
val lhs = if(bt != NoType) bt else e1
837+
flip(lhs) <:< flip(e2)
816838
}
817839
}
818840

src/reflect/scala/reflect/internal/Symbols.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2017,6 +2017,8 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
20172017
/** Internal method to clone a symbol's implementation with the given flags and no info. */
20182018
def cloneSymbolImpl(owner: Symbol, newFlags: Long): TypeOfClonedSymbol
20192019

2020+
def flipped: Symbol = this
2021+
20202022
// ------ access to related symbols --------------------------------------------------
20212023

20222024
/** The next enclosing class. */
@@ -3386,6 +3388,14 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
33863388
clone
33873389
}
33883390

3391+
override lazy val flipped: ClassSymbol = {
3392+
val clone = cloneSymbol(owner)
3393+
clone.rawInfo.typeParams.foreach { sym =>
3394+
if (sym.isContravariant) sym.resetFlag(Flag.CONTRAVARIANT).setFlag(Flag.COVARIANT)
3395+
}
3396+
clone
3397+
}
3398+
33893399
override def derivedValueClassUnbox =
33903400
// (info.decl(nme.unbox)) orElse uncomment once we accept unbox methods
33913401
(info.decls.find(_ hasAllFlags PARAMACCESSOR | METHOD) getOrElse

test/files/neg/t2509-2.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
t2509-2.scala:26: error: ambiguous implicit values:
2+
both value xb in object Test of type => X[B,Int]
3+
and value xa in object Test of type => X[A,Boolean]
4+
match expected type X[B,U]
5+
val fb = f(new B)
6+
^
7+
one error found

test/files/neg/t2509-2.scala

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
class A
2+
class B extends A
3+
class C extends B
4+
5+
trait X[-T, U] {
6+
val u: U
7+
}
8+
9+
object XA extends X[A, Boolean] {
10+
val u = true
11+
}
12+
13+
object XB extends X[B, Int] {
14+
val u = 23
15+
}
16+
17+
object Test {
18+
implicit def f[T, U](t: T)(implicit x: X[T, U]): U = x.u
19+
implicit val xa: X[A, Boolean] = XA
20+
implicit val xb: X[B, Int] = XB
21+
22+
val fa = f(new A)
23+
val ffa: Boolean = fa
24+
25+
// Should be ambiguous
26+
val fb = f(new B)
27+
val ffb: Int = fb
28+
}

test/files/neg/t2509-3.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
t2509-3.scala:31: error: value value is not a member of B
2+
println("B: " + b.value)
3+
^
4+
one error found

test/files/neg/t2509-3.scala

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
class A
2+
class B extends A
3+
4+
trait Y {
5+
def value: String
6+
}
7+
8+
trait X[-T] {
9+
def y(t: T): Y
10+
}
11+
12+
trait Z[-T] extends X[T]
13+
14+
object ZA extends Z[A] {
15+
def y(a: A) = new Y { def value = a.getClass + ": AValue" }
16+
}
17+
18+
object XB extends X[B] {
19+
def y(b: B) = new Y { def value = b.getClass + ": BValue" }
20+
}
21+
22+
object Test {
23+
implicit def f[T](t: T)(implicit x: X[T]): Y = x.y(t)
24+
implicit val za: Z[A] = ZA
25+
implicit val xb: X[B] = XB
26+
27+
def main(argv: Array[String]): Unit = {
28+
val a = new A
29+
val b = new B
30+
println("A: " + a.value)
31+
println("B: " + b.value)
32+
}
33+
}
34+

test/files/neg/t2509-4.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
t2509-4.scala:31: error: value value is not a member of B
2+
println("B: " + b.value)
3+
^
4+
one error found

test/files/neg/t2509-7b.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
t2509-7b.scala:30: error: ambiguous implicit values:
2+
both method make in object X of type => Both[X,X]
3+
and method make in trait Factory of type => Both[Y,Y]
4+
match expected type Both[Y,X]
5+
get
6+
^
7+
one error found

test/files/neg/t2509-7b.scala

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
class Both[-A, +B]
2+
3+
trait Factory[A] {
4+
implicit def make: Both[A, A] = new Both[A, A]
5+
}
6+
7+
trait X
8+
object X extends Factory[X] {
9+
override implicit def make: Both[X, X] = super.make
10+
}
11+
12+
class Y extends X
13+
object Y extends Factory[Y] {
14+
// See test/files/pos/t2509-7a.scala ... discussion below
15+
// override implicit def make: Both[Y, Y] = super.make
16+
}
17+
18+
object Test {
19+
def get(implicit ev: Both[Y, X]) = ev
20+
21+
// There are two possible implicits here: X.make and Y.make, neither are
22+
// subtype of each other, so who wins?
23+
// - Under the old scheme it's X.make because `isAsGood` sees that X.make is defined
24+
// in X whereas Y.make is defined in Factory
25+
// - Under the new scheme it's ambiguous because we replace contravariance by covariance
26+
// in top-level type parameters so Y.make is treated as a subtype of X.make
27+
// In both schemes we can get Y.make to win by uncommenting the override for make in Y
28+
// (Y wins against X because `isDerived` also considers the subtyping relationships
29+
// of companion classes)
30+
get
31+
}
File renamed without changes.

test/files/pos/t2509-5.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// See https://github.com/lampepfl/dotty/issues/2974
2+
trait Foo[-T]
3+
4+
trait Bar[-T] extends Foo[T]
5+
6+
object Test {
7+
implicit val fa: Foo[Any] = ???
8+
implicit val ba: Bar[Int] = ???
9+
10+
def test: Unit = {
11+
implicitly[Foo[Int]]
12+
}
13+
}

test/files/pos/t2509-6.scala

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
class A
2+
class B extends A
3+
4+
trait Y {
5+
def value: String
6+
}
7+
8+
trait X[-T] {
9+
def y(t: T): Y
10+
}
11+
12+
trait Z[-T] extends X[T]
13+
14+
object XA extends X[A] {
15+
def y(a: A) = new Y { def value = a.getClass + ": AValue" }
16+
}
17+
18+
object ZB extends Z[B] {
19+
def y(b: B) = new Y { def value = b.getClass + ": BValue" }
20+
}
21+
22+
object Test {
23+
implicit def f[T](t: T)(implicit x: X[T]): Y = x.y(t)
24+
implicit val za: X[A] = XA
25+
implicit val xb: Z[B] = ZB
26+
27+
def main(argv: Array[String]): Unit = {
28+
val a = new A
29+
val b = new B
30+
println("A: " + a.value)
31+
println("B: " + b.value)
32+
}
33+
}

test/files/pos/t2509-7a.scala

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
class Both[-A, +B]
2+
3+
trait Factory[A] {
4+
implicit def make: Both[A, A] = new Both[A, A]
5+
}
6+
7+
trait X
8+
object X extends Factory[X] {
9+
override implicit def make: Both[X, X] = super.make
10+
}
11+
12+
class Y extends X
13+
object Y extends Factory[Y] {
14+
// See test/files/neg/t2509-7b.scala ... discussion below
15+
override implicit def make: Both[Y, Y] = super.make
16+
}
17+
18+
object Test {
19+
def get(implicit ev: Both[Y, X]) = ev
20+
21+
// There are two possible implicits here: X.make and Y.make, neither are
22+
// subtype of each other, so who wins?
23+
// - Under the old scheme it's X.make because `isAsGood` sees that X.make is defined
24+
// in X whereas Y.make is defined in Factory
25+
// - Under the new scheme it's ambiguous because we replace contravariance by covariance
26+
// in top-level type parameters so Y.make is treated as a subtype of X.make
27+
// In both schemes we can get Y.make to win by uncommenting the override for make in Y
28+
// (Y wins against X because `isDerived` also considers the subtyping relationships
29+
// of companion classes)
30+
get
31+
}

test/files/run/t2509-1.check

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
A: class A: AValue
2+
B: class B: BValue

test/files/run/t2509-1.scala

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import scala.language.implicitConversions
2+
3+
class A
4+
class B extends A
5+
6+
trait Y {
7+
def value: String
8+
}
9+
10+
trait X[-T] {
11+
def y(t: T): Y
12+
}
13+
14+
object XA extends X[A] {
15+
def y(a: A) = new Y { def value = s"${a.getClass}: AValue" }
16+
}
17+
18+
object XB extends X[B] {
19+
def y(b: B) = new Y { def value = s"${b.getClass}: BValue" }
20+
}
21+
22+
object Test {
23+
implicit def f[T](t: T)(implicit x: X[T]): Y = x.y(t)
24+
implicit val xa: X[A] = XA
25+
implicit val xb: X[B] = XB
26+
27+
def main(argv: Array[String]): Unit = {
28+
val a = new A
29+
val b = new B
30+
println(s"A: ${a.value}")
31+
println(s"B: ${b.value}")
32+
}
33+
}

test/files/run/t2509-4.check

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
A: class A: AValue
2+
B: class B: BValue

test/files/run/t2509-4.scala

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import scala.language.implicitConversions
2+
3+
class A
4+
class B extends A
5+
6+
trait Y {
7+
def value: String
8+
}
9+
10+
trait X[-T] {
11+
def y(t: T): Y
12+
}
13+
14+
trait Z[-T] extends X[T]
15+
16+
object XA extends Z[A] {
17+
def y(a: A) = new Y { def value = s"${a.getClass}: AValue" }
18+
}
19+
20+
object ZB extends Z[B] {
21+
def y(b: B) = new Y { def value = s"${b.getClass}: BValue" }
22+
}
23+
24+
object Test {
25+
implicit def f[T](t: T)(implicit x: X[T]): Y = x.y(t)
26+
implicit val za: X[A] = XA
27+
implicit val xb: Z[B] = ZB
28+
29+
def main(argv: Array[String]): Unit = {
30+
val a = new A
31+
val b = new B
32+
println(s"A: ${a.value}")
33+
println(s"B: ${b.value}")
34+
}
35+
}

test/files/run/t7768.check

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Inv[A] Inv[B] Inv[C] Inv[D] Inv[E]
2+
Con[A] Con[A] Con[C] Con[C] Con[E]
3+
Cov[E] Cov[E] Cov[E] Cov[E] Cov[E]

0 commit comments

Comments
 (0)