diff --git a/project/ScalaOptionParser.scala b/project/ScalaOptionParser.scala index 0208921959df..9b4557243a9f 100644 --- a/project/ScalaOptionParser.scala +++ b/project/ScalaOptionParser.scala @@ -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", diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 9695d08c9174..88848001f90a 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -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]() @@ -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() diff --git a/src/reflect/mima-filters/2.12.0.forwards.excludes b/src/reflect/mima-filters/2.12.0.forwards.excludes index 0f4142213f96..44470ea26ee0 100644 --- a/src/reflect/mima-filters/2.12.0.forwards.excludes +++ b/src/reflect/mima-filters/2.12.0.forwards.excludes @@ -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") \ No newline at end of file +ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.runtime.SynchronizedSymbols#SynchronizedSymbol.exists") +ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.runtime.Settings.YhkTypevarUnification") diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index a4413e0d479b..80a268925390 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -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 diff --git a/src/reflect/scala/reflect/internal/settings/MutableSettings.scala b/src/reflect/scala/reflect/internal/settings/MutableSettings.scala index ab933ae61709..b37956ba6467 100644 --- a/src/reflect/scala/reflect/internal/settings/MutableSettings.scala +++ b/src/reflect/scala/reflect/internal/settings/MutableSettings.scala @@ -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 diff --git a/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala b/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala index 37d05c08a7c2..828e5e1af8f9 100644 --- a/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala +++ b/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala @@ -365,7 +365,28 @@ 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(_, _)) => + (tv2.params corresponds tv1.params)(methodHigherOrderTypeParamsSubVariance) && + { tv1.addHiBound(tv2); tv2.addLoBound(tv1); true } + 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.YhkTypevarUnification && 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) @@ -381,7 +402,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) ) ) } diff --git a/src/reflect/scala/reflect/runtime/Settings.scala b/src/reflect/scala/reflect/runtime/Settings.scala index 2d8bacd3b2e0..891307c434e6 100644 --- a/src/reflect/scala/reflect/runtime/Settings.scala +++ b/src/reflect/scala/reflect/runtime/Settings.scala @@ -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) diff --git a/test/files/neg/hk-typevar-unification.check b/test/files/neg/hk-typevar-unification.check new file mode 100644 index 000000000000..fa34f72345cf --- /dev/null +++ b/test/files/neg/hk-typevar-unification.check @@ -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 diff --git a/test/files/neg/hk-typevar-unification.flags b/test/files/neg/hk-typevar-unification.flags new file mode 100644 index 000000000000..11484d1821ce --- /dev/null +++ b/test/files/neg/hk-typevar-unification.flags @@ -0,0 +1 @@ +-Yhk-typevar-unification diff --git a/test/files/neg/hk-typevar-unification.scala b/test/files/neg/hk-typevar-unification.scala new file mode 100644 index 000000000000..cbeb79296385 --- /dev/null +++ b/test/files/neg/hk-typevar-unification.scala @@ -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) +} diff --git a/test/files/pos/t10197.flags b/test/files/pos/t10197.flags new file mode 100644 index 000000000000..11484d1821ce --- /dev/null +++ b/test/files/pos/t10197.flags @@ -0,0 +1 @@ +-Yhk-typevar-unification diff --git a/test/files/pos/t10197.scala b/test/files/pos/t10197.scala new file mode 100644 index 000000000000..54d7d6db20b7 --- /dev/null +++ b/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]]] +} diff --git a/test/files/pos/t10213.flags b/test/files/pos/t10213.flags new file mode 100644 index 000000000000..11484d1821ce --- /dev/null +++ b/test/files/pos/t10213.flags @@ -0,0 +1 @@ +-Yhk-typevar-unification diff --git a/test/files/pos/t10213.scala b/test/files/pos/t10213.scala new file mode 100644 index 000000000000..7f5a44197eef --- /dev/null +++ b/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]] +} diff --git a/test/files/pos/t10238.flags b/test/files/pos/t10238.flags new file mode 100644 index 000000000000..11484d1821ce --- /dev/null +++ b/test/files/pos/t10238.flags @@ -0,0 +1 @@ +-Yhk-typevar-unification diff --git a/test/files/pos/t10238.scala b/test/files/pos/t10238.scala new file mode 100644 index 000000000000..4fa06af7b5cb --- /dev/null +++ b/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] })#λ]] +} diff --git a/test/files/pos/t10372.flags b/test/files/pos/t10372.flags new file mode 100644 index 000000000000..11484d1821ce --- /dev/null +++ b/test/files/pos/t10372.flags @@ -0,0 +1 @@ +-Yhk-typevar-unification diff --git a/test/files/pos/t10372.scala b/test/files/pos/t10372.scala new file mode 100644 index 000000000000..9923457ebc57 --- /dev/null +++ b/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) + } +} diff --git a/test/files/pos/t6895b-2.flags b/test/files/pos/t6895b-2.flags new file mode 100644 index 000000000000..b177cdc0d1ca --- /dev/null +++ b/test/files/pos/t6895b-2.flags @@ -0,0 +1,2 @@ +-Yhk-typevar-unification + diff --git a/test/files/pos/t6895b-2.scala b/test/files/pos/t6895b-2.scala new file mode 100644 index 000000000000..3be68cd3bf9a --- /dev/null +++ b/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) +} diff --git a/test/files/run/hk-typevar-unification.check b/test/files/run/hk-typevar-unification.check new file mode 100644 index 000000000000..3b7cea967342 --- /dev/null +++ b/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) diff --git a/test/files/run/hk-typevar-unification.flags b/test/files/run/hk-typevar-unification.flags new file mode 100644 index 000000000000..11484d1821ce --- /dev/null +++ b/test/files/run/hk-typevar-unification.flags @@ -0,0 +1 @@ +-Yhk-typevar-unification diff --git a/test/files/run/hk-typevar-unification.scala b/test/files/run/hk-typevar-unification.scala new file mode 100644 index 000000000000..a8d895f2f948 --- /dev/null +++ b/test/files/run/hk-typevar-unification.scala @@ -0,0 +1,83 @@ +import scala.language.higherKinds + +trait Forall[F[_]] { + def instantiate[A]: F[A] +} + +object Forall { + implicit class Ops[F[_]](f: Forall[F]) { + def apply[A]: F[A] = f.instantiate[A] + } +} + +trait Forall2[F[_, _]] { + def instantiate[A, B]: F[A, B] +} + +object Forall2 { + implicit class Ops[F[_, _]](f: Forall2[F]) { + def apply[A, B]: F[A, B] = f.instantiate[A, B] + } +} + +trait FlatMap[F[_]] { + def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] +} + +object FlatMap { + implicit val optionInstance: FlatMap[Option] = new FlatMap[Option] { + def flatMap[A, B](fa: Option[A])(f: A => Option[B]) = fa.flatMap(f) + } +} + +object Test extends App { + + // natural transformation + type ~>[F[_], G[_]] = Forall[({ type L[A] = F[A] => G[A] })#L] + + // binatural transformation + type ~~>[F[_, _], G[_, _]] = Forall2[({ type L[A, B] = F[A, B] => G[A, B] })#L] + + + type RightAction[G[_], F[_, _]] = Forall2[({ type L[A, B] = (G[A], F[A, B]) => G[B] })#L] + type LeftAction[G[_], F[_, _]] = Forall2[({ type L[A, B] = (F[A, B], G[B]) => G[A] })#L] + + + val headOpt = new (List ~> Option) { + def instantiate[A]: List[A] => Option[A] = _.headOption + } + + // tests that implicit Forall.Ops is found + println(headOpt.apply(List(1, 2, 3))) + println(headOpt[Int](List(1, 2, 3))) + + val someEntry = new (Map ~~> ({ type L[K, V] = Option[(K, V)] })#L) { + def instantiate[K, V]: Map[K, V] => Option[(K, V)] = _.headOption + } + + // tests that implicit Forall2.Ops is found + println(someEntry.apply(Map(("hi", 5)))) + println(someEntry[String, Int](Map(("hi", 5)))) + + def kleisliPostCompose[F[_], Z](implicit F: FlatMap[F]) = + new RightAction[({ type L[A] = Z => F[A] })#L, ({ type L[A, B] = A => F[B] })#L] { + def instantiate[A, B]: (Z => F[A], A => F[B]) => (Z => F[B]) = (f, g) => (z => F.flatMap(f(z))(g)) + } + + def kleisliPreCompose[F[_], C](implicit F: FlatMap[F]) = + new LeftAction[({ type L[B] = B => F[C] })#L, ({ type L[A, B] = A => F[B] })#L] { + def instantiate[A, B]: (A => F[B], B => F[C]) => (A => F[C]) = (f, g) => (a => F.flatMap(f(a))(g)) + } + + def parseInt(s: String): Option[Int] = Some(42) + def toChar(i: Int): Option[Char] = Some('X') + + val ra = kleisliPostCompose[Option, String] + val la = kleisliPreCompose[Option, Char] + + // tests that implicit Forall2.Ops is found + println( ra.apply(parseInt(_), toChar(_)).apply("") ) + println( ra[Int, Char](parseInt(_), toChar(_))("") ) + println( la.apply(parseInt(_), toChar(_))("") ) + println( la[String, Int](parseInt(_), toChar(_))("") ) +}