From 4144cadaaa11a706faa03f7197a44d3cd5246205 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 8 Mar 2020 15:58:11 +0100 Subject: [PATCH 1/7] Restrict Eta-reduction to Java-defined classes Collapse `[X] =>> C[X]` only if `C` is a Java-defined class. Since Java does not have hk-types it must be a raw type in this case. --- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index acb545efdca4..0329373e9d98 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -605,7 +605,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w if (tparams1.nonEmpty) return recur(tp1.EtaExpand(tparams1), tp2) || fourthTry tp2 match { - case EtaExpansion(tycon2) if tycon2.symbol.isClass => + case EtaExpansion(tycon2) if tycon2.symbol.isClass && tycon2.symbol.is(JavaDefined) => return recur(tp1, tycon2) case _ => } @@ -773,7 +773,8 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w isNewSubType(tp1.parent) case tp1: HKTypeLambda => def compareHKLambda = tp1 match { - case EtaExpansion(tycon1) => recur(tycon1, tp2) + case EtaExpansion(tycon1) if tycon1.symbol.isClass && tycon1.symbol.is(JavaDefined) => + recur(tycon1, tp2) case _ => tp2 match { case tp2: HKTypeLambda => false // this case was covered in thirdTry case _ => tp2.typeParams.hasSameLengthAs(tp1.paramRefs) && isSubType(tp1.resultType, tp2.appliedTo(tp1.paramRefs)) @@ -2591,7 +2592,11 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) { } override def isSubType(tp1: Type, tp2: Type, approx: ApproxState): Boolean = - traceIndented(s"${show(tp1)} <:< ${show(tp2)}${if (Config.verboseExplainSubtype) s" ${tp1.getClass} ${tp2.getClass}" else ""} $approx ${if (frozenConstraint) " frozen" else ""}") { + def moreInfo = + if Config.verboseExplainSubtype || ctx.settings.verbose.value + then s" ${tp1.getClass} ${tp2.getClass}" + else "" + traceIndented(s"${show(tp1)} <:< ${show(tp2)}$moreInfo $approx ${if (frozenConstraint) " frozen" else ""}") { super.isSubType(tp1, tp2, approx) } From 3367591f9c1e1dc9d8f1b8ffb289aeee4ff6f92f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 8 Mar 2020 16:17:49 +0100 Subject: [PATCH 2/7] Test case #4906 --- tests/pos/i4906.scala | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tests/pos/i4906.scala diff --git a/tests/pos/i4906.scala b/tests/pos/i4906.scala new file mode 100644 index 000000000000..a76415cef348 --- /dev/null +++ b/tests/pos/i4906.scala @@ -0,0 +1,36 @@ +object Test { + trait A + trait TestConstructor1 { type F[_ <: A] } + trait TestConstructor2[D] { + type F[_ <: D] + class G[X <: D] + trait TestConstructor3[E] { + type G[_ <: D & E] + class H[X <: D & E] + } + } + + val v1: TestConstructor1 => Unit = { f => + type P[a <: A] = f.F[a] // OK + } + + val v2: TestConstructor2[A] => Unit = { f => + type P[a <: A] = f.F[a] // Error! Type argument a does not conform to upper bound D + } + + def f2(f: TestConstructor2[A]): Unit = { + type P[a <: A] = f.F[a] // Error! Type argument a does not conform to upper bound D + } + + val v3: (f: TestConstructor2[A]) => (g: f.TestConstructor3[A]) => Unit = ??? // ok + val v4: (f: TestConstructor2[A]) => (g: f.TestConstructor3[A]) => Unit = {f => ???} + val v5: (f: TestConstructor2[A]) => (g: f.TestConstructor3[A]) => Unit = {(f: TestConstructor2[A]) => ???} + // } + def f3(f: TestConstructor2[A], g: f.TestConstructor3[A]): Unit = { + type P[a <: A] = f.F[a] // Error! Type argument a does not conform to upper bound D + type Q[a <: A] = g.G[a] + // type R[a <: A] = (f.F & g.G)[a] // compiler error + type R[a <: A] = ([X <: A] =>> f.F[X] & g.G[X])[a] + type S[a <: A] = f.G[a] & g.H[a] + } +} From 5017f8b38d221572ecc426220b91ee4d070c9387 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 8 Mar 2020 16:19:47 +0100 Subject: [PATCH 3/7] Test case #4867 --- tests/pos/i4867.scala | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/pos/i4867.scala diff --git a/tests/pos/i4867.scala b/tests/pos/i4867.scala new file mode 100644 index 000000000000..5b36df39f8dc --- /dev/null +++ b/tests/pos/i4867.scala @@ -0,0 +1,16 @@ +object UnionMapping { + private def parse(string: String): Int | Double = { + if(string.contains(".")) + string.toDouble + else + string.toInt + } + + def test_number = { + val strings: Seq[String] = Seq("123", "2.0", "42") + // Works + val asdf: Seq[AnyVal] = strings.map(parse(_)) + // Fails to compile + val union: Seq[Int | Double] = strings.map(parse(_)) + } +} \ No newline at end of file From c70d59be0183d9e9945484a7931a80c8c1d28c18 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 8 Mar 2020 16:21:06 +0100 Subject: [PATCH 4/7] Test case #4854 --- tests/pos/i4854.scala | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 tests/pos/i4854.scala diff --git a/tests/pos/i4854.scala b/tests/pos/i4854.scala new file mode 100644 index 000000000000..f6a6127bf946 --- /dev/null +++ b/tests/pos/i4854.scala @@ -0,0 +1,5 @@ +class C { + def f(x: Int): Unit = () + val f: String => Unit = s => () + f("a") +} \ No newline at end of file From 96cca21d90a97d9f05ec3809f3e4461b5ab8cd0d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 8 Mar 2020 16:27:49 +0100 Subject: [PATCH 5/7] Test case #4742 --- tests/pos/i4742.scala | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/pos/i4742.scala diff --git a/tests/pos/i4742.scala b/tests/pos/i4742.scala new file mode 100644 index 000000000000..d59d4bd403bc --- /dev/null +++ b/tests/pos/i4742.scala @@ -0,0 +1,6 @@ +import scala.reflect.ClassTag + +class Test { + def foo[T <: String: ClassTag](f: T => Int) = 1 + def bar(f: String => Int) = foo(f) +} \ No newline at end of file From 66146285ecd9f4df775200bf8e004b4aedc0a72a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 8 Mar 2020 16:39:00 +0100 Subject: [PATCH 6/7] Test case for pending #4908 --- tests/pending/pos/i4908.scala | 41 +++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 tests/pending/pos/i4908.scala diff --git a/tests/pending/pos/i4908.scala b/tests/pending/pos/i4908.scala new file mode 100644 index 000000000000..c0465e024c9e --- /dev/null +++ b/tests/pending/pos/i4908.scala @@ -0,0 +1,41 @@ +object Test { + trait A + trait B + trait TestConstructor1 { type F[X <: A] + trait FromSet[C[_ <: A with B]] + + trait MSetLike[X <: A with B, This <: MSet[X] with MSetLike[X, This]] { + def to[C[X <: A with B] <: MSet[X] with MSetLike[X, C[X]]](fi: FromSet[C]): C[X] = ??? + } + trait MSet[X <: A with B] extends MSetLike[X, MSet[X]] + object MSetFactory extends FromSet[MSet] + } + + trait TestConstructor4[D] { + trait TestConstructor5[E] { + trait FromSet[C[_ <: D with E]] + + trait MSetLike[X <: D with E, This <: MSet[X] with MSetLike[X, This]] { + def to[C[X <: D with E] <: MSet[X] with MSetLike[X, C[X]]](fi: FromSet[C]): C[X] = ??? + } + trait MSet[X <: D with E] extends MSetLike[X, MSet[X]] + object MSetFactory extends FromSet[MSet] + } + } + + type C = A & B + val v1: TestConstructor1 => Unit = { f => + type P[a <: A] = f.F[a] + + type P1[c <: C] = f.MSet[c] + (f.MSetFactory: f.FromSet[f.MSet]): Unit + (x: P1[C]) => x.to(f.MSetFactory) + } + + def f3(f: TestConstructor4[A])(g: f.TestConstructor5[B]): Unit = { + type P1[c <: C] = g.MSet[c] + (g.MSetFactory: g.FromSet[g.MSet]): Unit + (x: P1[C]) => x.to(g.MSetFactory) + (x: P1[C]) => x.to[g.MSet](g.MSetFactory) + } +} \ No newline at end of file From 144d554675e33eb92db611753d970d95ee3a8571 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 9 Mar 2020 13:34:39 +0100 Subject: [PATCH 7/7] Document why we test for Java eta expansions in TypeComparer --- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 0329373e9d98..3f562bdd9025 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -606,10 +606,10 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w return recur(tp1.EtaExpand(tparams1), tp2) || fourthTry tp2 match { case EtaExpansion(tycon2) if tycon2.symbol.isClass && tycon2.symbol.is(JavaDefined) => - return recur(tp1, tycon2) + recur(tp1, tycon2) || fourthTry case _ => + fourthTry } - fourthTry } compareTypeLambda case OrType(tp21, tp22) => @@ -774,6 +774,8 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w case tp1: HKTypeLambda => def compareHKLambda = tp1 match { case EtaExpansion(tycon1) if tycon1.symbol.isClass && tycon1.symbol.is(JavaDefined) => + // It's a raw type that was mistakenly eta-expanded to a hk-type. + // This can happen because we do not cook types coming from Java sources recur(tycon1, tp2) case _ => tp2 match { case tp2: HKTypeLambda => false // this case was covered in thirdTry