Skip to content

Commit 0b36ee6

Browse files
committed
Higher-kinded type variable unification.
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.
1 parent e1e8d05 commit 0b36ee6

23 files changed

+344
-6
lines changed

project/ScalaOptionParser.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ object ScalaOptionParser {
8585
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",
8686
"-Xno-forwarders", "-Xno-patmat-analysis", "-Xno-uescape", "-Xnojline", "-Xprint-pos", "-Xprint-types", "-Xprompt", "-Xresident", "-Xshow-phases", "-Xstrict-inference", "-Xverify", "-Y",
8787
"-Ybreak-cycles", "-Ydebug", "-Ycompact-trees", "-YdisableFlatCpCaching", "-Ydoc-debug",
88-
"-Yide-debug", "-Yinfer-argument-types",
88+
"-Yhk-typevar-unification", "-Yide-debug", "-Yinfer-argument-types",
8989
"-Yissue-debug", "-Ylog-classpath", "-Ymacro-debug-lite", "-Ymacro-debug-verbose", "-Ymacro-no-expand",
9090
"-Yno-completion", "-Yno-generic-signatures", "-Yno-imports", "-Yno-predef",
9191
"-Yoverride-objects", "-Yoverride-vars", "-Ypatmat-debug", "-Yno-adapted-args", "-Ypartial-unification", "-Ypos-debug", "-Ypresentation-debug",

src/compiler/scala/tools/nsc/settings/ScalaSettings.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ trait ScalaSettings extends AbsScalaSettings
3030
protected def defaultClasspath = sys.env.getOrElse("CLASSPATH", ".")
3131

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

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

225226
val exposeEmptyPackage = BooleanSetting ("-Yexpose-empty-package", "Internal only: expose the empty package.").internalOnly()

src/reflect/mima-filters/2.12.0.forwards.excludes

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ ProblemFilters.exclude[MissingClassProblem]("scala.reflect.io.FileZipArchive$Laz
1313
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.io.ZipArchive.closeZipFile")
1414
ProblemFilters.exclude[MissingClassProblem]("scala.reflect.io.FileZipArchive$LeakyEntry")
1515

16-
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.runtime.SynchronizedSymbols#SynchronizedSymbol.exists")
16+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.runtime.SynchronizedSymbols#SynchronizedSymbol.exists")
17+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.runtime.Settings.YhkTypevarUnification")

src/reflect/scala/reflect/internal/Types.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -3325,7 +3325,7 @@ trait Types
33253325
)
33263326
override def etaExpand: Type = (
33273327
if (!isHigherKinded) this
3328-
else logResult("Normalizing HK $this")(typeFun(params, applyArgs(params map (_.typeConstructor))))
3328+
else logResult(s"Normalizing HK $this")(typeFun(params, applyArgs(params map (_.typeConstructor))))
33293329
)
33303330
override def typeSymbol = origin.typeSymbol
33313331

src/reflect/scala/reflect/internal/settings/MutableSettings.scala

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ abstract class MutableSettings extends AbsSettings {
5353
def printtypes: BooleanSetting
5454
def uniqid: BooleanSetting
5555
def verbose: BooleanSetting
56+
def YhkTypevarUnification: BooleanSetting
5657
def YpartialUnification: BooleanSetting
5758
def Yvirtpatmat: BooleanSetting
5859

src/reflect/scala/reflect/internal/tpe/TypeComparers.scala

+27-2
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,32 @@ trait TypeComparers {
365365

366366
// @assume tp1.isHigherKinded || tp2.isHigherKinded
367367
def isHKSubType(tp1: Type, tp2: Type, depth: Depth): Boolean = {
368-
def isSub(ntp1: Type, ntp2: Type) = (ntp1.withoutAnnotations, ntp2.withoutAnnotations) match {
368+
369+
def isSubHKTypeVar(tp1: Type, tp2: Type) = (tp1, tp2) match {
370+
case (tv1 @ TypeVar(_, _), tv2 @ TypeVar(_, _)) =>
371+
reporter.warning(tv1.typeSymbol.pos,
372+
sm"""|compiler bug: Unexpected code path: testing two type variables for subtype relation:
373+
| ${tv1} <:< ${tv2}
374+
|Please report bug at https://github.com/scala/bug/issues
375+
""".trim)
376+
false
377+
case (tp1, tv2 @ TypeVar(_, _)) =>
378+
val ntp1 = tp1.normalize
379+
(tv2.params corresponds ntp1.typeParams)(methodHigherOrderTypeParamsSubVariance) &&
380+
{ tv2.addLoBound(ntp1); true }
381+
case (tv1 @ TypeVar(_, _), tp2) =>
382+
val ntp2 = tp2.normalize
383+
(ntp2.typeParams corresponds tv1.params)(methodHigherOrderTypeParamsSubVariance) &&
384+
{ tv1.addHiBound(ntp2); true }
385+
case _ =>
386+
false
387+
}
388+
389+
def isSub(tp1: Type, tp2: Type) =
390+
settings.YhkTypevarUnification && isSubHKTypeVar(tp1, tp2) ||
391+
isSub2(tp1.normalize, tp2.normalize) // @M! normalize reduces higher-kinded case to PolyType's
392+
393+
def isSub2(ntp1: Type, ntp2: Type) = (ntp1, ntp2) match {
369394
case (TypeRef(_, AnyClass, _), _) => false // avoid some warnings when Nothing/Any are on the other side
370395
case (_, TypeRef(_, NothingClass, _)) => false
371396
case (pt1: PolyType, pt2: PolyType) => isPolySubType(pt1, pt2) // @assume both .isHigherKinded (both normalized to PolyType)
@@ -381,7 +406,7 @@ trait TypeComparers {
381406
|| (if (isNoArgStaticClassTypeRef(tp1) && isNoArgStaticClassTypeRef(tp2))
382407
tp1.typeSymbolDirect.isNonBottomSubClass(tp2.typeSymbolDirect) // OPT faster than comparing eta-expanded types
383408
else
384-
isSub(tp1.normalize, tp2.normalize) && annotationsConform(tp1, tp2) // @M! normalize reduces higher-kinded case to PolyType's
409+
isSub(tp1.withoutAnnotations, tp2.withoutAnnotations) && annotationsConform(tp1, tp2)
385410
)
386411
)
387412
}

src/reflect/scala/reflect/runtime/Settings.scala

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ private[reflect] class Settings extends MutableSettings {
4747
val printtypes = new BooleanSetting(false)
4848
val uniqid = new BooleanSetting(false)
4949
val verbose = new BooleanSetting(false)
50+
val YhkTypevarUnification = new BooleanSetting(false)
5051
val YpartialUnification = new BooleanSetting(false)
5152
val Yvirtpatmat = new BooleanSetting(false)
5253

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
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).
2+
[_]Foo[_]'s type parameters do not match type F's expected parameters:
3+
type _ (in class Foo) is invariant, but type _ is declared covariant
4+
g(tcFoo)
5+
^
6+
hk-typevar-unification.scala:16: error: type mismatch;
7+
found : TC[Foo]
8+
required: TC[F]
9+
g(tcFoo)
10+
^
11+
two errors found
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Yhk-typevar-unification
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
trait TC[F[_]]
2+
class A
3+
class Foo[_]
4+
5+
object Test {
6+
7+
def f[F[_ <: A]](tc: TC[F]): Unit = ()
8+
def g[F[+_]] (tc: TC[F]): Unit = ()
9+
10+
val tcFoo: TC[Foo] = new TC[Foo] {}
11+
12+
// incompatible bounds
13+
f(tcFoo)
14+
15+
// incompatible variance
16+
g(tcFoo)
17+
}

test/files/pos/t10197.flags

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Yhk-typevar-unification

test/files/pos/t10197.scala

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import scala.language.higherKinds
2+
3+
final case class Getter[S, A](get: S => A)
4+
5+
final case class Wrap[F[_], A](value: F[A])
6+
7+
object Wrap {
8+
// Helper to defer specifying second argument to Wrap.
9+
// Basically a type lambda specialized for Wrap.
10+
// Wr[F]#ap[A] =:= Wrap[F, A]
11+
type Wr[F[_]] = { type ap[A] = Wrap[F, A] }
12+
13+
implicit def unwrapper[F[_], A]: Getter[Wrap[F, A], F[A]] =
14+
Getter(w => w.value)
15+
}
16+
17+
object Test {
18+
import Wrap._
19+
20+
type Foo[A] = List[A]
21+
type Bar[A] = String
22+
23+
type WrapFoo1[A] = Wrap[Foo, A]
24+
type WrapBar1[A] = Wrap[Bar, A]
25+
26+
implicitly[Getter[WrapFoo1[Int], Foo[Int]]]
27+
implicitly[Getter[WrapBar1[Int], Bar[Int]]]
28+
29+
type WrapFoo2[A] = Wr[Foo]#ap[A]
30+
type WrapBar2[A] = Wr[Bar]#ap[A]
31+
32+
// here's evidence that the new types are the same as the old ones
33+
implicitly[WrapFoo2[Int] =:= WrapFoo1[Int]]
34+
implicitly[WrapBar2[Int] =:= WrapBar1[Int]]
35+
36+
implicitly[Getter[WrapFoo2[Int], Foo[Int]]]
37+
implicitly[Getter[WrapBar2[Int], Bar[Int]]]
38+
}

test/files/pos/t10213.flags

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Yhk-typevar-unification

test/files/pos/t10213.scala

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import scala.language.higherKinds
2+
3+
final case class Coproduct[F[_], G[_], A](run: Either[F[A], G[A]])
4+
5+
object Coproduct {
6+
7+
sealed trait Builder {
8+
type Out[_]
9+
}
10+
11+
sealed trait :++:[F[_], G[_]] extends Builder {
12+
type Out[A] = Coproduct[F, G, A]
13+
}
14+
15+
sealed trait :+:[F[_], B <: Builder] extends Builder {
16+
type Out[A] = Coproduct[F, B#Out, A]
17+
}
18+
}
19+
20+
trait Inject[F[_], H[_]] {
21+
def inj[A](fa: F[A]): H[A]
22+
}
23+
24+
object Inject {
25+
import Coproduct._
26+
27+
implicit def reflexiveInject[F[_]]: Inject[F, F] =
28+
new Inject[F, F] {
29+
def inj[A](fa: F[A]): F[A] = fa
30+
}
31+
32+
implicit def injectLeft[F[_], G[_]]: Inject[F, (F :++: G)#Out] =
33+
new Inject[F, (F :++: G)#Out] {
34+
def inj[A](fa: F[A]): Coproduct[F, G, A] = Coproduct(Left(fa))
35+
}
36+
37+
implicit def injectRight[F[_], G[_], H[_]](implicit I: Inject[F, H]): Inject[F, (G :++: H)#Out] =
38+
new Inject[F, (G :++: H)#Out] {
39+
def inj[A](fa: F[A]): Coproduct[G, H , A] = Coproduct(Right(I.inj(fa)))
40+
}
41+
}
42+
43+
object Test1 {
44+
import Coproduct.{:++:, :+:}
45+
46+
class Foo[A]
47+
class Bar[A]
48+
class Baz[A]
49+
50+
implicitly[Inject[Baz, (Foo :+: Bar :++: Baz)#Out]]
51+
52+
implicitly[Inject[Baz, ({ type Out[A] = Coproduct[Foo, ({ type Out1[a] = Coproduct[Bar, Baz, a] })#Out1, A] })#Out]]
53+
}

test/files/pos/t10238.flags

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Yhk-typevar-unification

test/files/pos/t10238.scala

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
object Test {
2+
3+
// Data types
4+
5+
type Id[A] = A
6+
7+
class MaybeT[F[_], A]
8+
9+
type Maybe[A] = MaybeT[Id, A]
10+
11+
type MaybeMaybe[A] = MaybeT[Maybe, A]
12+
13+
14+
// Typeclass
15+
16+
trait Monad[F[_]]
17+
18+
19+
// Instances
20+
21+
implicit val monadId: Monad[Id] = ???
22+
23+
implicit def monadMaybeT[F[_]: Monad]: Monad[({ type λ[A] = MaybeT[F, A] })#λ] = ???
24+
25+
implicit val monadOption: Monad[Option] = ???
26+
27+
28+
// Implicit search tests
29+
30+
implicitly[Monad[Id]]
31+
implicitly[Monad[({ type λ[A] = A })#λ]]
32+
implicitly[Monad[Maybe]]
33+
implicitly[Monad[({ type λ[A] = MaybeT[Id, A] })#λ]]
34+
implicitly[Monad[MaybeMaybe]]
35+
implicitly[Monad[({ type λ[A] = MaybeT[Maybe, A] })#λ]]
36+
}

test/files/pos/t10372.flags

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Yhk-typevar-unification

test/files/pos/t10372.scala

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import scala.language.higherKinds
2+
import scala.language.implicitConversions
3+
4+
object Test {
5+
class Expected[T, Func[_]]
6+
implicit def conv[T, Func[_]](i : Int) : Expected[T, Func] = ???
7+
type FuncId[T] = T
8+
9+
object DoesNotCompile {
10+
class Bla {
11+
type Alias[T] = Expected[T, FuncId]
12+
def bla[T](expected : Alias[T]) : Unit = {}
13+
}
14+
(new Bla).bla(2)
15+
}
16+
}

test/files/pos/t6895b-2.flags

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-Yhk-typevar-unification
2+

test/files/pos/t6895b-2.scala

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
trait Foo[F[_]]
2+
trait Bar[F[_], A]
3+
4+
trait Or[A, B]
5+
6+
class Test {
7+
implicit def orFoo[A]: Foo[({type L[X] = Or[A, X]})#L] = ???
8+
implicit def barFoo[F[_]](implicit f: Foo[F]): Foo[({type L[X] = Bar[F, X]})#L] = ???
9+
10+
// Now we can define a couple of type aliases:
11+
type StringOr[X] = Or[String, X]
12+
type BarStringOr[X] = Bar[StringOr, X]
13+
14+
// ok
15+
implicitly[Foo[BarStringOr]]
16+
barFoo[StringOr](null) : Foo[BarStringOr]
17+
barFoo(null) : Foo[BarStringOr]
18+
19+
// nok
20+
implicitly[Foo[({type L[X] = Bar[StringOr, X]})#L]]
21+
// Let's write the application explicitly, and then
22+
// compile with just this line enabled and -explaintypes.
23+
barFoo(null) : Foo[({type L[X] = Bar[StringOr, X]})#L]
24+
25+
// Foo[[X]Bar[F,X]] <: Foo[[X]Bar[[X]Or[String,X],X]]?
26+
// Bar[[X]Or[String,X],X] <: Bar[F,X]?
27+
// F[_] <: Or[String,_]?
28+
// false
29+
// false
30+
// false
31+
32+
// Note that the type annotation above is typechecked as
33+
// Foo[[X]Bar[[X]Or[String,X],X]], ie the type alias `L`
34+
// is eta expanded.
35+
//
36+
// This is done so that it does not escape its defining scope.
37+
// However, one this is done, higher kinded inference
38+
// no longer is able to unify F with `StringOr` (scala/bug#2712)
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Some(1)
2+
Some(1)
3+
Some((hi,5))
4+
Some((hi,5))
5+
Some(X)
6+
Some(X)
7+
Some(X)
8+
Some(X)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Yhk-typevar-unification

0 commit comments

Comments
 (0)