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 a compiler flag
-Yhk-typevar-unification.

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 14, 2017
1 parent e1e8d05 commit 468fbdc
Show file tree
Hide file tree
Showing 23 changed files with 335 additions and 6 deletions.
2 changes: 1 addition & 1 deletion project/ScalaOptionParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ object ScalaOptionParser {
private def booleanSettingNames = List("-X", "-Xcheckinit", "-Xdev", "-Xdisable-assertions", "-Xexperimental", "-Xfatal-warnings", "-Xfull-lubs", "-Xfuture", "-Xlog-free-terms", "-Xlog-free-types", "-Xlog-implicit-conversions", "-Xlog-implicits", "-Xlog-reflective-calls",
"-Xno-forwarders", "-Xno-patmat-analysis", "-Xno-uescape", "-Xnojline", "-Xprint-pos", "-Xprint-types", "-Xprompt", "-Xresident", "-Xshow-phases", "-Xstrict-inference", "-Xverify", "-Y",
"-Ybreak-cycles", "-Ydebug", "-Ycompact-trees", "-YdisableFlatCpCaching", "-Ydoc-debug",
"-Yide-debug", "-Yinfer-argument-types",
"-Yhk-typevar-unification", "-Yide-debug", "-Yinfer-argument-types",
"-Yissue-debug", "-Ylog-classpath", "-Ymacro-debug-lite", "-Ymacro-debug-verbose", "-Ymacro-no-expand",
"-Yno-completion", "-Yno-generic-signatures", "-Yno-imports", "-Yno-predef",
"-Yoverride-objects", "-Yoverride-vars", "-Ypatmat-debug", "-Yno-adapted-args", "-Ypartial-unification", "-Ypos-debug", "-Ypresentation-debug",
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ trait ScalaSettings extends AbsScalaSettings
protected def defaultClasspath = sys.env.getOrElse("CLASSPATH", ".")

/** Enabled under -Xexperimental. */
protected def experimentalSettings = List[BooleanSetting](YpartialUnification)
protected def experimentalSettings = List[BooleanSetting](YhkTypevarUnification, YpartialUnification)

/** Enabled under -Xfuture. */
protected def futureSettings = List[BooleanSetting]()
Expand Down Expand Up @@ -220,6 +220,7 @@ trait ScalaSettings extends AbsScalaSettings
val YmethodInfer = BooleanSetting ("-Yinfer-argument-types", "Infer types for arguments of overridden methods.")
val YdisableFlatCpCaching = BooleanSetting ("-YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.")
val YpartialUnification = BooleanSetting ("-Ypartial-unification", "Enable partial unification in type constructor inference")
val YhkTypevarUnification = BooleanSetting ("-Yhk-typevar-unification", "Enable unification of higher-kinded type variables with type constructors")
val Yvirtpatmat = BooleanSetting ("-Yvirtpatmat", "Enable pattern matcher virtualization")

val exposeEmptyPackage = BooleanSetting ("-Yexpose-empty-package", "Internal only: expose the empty package.").internalOnly()
Expand Down
3 changes: 2 additions & 1 deletion src/reflect/mima-filters/2.12.0.forwards.excludes
Original file line number Diff line number Diff line change
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.YhkTypevarUnification")
2 changes: 1 addition & 1 deletion src/reflect/scala/reflect/internal/Types.scala
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ abstract class MutableSettings extends AbsSettings {
def printtypes: BooleanSetting
def uniqid: BooleanSetting
def verbose: BooleanSetting
def YhkTypevarUnification: BooleanSetting
def YpartialUnification: BooleanSetting
def Yvirtpatmat: BooleanSetting

Expand Down
20 changes: 18 additions & 2 deletions src/reflect/scala/reflect/internal/tpe/TypeComparers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,23 @@ 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 isSub(tp1: Type, tp2: Type) = (tp1.withoutAnnotations, tp2.withoutAnnotations) match {
case (tv1 @ TypeVar(_, _), tv2 @ TypeVar(_, _)) if settings.YhkTypevarUnification =>
(tv2.params corresponds tv1.params)(methodHigherOrderTypeParamsSubVariance) &&
{ tv1.addHiBound(tv2); tv2.addLoBound(tv1); true }
case (tp1, tv2 @ TypeVar(_, _)) if settings.YhkTypevarUnification =>
val ntp1 = tp1.normalize
(tv2.params corresponds ntp1.typeParams)(methodHigherOrderTypeParamsSubVariance) &&
{ tv2.addLoBound(ntp1); true }
case (tv1 @ TypeVar(_, _), tp2) if settings.YhkTypevarUnification =>
val ntp2 = tp2.normalize
(ntp2.typeParams corresponds tv1.params)(methodHigherOrderTypeParamsSubVariance) &&
{ tv1.addHiBound(ntp2); true }
case (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 +397,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, tp2) && annotationsConform(tp1, tp2)
)
)
}
Expand Down
1 change: 1 addition & 0 deletions src/reflect/scala/reflect/runtime/Settings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ private[reflect] class Settings extends MutableSettings {
val printtypes = new BooleanSetting(false)
val uniqid = new BooleanSetting(false)
val verbose = new BooleanSetting(false)
val YhkTypevarUnification = new BooleanSetting(false)
val YpartialUnification = new BooleanSetting(false)
val Yvirtpatmat = new BooleanSetting(false)

Expand Down
11 changes: 11 additions & 0 deletions test/files/neg/hk-typevar-unification.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
hk-typevar-unification.scala:16: error: inferred kinds of the type arguments ([_]Foo[_]) do not conform to the expected kinds of the type parameters (type F).
[_]Foo[_]'s type parameters do not match type F's expected parameters:
type _ (in class Foo) is invariant, but type _ is declared covariant
g(tcFoo)
^
hk-typevar-unification.scala:16: error: type mismatch;
found : TC[Foo]
required: TC[F]
g(tcFoo)
^
two errors found
1 change: 1 addition & 0 deletions test/files/neg/hk-typevar-unification.flags
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-Yhk-typevar-unification
17 changes: 17 additions & 0 deletions test/files/neg/hk-typevar-unification.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
trait TC[F[_]]
class A
class Foo[_]

object Test {

def f[F[_ <: A]](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/t10197.flags
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-Yhk-typevar-unification
38 changes: 38 additions & 0 deletions test/files/pos/t10197.scala
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-Yhk-typevar-unification
53 changes: 53 additions & 0 deletions test/files/pos/t10213.scala
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-Yhk-typevar-unification
36 changes: 36 additions & 0 deletions test/files/pos/t10238.scala
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-Yhk-typevar-unification
16 changes: 16 additions & 0 deletions test/files/pos/t10372.scala
Original file line number Diff line number Diff line change
@@ -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)
}
}
2 changes: 2 additions & 0 deletions test/files/pos/t6895b-2.flags
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-Yhk-typevar-unification

39 changes: 39 additions & 0 deletions test/files/pos/t6895b-2.scala
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-Yhk-typevar-unification
Loading

0 comments on commit 468fbdc

Please sign in to comment.