aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/dotty/tools/dotc/core/ConstraintHandling.scala37
-rw-r--r--src/dotty/tools/dotc/core/Symbols.scala1
-rw-r--r--src/dotty/tools/dotc/core/TypeApplications.scala6
-rw-r--r--src/dotty/tools/dotc/core/TypeComparer.scala53
-rw-r--r--src/dotty/tools/dotc/core/TypeParamInfo.scala3
-rw-r--r--src/dotty/tools/dotc/core/Types.scala2
-rw-r--r--tests/neg/i94-nada.scala11
-rw-r--r--tests/pickling/i94-nada.scala4
-rw-r--r--tests/pos/i94-nada.scala4
-rw-r--r--tests/pos/t2712-1.scala9
-rw-r--r--tests/pos/t2712-4.scala17
-rw-r--r--tests/pos/t2712-7.scala15
-rw-r--r--tests/pos/t5683.scala23
13 files changed, 163 insertions, 22 deletions
diff --git a/src/dotty/tools/dotc/core/ConstraintHandling.scala b/src/dotty/tools/dotc/core/ConstraintHandling.scala
index d21f62440..1c3bd7384 100644
--- a/src/dotty/tools/dotc/core/ConstraintHandling.scala
+++ b/src/dotty/tools/dotc/core/ConstraintHandling.scala
@@ -35,6 +35,11 @@ trait ConstraintHandling {
/** If the constraint is frozen we cannot add new bounds to the constraint. */
protected var frozenConstraint = false
+ /** We are currently comparing lambdas. Used as a flag for
+ * optimization: when `false`, no need to do an expensive `pruneLambdaParams`
+ */
+ protected var comparingLambdas = false
+
private def addOneBound(param: PolyParam, bound: Type, isUpper: Boolean): Boolean =
!constraint.contains(param) || {
def occursIn(bound: Type): Boolean = {
@@ -289,6 +294,34 @@ trait ConstraintHandling {
checkPropagated(s"added $description") {
addConstraintInvocations += 1
+ /** When comparing lambdas we might get constraints such as
+ * `A <: X0` or `A = List[X0]` where `A` is a constrained parameter
+ * and `X0` is a lambda parameter. The constraint for `A` is not allowed
+ * to refer to such a lambda parameter because the lambda parameter is
+ * not visible where `A` is defined. Consequently, we need to
+ * approximate the bound so that the lambda parameter does not appear in it.
+ * Test case in neg/i94-nada.scala. This test crashes with an illegal instance
+ * error when the rest of the SI-2712 fix is applied but `pruneLambdaParams` is
+ * missing.
+ */
+ def pruneLambdaParams(tp: Type) =
+ if (comparingLambdas) {
+ val approx = new ApproximatingTypeMap {
+ def apply(t: Type): Type = t match {
+ case t @ PolyParam(tl: TypeLambda, n) =>
+ val effectiveVariance = if (fromBelow) -variance else variance
+ val bounds = tl.paramBounds(n)
+ if (effectiveVariance > 0) bounds.hi
+ else if (effectiveVariance < 0 ) bounds.lo
+ else NoType
+ case _ =>
+ mapOver(t)
+ }
+ }
+ approx(tp)
+ }
+ else tp
+
def addParamBound(bound: PolyParam) =
if (fromBelow) addLess(bound, param) else addLess(param, bound)
@@ -336,7 +369,7 @@ trait ConstraintHandling {
prune(bound.underlying)
case bound: PolyParam =>
constraint.entry(bound) match {
- case NoType => bound
+ case NoType => pruneLambdaParams(bound)
case _: TypeBounds =>
if (!addParamBound(bound)) NoType
else if (fromBelow) defn.NothingType
@@ -345,7 +378,7 @@ trait ConstraintHandling {
prune(inst)
}
case _ =>
- bound
+ pruneLambdaParams(bound)
}
try bound match {
diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala
index df8bc8116..ae88753d0 100644
--- a/src/dotty/tools/dotc/core/Symbols.scala
+++ b/src/dotty/tools/dotc/core/Symbols.scala
@@ -495,6 +495,7 @@ object Symbols {
def paramBounds(implicit ctx: Context) = denot.info.bounds
def paramBoundsAsSeenFrom(pre: Type)(implicit ctx: Context) = pre.memberInfo(this).bounds
def paramVariance(implicit ctx: Context) = denot.variance
+ def paramRef(implicit ctx: Context) = denot.typeRef
// -------- Printing --------------------------------------------------------
diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala
index be0c08d15..6e0bf7786 100644
--- a/src/dotty/tools/dotc/core/TypeApplications.scala
+++ b/src/dotty/tools/dotc/core/TypeApplications.scala
@@ -341,11 +341,11 @@ class TypeApplications(val self: Type) extends AnyVal {
*
* TODO: Handle parameterized lower bounds
*/
- def LambdaAbstract(tparams: List[Symbol])(implicit ctx: Context): Type = {
+ def LambdaAbstract(tparams: List[TypeParamInfo])(implicit ctx: Context): Type = {
def expand(tp: Type) =
TypeLambda(
- tpnme.syntheticLambdaParamNames(tparams.length), tparams.map(_.variance))(
- tl => tparams.map(tparam => tl.lifted(tparams, tparam.info).bounds),
+ tpnme.syntheticLambdaParamNames(tparams.length), tparams.map(_.paramVariance))(
+ tl => tparams.map(tparam => tl.lifted(tparams, tparam.paramBounds).bounds),
tl => tl.lifted(tparams, tp))
assert(!isHK, self)
self match {
diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala
index 3036cf878..3a0311977 100644
--- a/src/dotty/tools/dotc/core/TypeComparer.scala
+++ b/src/dotty/tools/dotc/core/TypeComparer.scala
@@ -398,7 +398,12 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
// This wpuld mean that there is no convenient means anymore to express a kind
// as a supertype. The fix is to delay the checking of bounds so that only
// bounds of * types are checked.
- variancesConform(tparams1, tparams2) && isSubType(body1, body2.subst(tp2, tp1))
+ val saved = comparingLambdas
+ comparingLambdas = true
+ try
+ variancesConform(tparams1, tparams2) &&
+ isSubType(body1, body2.subst(tp2, tp1))
+ finally comparingLambdas = saved
case _ =>
if (!tp1.isHK) {
tp2 match {
@@ -592,32 +597,54 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
false
}
- /** `param2` can be instantiated to the type constructor of the LHS
- * or to the type constructor of one of the LHS base class instances
+ /** `param2` can be instantiated to a type application prefix of the LHS
+ * or to a type application prefix of one of the LHS base class instances
* and the resulting type application is a supertype of `tp1`,
* or fallback to fourthTry.
*/
def canInstantiate(param2: PolyParam): Boolean = {
- /** `param2` can be instantiated to `tycon1a`.
- * and the resulting type application is a supertype of `tp1`.
+ /** Let
+ *
+ * `tparams_1, ..., tparams_k-1` be the type parameters of the rhs
+ * `tparams1_1, ..., tparams1_n-1` be the type parameters of the constructor of the lhs
+ * `args1_1, ..., args1_n-1` be the type arguments of the lhs
+ * `d = n - k`
+ *
+ * Returns `true` iff `d >= 0` and `param2` can be instantiated to
+ *
+ * [tparams1_d, ... tparams1_n-1] -> tycon1a[args_1, ..., args_d-1, tparams_d, ... tparams_n-1]
+ *
+ * such that the resulting type application is a supertype of `tp1`.
*/
- def tyconOK(tycon1a: Type) =
- variancesConform(tycon1a.typeParams, tparams) && {
- (ctx.mode.is(Mode.TypevarsMissContext) ||
- tryInstantiate(param2, tycon1a.ensureHK)) &&
- isSubType(tp1, tycon1a.appliedTo(args2))
+ def tyconOK(tycon1a: Type, args1: List[Type]) = {
+ var tycon1b = tycon1a
+ val tparams1a = tycon1a.typeParams
+ val lengthDiff = tparams1a.length - tparams.length
+ lengthDiff >= 0 && {
+ val tparams1 = tparams1a.drop(lengthDiff)
+ variancesConform(tparams1, tparams) && {
+ if (lengthDiff > 0)
+ tycon1b = tycon1a
+ .appliedTo(args1.take(lengthDiff) ++ tparams1.map(_.paramRef))
+ .LambdaAbstract(tparams1)
+ (ctx.mode.is(Mode.TypevarsMissContext) ||
+ tryInstantiate(param2, tycon1b.ensureHK)) &&
+ isSubType(tp1, tycon1b.appliedTo(args2))
+ }
}
+ }
tp1.widen match {
- case tp1w @ HKApply(tycon1, _) =>
- tyconOK(tycon1)
+ case tp1w @ HKApply(tycon1, args1) =>
+ tyconOK(tycon1, args1)
case tp1w =>
tp1w.typeSymbol.isClass && {
val classBounds = tycon2.classSymbols
def liftToBase(bcs: List[ClassSymbol]): Boolean = bcs match {
case bc :: bcs1 =>
- classBounds.exists(bc.derivesFrom) && tyconOK(tp1w.baseTypeRef(bc)) ||
+ classBounds.exists(bc.derivesFrom) &&
+ tyconOK(tp1w.baseTypeRef(bc), tp1w.baseArgInfos(bc)) ||
liftToBase(bcs1)
case _ =>
false
diff --git a/src/dotty/tools/dotc/core/TypeParamInfo.scala b/src/dotty/tools/dotc/core/TypeParamInfo.scala
index ff3c8fca7..6bec2e0e0 100644
--- a/src/dotty/tools/dotc/core/TypeParamInfo.scala
+++ b/src/dotty/tools/dotc/core/TypeParamInfo.scala
@@ -26,4 +26,7 @@ trait TypeParamInfo {
/** The variance of the type parameter */
def paramVariance(implicit ctx: Context): Int
+
+ /** A type that refers to the parameter */
+ def paramRef(implicit ctx: Context): Type
} \ No newline at end of file
diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala
index 03fdcb957..728f7fc21 100644
--- a/src/dotty/tools/dotc/core/Types.scala
+++ b/src/dotty/tools/dotc/core/Types.scala
@@ -2614,12 +2614,14 @@ object Types {
def paramBoundsAsSeenFrom(pre: Type)(implicit ctx: Context): TypeBounds = paramBounds
def paramVariance(implicit ctx: Context): Int = tl.variances(n)
def toArg: Type = PolyParam(tl, n)
+ def paramRef(implicit ctx: Context): Type = PolyParam(tl, n)
}
object TypeLambda {
def apply(paramNames: List[TypeName], variances: List[Int])(paramBoundsExp: GenericType => List[TypeBounds], resultTypeExp: GenericType => Type)(implicit ctx: Context): TypeLambda = {
unique(new TypeLambda(paramNames, variances)(paramBoundsExp, resultTypeExp))
}
+
def fromSymbols(tparams: List[Symbol], resultType: Type)(implicit ctx: Context) =
if (tparams.isEmpty) resultType
else apply(tparams map (_.name.asTypeName), tparams.map(_.variance))(
diff --git a/tests/neg/i94-nada.scala b/tests/neg/i94-nada.scala
new file mode 100644
index 000000000..8ca104e06
--- /dev/null
+++ b/tests/neg/i94-nada.scala
@@ -0,0 +1,11 @@
+trait Test1 {
+ trait Monad[MX] {
+ def x: MX
+ }
+ sealed abstract class Either[A,B]
+ case class Left[A,B](x: A) extends Either[A,B] with Monad[A]
+ case class Right[A,B](x: B) extends Either[A,B] with Monad[B]
+ def flatMap[FX,FY,M[FMX]<:Monad[FMX]](m: M[FX], f: FX => M[FY]): M[FY] = f(m.x)
+ println(flatMap(Left(1), {x: Int => Left(x)})) // error: Left does not conform to [X] -> Monad[X]
+
+}
diff --git a/tests/pickling/i94-nada.scala b/tests/pickling/i94-nada.scala
index ce8dc98ad..cf39ee2ae 100644
--- a/tests/pickling/i94-nada.scala
+++ b/tests/pickling/i94-nada.scala
@@ -27,7 +27,7 @@ trait Test1 {
case class Left[A,B](x: A) extends Either[A,B] with Monad[A]
case class Right[A,B](x: B) extends Either[A,B] with Monad[B]
def flatMap[X,Y,M[X]<:Monad[X]](m: M[X], f: X => M[Y]): M[Y] = f(m.x)
- println(flatMap(Left(1), {x: Int => Left(x)}))
+ println(flatMap(Right(1), {x: Int => Right(x)}))
}
trait Test2 {
trait Monad[X] {
@@ -37,7 +37,7 @@ trait Test2 {
case class Left[A,B](x: A) extends Either[A,B] with Monad[A]
case class Right[A,B](x: B) extends Either[A,B] with Monad[B]
def flatMap[X,Y,M[X]](m: M[X], f: X => M[Y]): M[Y]
- println(flatMap(Left(1), {x: Int => Left(x)}))
+ println(flatMap(Right(1), {x: Int => Right(x)}))
}
trait Test3 {
def flatMap[X,Y,M[X]](m: M[X], f: X => M[Y]): M[Y]
diff --git a/tests/pos/i94-nada.scala b/tests/pos/i94-nada.scala
index f8263ccf2..1c7d88a10 100644
--- a/tests/pos/i94-nada.scala
+++ b/tests/pos/i94-nada.scala
@@ -25,7 +25,7 @@ trait Test1 {
case class Left[A,B](x: A) extends Either[A,B] with Monad[A]
case class Right[A,B](x: B) extends Either[A,B] with Monad[B]
def flatMap[X,Y,M[X]<:Monad[X]](m: M[X], f: X => M[Y]): M[Y] = f(m.x)
- println(flatMap(Left(1), {x: Int => Left(x)}))
+ println(flatMap(Right(1), {x: Int => Right(x)}))
}
trait Test2 {
trait Monad[X] {
@@ -35,7 +35,7 @@ trait Test2 {
case class Left[A,B](x: A) extends Either[A,B] with Monad[A]
case class Right[A,B](x: B) extends Either[A,B] with Monad[B]
def flatMap[X,Y,M[X]](m: M[X], f: X => M[Y]): M[Y]
- println(flatMap(Left(1), {x: Int => Left(x)}))
+ println(flatMap(Right(1), {x: Int => Right(x)}))
}
trait Test3 {
def flatMap[X,Y,M[X]](m: M[X], f: X => M[Y]): M[Y]
diff --git a/tests/pos/t2712-1.scala b/tests/pos/t2712-1.scala
new file mode 100644
index 000000000..4f84c9df5
--- /dev/null
+++ b/tests/pos/t2712-1.scala
@@ -0,0 +1,9 @@
+package test
+
+// Original test case from,
+//
+// https://issues.scala-lang.org/browse/SI-2712
+object Test {
+ def meh[M[_], A](x: M[A]): M[A] = x
+ meh{(x: Int) => x} // solves ?M = [X] Int => X and ?A = Int ...
+}
diff --git a/tests/pos/t2712-4.scala b/tests/pos/t2712-4.scala
new file mode 100644
index 000000000..3e2e5cdda
--- /dev/null
+++ b/tests/pos/t2712-4.scala
@@ -0,0 +1,17 @@
+package test
+
+object Test1 {
+ trait X
+ trait Y extends X
+ class Foo[T, U <: X]
+ def meh[M[_ <: A], A](x: M[A]): M[A] = x
+ meh(new Foo[Int, Y])
+}
+
+object Test2 {
+ trait X
+ trait Y extends X
+ class Foo[T, U >: Y]
+ def meh[M[_ >: A], A](x: M[A]): M[A] = x
+ meh(new Foo[Int, X])
+}
diff --git a/tests/pos/t2712-7.scala b/tests/pos/t2712-7.scala
new file mode 100644
index 000000000..d9c5243f1
--- /dev/null
+++ b/tests/pos/t2712-7.scala
@@ -0,0 +1,15 @@
+package test
+
+// Cats Xor, Scalaz \/, scala.util.Either
+sealed abstract class Xor[+A, +B] extends Product with Serializable
+object Xor {
+ final case class Left[+A](a: A) extends (A Xor Nothing)
+ final case class Right[+B](b: B) extends (Nothing Xor B)
+}
+
+object TestXor {
+ import Xor._
+ def meh[F[_], A, B](fa: F[A])(f: A => B): F[B] = ???
+ meh(new Right(23): Xor[Boolean, Int])(_ < 13)
+ meh(new Left(true): Xor[Boolean, Int])(_ < 13)
+}
diff --git a/tests/pos/t5683.scala b/tests/pos/t5683.scala
new file mode 100644
index 000000000..05ab03579
--- /dev/null
+++ b/tests/pos/t5683.scala
@@ -0,0 +1,23 @@
+object Test {
+ trait NT[X]
+ trait W[W, A] extends NT[Int]
+ type StringW[T] = W[String, T]
+ trait K[M[_], A, B]
+
+ def k[M[_], B](f: Int => M[B]): K[M, Int, B] = null
+
+ val okay1: K[StringW,Int,Int] = k{ (y: Int) => null: StringW[Int] }
+ val okay2 = k[StringW,Int]{ (y: Int) => null: W[String, Int] }
+
+ val crash: K[StringW,Int,Int] = k{ (y: Int) => null: W[String, Int] }
+
+ // remove `extends NT[Int]`, and the last line gives an inference error
+ // rather than a crash.
+ // test/files/pos/t5683.scala:12: error: no type parameters for method k: (f: Int => M[B])Test.K[M,Int,B] exist so that it can be applied to arguments (Int => Test.W[String,Int])
+ // --- because ---
+ // argument expression's type is not compatible with formal parameter type;
+ // found : Int => Test.W[String,Int]
+ // required: Int => ?M[?B]
+ // val crash: K[StringW,Int,Int] = k{ (y: Int) => null: W[String, Int] }
+ // ^
+}