Skip to content

Commit

Permalink
Higher-kinded type variable unification.
Browse files Browse the repository at this point in the history
Can cause ambiguous implicits, so is under the compiler flag
-Xsource:2.13

Fixes scala/bug#10185
Fixes scala/bug#10195
Fixes scala/bug#10197
Fixes scala/bug#10213
Fixes scala/bug#10238
Fixes scala/bug#10372
Presents an alternative fix to scala/bug#6895.
  • Loading branch information
TomasMikula committed Sep 29, 2017
1 parent e1e8d05 commit 33478bd
Show file tree
Hide file tree
Showing 31 changed files with 585 additions and 5 deletions.
3 changes: 2 additions & 1 deletion src/reflect/mima-filters/2.12.0.forwards.excludes
Expand Up @@ -13,4 +13,5 @@ ProblemFilters.exclude[MissingClassProblem]("scala.reflect.io.FileZipArchive$Laz
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.io.ZipArchive.closeZipFile")
ProblemFilters.exclude[MissingClassProblem]("scala.reflect.io.FileZipArchive$LeakyEntry")

ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.runtime.SynchronizedSymbols#SynchronizedSymbol.exists")
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.runtime.SynchronizedSymbols#SynchronizedSymbol.exists")
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.runtime.Settings.isScala213")
2 changes: 1 addition & 1 deletion src/reflect/scala/reflect/internal/Types.scala
Expand Up @@ -3325,7 +3325,7 @@ trait Types
)
override def etaExpand: Type = (
if (!isHigherKinded) this
else logResult("Normalizing HK $this")(typeFun(params, applyArgs(params map (_.typeConstructor))))
else logResult(s"Normalizing HK $this")(typeFun(params, applyArgs(params map (_.typeConstructor))))
)
override def typeSymbol = origin.typeSymbol

Expand Down
Expand Up @@ -61,6 +61,7 @@ abstract class MutableSettings extends AbsSettings {

def isScala211: Boolean
def isScala212: Boolean
private[scala] def isScala213: Boolean
}

object MutableSettings {
Expand Down
29 changes: 27 additions & 2 deletions src/reflect/scala/reflect/internal/tpe/TypeComparers.scala
Expand Up @@ -365,7 +365,32 @@ trait TypeComparers {

// @assume tp1.isHigherKinded || tp2.isHigherKinded
def isHKSubType(tp1: Type, tp2: Type, depth: Depth): Boolean = {
def isSub(ntp1: Type, ntp2: Type) = (ntp1.withoutAnnotations, ntp2.withoutAnnotations) match {

def isSubHKTypeVar(tp1: Type, tp2: Type) = (tp1, tp2) match {
case (tv1 @ TypeVar(_, _), tv2 @ TypeVar(_, _)) =>
reporter.warning(tv1.typeSymbol.pos,
sm"""|compiler bug: Unexpected code path: testing two type variables for subtype relation:
| ${tv1} <:< ${tv2}
|Please report bug at https://github.com/scala/bug/issues
""".trim)
false
case (tp1, tv2 @ TypeVar(_, _)) =>
val ntp1 = tp1.normalize
(tv2.params corresponds ntp1.typeParams)(methodHigherOrderTypeParamsSubVariance) &&
{ tv2.addLoBound(ntp1); true }
case (tv1 @ TypeVar(_, _), tp2) =>
val ntp2 = tp2.normalize
(ntp2.typeParams corresponds tv1.params)(methodHigherOrderTypeParamsSubVariance) &&
{ tv1.addHiBound(ntp2); true }
case _ =>
false
}

def isSub(tp1: Type, tp2: Type) =
settings.isScala213 && isSubHKTypeVar(tp1, tp2) ||
isSub2(tp1.normalize, tp2.normalize) // @M! normalize reduces higher-kinded case to PolyType's

def isSub2(ntp1: Type, ntp2: Type) = (ntp1, ntp2) match {
case (TypeRef(_, AnyClass, _), _) => false // avoid some warnings when Nothing/Any are on the other side
case (_, TypeRef(_, NothingClass, _)) => false
case (pt1: PolyType, pt2: PolyType) => isPolySubType(pt1, pt2) // @assume both .isHigherKinded (both normalized to PolyType)
Expand All @@ -381,7 +406,7 @@ trait TypeComparers {
|| (if (isNoArgStaticClassTypeRef(tp1) && isNoArgStaticClassTypeRef(tp2))
tp1.typeSymbolDirect.isNonBottomSubClass(tp2.typeSymbolDirect) // OPT faster than comparing eta-expanded types
else
isSub(tp1.normalize, tp2.normalize) && annotationsConform(tp1, tp2) // @M! normalize reduces higher-kinded case to PolyType's
isSub(tp1.withoutAnnotations, tp2.withoutAnnotations) && annotationsConform(tp1, tp2)
)
)
}
Expand Down
1 change: 1 addition & 0 deletions src/reflect/scala/reflect/runtime/Settings.scala
Expand Up @@ -54,4 +54,5 @@ private[reflect] class Settings extends MutableSettings {
val maxClassfileName = new IntSetting(255)
def isScala211 = true
def isScala212 = true
private[scala] def isScala213 = false
}
22 changes: 22 additions & 0 deletions test/files/neg/hk-typevar-unification.check
@@ -0,0 +1,22 @@
hk-typevar-unification.scala:14: error: inferred kinds of the type arguments ([_ <: B]Foo[_]) do not conform to the expected kinds of the type parameters (type F).
[_ <: B]Foo[_]'s type parameters do not match type F's expected parameters:
type _ (in class Foo)'s bounds <: B are stricter than type _'s declared bounds >: Nothing <: Any
f(tcFoo)
^
hk-typevar-unification.scala:14: error: type mismatch;
found : TC[Foo]
required: TC[F]
f(tcFoo)
^
hk-typevar-unification.scala:17: error: inferred kinds of the type arguments ([_ <: B]Foo[_]) do not conform to the expected kinds of the type parameters (type F).
[_ <: B]Foo[_]'s type parameters do not match type F's expected parameters:
type _ (in class Foo) is invariant, but type _ is declared covariant
type _ (in class Foo)'s bounds <: B are stricter than type _'s declared bounds >: Nothing <: Any
g(tcFoo)
^
hk-typevar-unification.scala:17: error: type mismatch;
found : TC[Foo]
required: TC[F]
g(tcFoo)
^
four errors found
1 change: 1 addition & 0 deletions test/files/neg/hk-typevar-unification.flags
@@ -0,0 +1 @@
-Xsource:2.13
18 changes: 18 additions & 0 deletions test/files/neg/hk-typevar-unification.scala
@@ -0,0 +1,18 @@
class A
class B
trait TC[F[_ <: A]]
class Foo[_ <: B]

object Test {

def f[F[ _]](tc: TC[F]): Unit = ()
def g[F[+_]](tc: TC[F]): Unit = ()

val tcFoo: TC[Foo] = new TC[Foo] {}

// incompatible bounds
f(tcFoo)

// incompatible variance
g(tcFoo)
}
1 change: 1 addition & 0 deletions test/files/pos/patmat-hk.flags
@@ -0,0 +1 @@
-Xsource:2.13
13 changes: 13 additions & 0 deletions test/files/pos/patmat-hk.scala
@@ -0,0 +1,13 @@
case class Foo[F[_]]()

case class APair[F[_], G[_], A](f: F[A], g: G[A])

object Test {
Foo[({ type L[a] = (a, Int) })#L]() match {
case Foo() => ()
}

APair[({ type L[a] = (Boolean, a) })#L, ({ type L[a] = a => Int })#L, String]((true, "two"), _.length) match {
case APair((b, s), f) => ()
}
}
1 change: 1 addition & 0 deletions test/files/pos/t10185.flags
@@ -0,0 +1 @@
-Xsource:2.13
10 changes: 10 additions & 0 deletions test/files/pos/t10185.scala
@@ -0,0 +1,10 @@
sealed trait Foo[A, F[_ <: A]]
case class Bar[A, F[_ <: A]]() extends Foo[A, F]

class F[S <: String]

object Test {
def f(foo: Foo[String, F]): Unit = foo match {
case Bar() => ()
}
}
1 change: 1 addition & 0 deletions test/files/pos/t10195.flags
@@ -0,0 +1 @@
-Xsource:2.13
11 changes: 11 additions & 0 deletions test/files/pos/t10195.scala
@@ -0,0 +1,11 @@
sealed trait Foo[F[_]]
case class Bar[F[_]]() extends Foo[F]

object Test {

val foo: Foo[({ type Out[X] = String })#Out] = ???

foo match {
case Bar() =>
}
}
1 change: 1 addition & 0 deletions test/files/pos/t10195b.flags
@@ -0,0 +1 @@
-Xsource:2.13
19 changes: 19 additions & 0 deletions test/files/pos/t10195b.scala
@@ -0,0 +1,19 @@
sealed trait Foo[F[_]]
case class Bar[F[_]]() extends Foo[F]

trait TC[A, B] {
type F[X] = B
}

object TC {
implicit val intInstance: TC[Int, String] =
new TC[Int, String] {}

implicit class Ops[A, B](a: A)(implicit val tc: TC[A, B]) {
def getFoo: Foo[tc.F] = ???
}

1.getFoo match {
case Bar() =>
}
}
1 change: 1 addition & 0 deletions test/files/pos/t10197.flags
@@ -0,0 +1 @@
-Xsource:2.13
38 changes: 38 additions & 0 deletions test/files/pos/t10197.scala
@@ -0,0 +1,38 @@
import scala.language.higherKinds

final case class Getter[S, A](get: S => A)

final case class Wrap[F[_], A](value: F[A])

object Wrap {
// Helper to defer specifying second argument to Wrap.
// Basically a type lambda specialized for Wrap.
// Wr[F]#ap[A] =:= Wrap[F, A]
type Wr[F[_]] = { type ap[A] = Wrap[F, A] }

implicit def unwrapper[F[_], A]: Getter[Wrap[F, A], F[A]] =
Getter(w => w.value)
}

object Test {
import Wrap._

type Foo[A] = List[A]
type Bar[A] = String

type WrapFoo1[A] = Wrap[Foo, A]
type WrapBar1[A] = Wrap[Bar, A]

implicitly[Getter[WrapFoo1[Int], Foo[Int]]]
implicitly[Getter[WrapBar1[Int], Bar[Int]]]

type WrapFoo2[A] = Wr[Foo]#ap[A]
type WrapBar2[A] = Wr[Bar]#ap[A]

// here's evidence that the new types are the same as the old ones
implicitly[WrapFoo2[Int] =:= WrapFoo1[Int]]
implicitly[WrapBar2[Int] =:= WrapBar1[Int]]

implicitly[Getter[WrapFoo2[Int], Foo[Int]]]
implicitly[Getter[WrapBar2[Int], Bar[Int]]]
}
1 change: 1 addition & 0 deletions test/files/pos/t10213.flags
@@ -0,0 +1 @@
-Xsource:2.13
53 changes: 53 additions & 0 deletions test/files/pos/t10213.scala
@@ -0,0 +1,53 @@
import scala.language.higherKinds

final case class Coproduct[F[_], G[_], A](run: Either[F[A], G[A]])

object Coproduct {

sealed trait Builder {
type Out[_]
}

sealed trait :++:[F[_], G[_]] extends Builder {
type Out[A] = Coproduct[F, G, A]
}

sealed trait :+:[F[_], B <: Builder] extends Builder {
type Out[A] = Coproduct[F, B#Out, A]
}
}

trait Inject[F[_], H[_]] {
def inj[A](fa: F[A]): H[A]
}

object Inject {
import Coproduct._

implicit def reflexiveInject[F[_]]: Inject[F, F] =
new Inject[F, F] {
def inj[A](fa: F[A]): F[A] = fa
}

implicit def injectLeft[F[_], G[_]]: Inject[F, (F :++: G)#Out] =
new Inject[F, (F :++: G)#Out] {
def inj[A](fa: F[A]): Coproduct[F, G, A] = Coproduct(Left(fa))
}

implicit def injectRight[F[_], G[_], H[_]](implicit I: Inject[F, H]): Inject[F, (G :++: H)#Out] =
new Inject[F, (G :++: H)#Out] {
def inj[A](fa: F[A]): Coproduct[G, H , A] = Coproduct(Right(I.inj(fa)))
}
}

object Test1 {
import Coproduct.{:++:, :+:}

class Foo[A]
class Bar[A]
class Baz[A]

implicitly[Inject[Baz, (Foo :+: Bar :++: Baz)#Out]]

implicitly[Inject[Baz, ({ type Out[A] = Coproduct[Foo, ({ type Out1[a] = Coproduct[Bar, Baz, a] })#Out1, A] })#Out]]
}
1 change: 1 addition & 0 deletions test/files/pos/t10238.flags
@@ -0,0 +1 @@
-Xsource:2.13
36 changes: 36 additions & 0 deletions test/files/pos/t10238.scala
@@ -0,0 +1,36 @@
object Test {

// Data types

type Id[A] = A

class MaybeT[F[_], A]

type Maybe[A] = MaybeT[Id, A]

type MaybeMaybe[A] = MaybeT[Maybe, A]


// Typeclass

trait Monad[F[_]]


// Instances

implicit val monadId: Monad[Id] = ???

implicit def monadMaybeT[F[_]: Monad]: Monad[({ type λ[A] = MaybeT[F, A] })#λ] = ???

implicit val monadOption: Monad[Option] = ???


// Implicit search tests

implicitly[Monad[Id]]
implicitly[Monad[({ type λ[A] = A })#λ]]
implicitly[Monad[Maybe]]
implicitly[Monad[({ type λ[A] = MaybeT[Id, A] })#λ]]
implicitly[Monad[MaybeMaybe]]
implicitly[Monad[({ type λ[A] = MaybeT[Maybe, A] })#λ]]
}
1 change: 1 addition & 0 deletions test/files/pos/t10372.flags
@@ -0,0 +1 @@
-Xsource:2.13
16 changes: 16 additions & 0 deletions test/files/pos/t10372.scala
@@ -0,0 +1,16 @@
import scala.language.higherKinds
import scala.language.implicitConversions

object Test {
class Expected[T, Func[_]]
implicit def conv[T, Func[_]](i : Int) : Expected[T, Func] = ???
type FuncId[T] = T

object DoesNotCompile {
class Bla {
type Alias[T] = Expected[T, FuncId]
def bla[T](expected : Alias[T]) : Unit = {}
}
(new Bla).bla(2)
}
}
1 change: 1 addition & 0 deletions test/files/pos/t6895b-2.flags
@@ -0,0 +1 @@
-Xsource:2.13
39 changes: 39 additions & 0 deletions test/files/pos/t6895b-2.scala
@@ -0,0 +1,39 @@
trait Foo[F[_]]
trait Bar[F[_], A]

trait Or[A, B]

class Test {
implicit def orFoo[A]: Foo[({type L[X] = Or[A, X]})#L] = ???
implicit def barFoo[F[_]](implicit f: Foo[F]): Foo[({type L[X] = Bar[F, X]})#L] = ???

// Now we can define a couple of type aliases:
type StringOr[X] = Or[String, X]
type BarStringOr[X] = Bar[StringOr, X]

// ok
implicitly[Foo[BarStringOr]]
barFoo[StringOr](null) : Foo[BarStringOr]
barFoo(null) : Foo[BarStringOr]

// nok
implicitly[Foo[({type L[X] = Bar[StringOr, X]})#L]]
// Let's write the application explicitly, and then
// compile with just this line enabled and -explaintypes.
barFoo(null) : Foo[({type L[X] = Bar[StringOr, X]})#L]

// Foo[[X]Bar[F,X]] <: Foo[[X]Bar[[X]Or[String,X],X]]?
// Bar[[X]Or[String,X],X] <: Bar[F,X]?
// F[_] <: Or[String,_]?
// false
// false
// false

// Note that the type annotation above is typechecked as
// Foo[[X]Bar[[X]Or[String,X],X]], ie the type alias `L`
// is eta expanded.
//
// This is done so that it does not escape its defining scope.
// However, one this is done, higher kinded inference
// no longer is able to unify F with `StringOr` (scala/bug#2712)
}
8 changes: 8 additions & 0 deletions test/files/run/hk-typevar-unification.check
@@ -0,0 +1,8 @@
Some(1)
Some(1)
Some((hi,5))
Some((hi,5))
Some(X)
Some(X)
Some(X)
Some(X)
1 change: 1 addition & 0 deletions test/files/run/hk-typevar-unification.flags
@@ -0,0 +1 @@
-Xsource:2.13

0 comments on commit 33478bd

Please sign in to comment.