Skip to content

Commit

Permalink
Downwards comparisons for implicit search and overloading resolution
Browse files Browse the repository at this point in the history
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 top level 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.
  • Loading branch information
milessabin committed Mar 6, 2018
1 parent 8741bd9 commit 0407d6a
Show file tree
Hide file tree
Showing 18 changed files with 416 additions and 1 deletion.
22 changes: 21 additions & 1 deletion src/compiler/scala/tools/nsc/typechecker/Infer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -837,13 +837,33 @@ trait Infer extends Checkable {
case _ => onRight
}
}

private def isAsSpecificValueType(tpe1: Type, tpe2: Type, undef1: List[Symbol], undef2: List[Symbol]): Boolean = tpe1 match {
case PolyType(tparams1, rtpe1) =>
isAsSpecificValueType(rtpe1, tpe2, undef1 ::: tparams1, undef2)
case _ =>
tpe2 match {
case PolyType(tparams2, rtpe2) => isAsSpecificValueType(tpe1, rtpe2, undef1, undef2 ::: tparams2)
case _ => existentialAbstraction(undef1, tpe1) <:< existentialAbstraction(undef2, tpe2)
case _ =>

val e1 = existentialAbstraction(undef1, tpe1)
val e2 = existentialAbstraction(undef2, tpe2)

// Backport of fix for https://github.com/scala/bug/issues/2509
// from Dotty https://github.com/lampepfl/dotty/commit/89540268e6c49fb92b9ca61249e46bb59981bf5a
val flip = new TypeMap(trackVariance = true) {
def apply(tp: Type): Type = tp match {
case TypeRef(pre, sym, args) if variance > 0 && sym.typeParams.exists(_.isContravariant) =>
TypeRef(pre, sym.flipped, args)
case _: TypeRef => tp
case _ =>
mapOver(tp)
}
}

val bt = e1.baseType(e2.typeSymbol)
val lhs = if(bt != NoType) bt else e1
flip(lhs) <:< flip(e2)
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/reflect/scala/reflect/internal/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2003,6 +2003,8 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
/** Internal method to clone a symbol's implementation with the given flags and no info. */
def cloneSymbolImpl(owner: Symbol, newFlags: Long): TypeOfClonedSymbol

def flipped: Symbol = this

// ------ access to related symbols --------------------------------------------------

/** The next enclosing class. */
Expand Down Expand Up @@ -3369,6 +3371,14 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
clone
}

override lazy val flipped: ClassSymbol = {
val clone = cloneSymbol(owner)
clone.rawInfo.typeParams.foreach { sym =>
if (sym.isContravariant) sym.resetFlag(Flag.CONTRAVARIANT).setFlag(Flag.COVARIANT)
}
clone
}

override def derivedValueClassUnbox =
// (info.decl(nme.unbox)) orElse uncomment once we accept unbox methods
(info.decls.find(_ hasAllFlags PARAMACCESSOR | METHOD) getOrElse
Expand Down
7 changes: 7 additions & 0 deletions test/files/neg/t2030.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
t2030.scala:5: error: ambiguous implicit values:
both method newCanBuildFrom in object SortedSet of type [A](implicit ord: Ordering[A])scala.collection.generic.CanBuildFrom[scala.collection.immutable.SortedSet.Coll,A,scala.collection.immutable.SortedSet[A]]
and method newCanBuildFrom in class SortedSetFactory of type [A](implicit ord: Ordering[A])scala.collection.generic.CanBuildFrom[scala.collection.immutable.TreeSet.Coll,A,scala.collection.immutable.TreeSet[A]]
match expected type scala.collection.generic.CanBuildFrom[scala.collection.immutable.TreeSet[Int],Int,That]
val res1 = res0.map(x => x)
^
one error found
File renamed without changes.
7 changes: 7 additions & 0 deletions test/files/neg/t2509-2.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
t2509-2.scala:26: error: ambiguous implicit values:
both value xb in object Test of type => X[B,Int]
and value xa in object Test of type => X[A,Boolean]
match expected type X[B,U]
val fb = f(new B)
^
one error found
28 changes: 28 additions & 0 deletions test/files/neg/t2509-2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
class A
class B extends A
class C extends B

trait X[-T, U] {
val u: U
}

object XA extends X[A, Boolean] {
val u = true
}

object XB extends X[B, Int] {
val u = 23
}

object Test {
implicit def f[T, U](t: T)(implicit x: X[T, U]): U = x.u
implicit val xa: X[A, Boolean] = XA
implicit val xb: X[B, Int] = XB

val fa = f(new A)
val ffa: Boolean = fa

// Should be ambiguous
val fb = f(new B)
val ffb: Int = fb
}
4 changes: 4 additions & 0 deletions test/files/neg/t2509-3.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
t2509-3.scala:31: error: value value is not a member of B
println("B: " + b.value)
^
one error found
34 changes: 34 additions & 0 deletions test/files/neg/t2509-3.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
class A
class B extends A

trait Y {
def value: String
}

trait X[-T] {
def y(t: T): Y
}

trait Z[-T] extends X[T]

object ZA extends Z[A] {
def y(a: A) = new Y { def value = a.getClass + ": AValue" }
}

object XB extends X[B] {
def y(b: B) = new Y { def value = b.getClass + ": BValue" }
}

object Test {
implicit def f[T](t: T)(implicit x: X[T]): Y = x.y(t)
implicit val za: Z[A] = ZA
implicit val xb: X[B] = XB

def main(argv: Array[String]): Unit = {
val a = new A
val b = new B
println("A: " + a.value)
println("B: " + b.value)
}
}

4 changes: 4 additions & 0 deletions test/files/neg/t2509-4.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
t2509-4.scala:31: error: value value is not a member of B
println("B: " + b.value)
^
one error found
13 changes: 13 additions & 0 deletions test/files/pos/t2509-5.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// See https://github.com/lampepfl/dotty/issues/2974
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]]
}
}
33 changes: 33 additions & 0 deletions test/files/pos/t2509-6.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class A
class B extends A

trait Y {
def value: String
}

trait X[-T] {
def y(t: T): Y
}

trait Z[-T] extends X[T]

object XA extends X[A] {
def y(a: A) = new Y { def value = a.getClass + ": AValue" }
}

object ZB extends Z[B] {
def y(b: B) = new Y { def value = b.getClass + ": BValue" }
}

object Test {
implicit def f[T](t: T)(implicit x: X[T]): Y = x.y(t)
implicit val za: X[A] = XA
implicit val xb: Z[B] = ZB

def main(argv: Array[String]): Unit = {
val a = new A
val b = new B
println("A: " + a.value)
println("B: " + b.value)
}
}
3 changes: 3 additions & 0 deletions test/files/run/t2509-1.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
warning: there was one feature warning; re-run with -feature for details
A: class A: AValue
B: class B: BValue
31 changes: 31 additions & 0 deletions test/files/run/t2509-1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class A
class B extends A

trait Y {
def value: String
}

trait X[-T] {
def y(t: T): Y
}

object XA extends X[A] {
def y(a: A) = new Y { def value = a.getClass + ": AValue" }
}

object XB extends X[B] {
def y(b: B) = new Y { def value = b.getClass + ": BValue" }
}

object Test {
implicit def f[T](t: T)(implicit x: X[T]): Y = x.y(t)
implicit val xa: X[A] = XA
implicit val xb: X[B] = XB

def main(argv: Array[String]) {
val a = new A
val b = new B
println("A: " + a.value)
println("B: " + b.value)
}
}
3 changes: 3 additions & 0 deletions test/files/run/t2509-4.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
warning: there was one feature warning; re-run with -feature for details
A: class A: AValue
B: class B: BValue
34 changes: 34 additions & 0 deletions test/files/run/t2509-4.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
class A
class B extends A

trait Y {
def value: String
}

trait X[-T] {
def y(t: T): Y
}

trait Z[-T] extends X[T]

object XA extends Z[A] {
def y(a: A) = new Y { def value = a.getClass + ": AValue" }
}

object ZB extends Z[B] {
def y(b: B) = new Y { def value = b.getClass + ": BValue" }
}

object Test {
implicit def f[T](t: T)(implicit x: X[T]): Y = x.y(t)
implicit val za: X[A] = XA
implicit val xb: Z[B] = ZB

def main(argv: Array[String]): Unit = {
val a = new A
val b = new B
println("A: " + a.value)
println("B: " + b.value)
}
}

3 changes: 3 additions & 0 deletions test/files/run/t7768.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Inv[A] Inv[B] Inv[C] Inv[D] Inv[E]
Con[A] Con[A] Con[C] Con[C] Con[E]
Cov[E] Cov[E] Cov[E] Cov[E] Cov[E]
89 changes: 89 additions & 0 deletions test/files/run/t7768.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
class A
class B extends A
class C extends B
class D extends C
class E extends D

class Inv[T](override val toString: String)
class Con[-T](override val toString: String)
class Cov[+T](override val toString: String)

object InvTest {
implicit val a : Inv[A] = new Inv[A]("Inv[A]")
implicit val b : Inv[B] = new Inv[B]("Inv[B]")
implicit val c : Inv[C] = new Inv[C]("Inv[C]")
implicit val d : Inv[D] = new Inv[D]("Inv[D]")
implicit val e : Inv[E] = new Inv[E]("Inv[E]")
}
object ConTest {
implicit val a : Con[A] = new Con[A]("Con[A]")
implicit val c : Con[C] = new Con[C]("Con[C]")
implicit val e : Con[E] = new Con[E]("Con[E]")
}
object CovTest {
implicit val a : Cov[A] = new Cov[A]("Cov[A]")
implicit val c : Cov[C] = new Cov[C]("Cov[C]")
implicit val e : Cov[E] = new Cov[E]("Cov[E]")
}

object Test {
def f0(): Unit = {
import InvTest._
println(List(
implicitly[Inv[A]],
implicitly[Inv[B]],
implicitly[Inv[C]],
implicitly[Inv[D]],
implicitly[Inv[E]]
) mkString " ")
}
def f1(): Unit = {
import ConTest._
println(List(
implicitly[Con[A]],
implicitly[Con[B]],
implicitly[Con[C]],
implicitly[Con[D]],
implicitly[Con[E]]
) mkString " ")
}
def f2(): Unit = {
import CovTest._
println(List(
implicitly[Cov[A]],
implicitly[Cov[B]],
implicitly[Cov[C]],
implicitly[Cov[D]],
implicitly[Cov[E]]
) mkString " ")
}

def main(args: Array[String]): Unit = {
f0()
f1()
f2()
}
}

/***
Previously:
Inv[A] Inv[B] Inv[C] Inv[D] Inv[E]
Con[A] Con[A] Con[A] Con[A] Con[A]
Cov[E] Cov[E] Cov[E] Cov[E] Cov[E]
Currently (and in Dotty):
Inv[A] Inv[B] Inv[C] Inv[D] Inv[E]
Con[A] Con[A] Con[C] Con[C] Con[E]
Cov[E] Cov[E] Cov[E] Cov[E] Cov[E]
Note that @paulp thinks that f2 should produce,
Cov[A] Cov[C] Cov[C] Cov[E] Cov[E]
I don't think that behaviour would be desirable: in the covariant case the
expectation that the most derived should be selected as the most specific
seems reasonable.
***/
Loading

0 comments on commit 0407d6a

Please sign in to comment.