summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Zaugg <jzaugg@gmail.com>2014-09-14 15:14:41 +0200
committerJason Zaugg <jzaugg@gmail.com>2014-11-07 11:42:51 +1000
commita9182fbeaf018c1aa0f88f0aee7b921383b746f2 (patch)
tree01801890071c5cef286638721bf074ed0302ac8d
parentc12a9b7bf8dc423783dd02eb0e4c477c86de96df (diff)
downloadscala-a9182fbeaf018c1aa0f88f0aee7b921383b746f2.tar.gz
scala-a9182fbeaf018c1aa0f88f0aee7b921383b746f2.tar.bz2
scala-a9182fbeaf018c1aa0f88f0aee7b921383b746f2.zip
SI-8575 Fix subtyping transitivity with aliases, compound types
Thanks to @b-studios and @Blaisorblade for investigation into this bug. `RefinedType#normalize` is responsible for flattening nested compound types to a flat representation. Types are normalized during `=:=` in search of a successful result. This means that `((A with B) with C) =:= (A with B with C)`. However, if we introduce a type alias for `A with B` on the LHS, the attempt at flattening is thwarted. This commit changes normalization of refined types. If a parent is an alias for another refined type, it is dealiased during the flattening process. Two tests are included. The first demonstrates a symptom of this problem: failure to install a bridge method resulted in a linkage error. The second test uses the reflection API to directly show transitivity of subtyping now holds. Targetting at 2.12, as bug fixes in subtyping usually can be shown to lead to binary incompatibilities between code using the old and new compilers.
-rw-r--r--src/reflect/scala/reflect/internal/Types.scala9
-rw-r--r--test/files/run/t8575.scala33
-rw-r--r--test/files/run/t8575b.scala17
-rw-r--r--test/files/run/t8575c.scala23
4 files changed, 81 insertions, 1 deletions
diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala
index a95f626a0b..554dede8ec 100644
--- a/src/reflect/scala/reflect/internal/Types.scala
+++ b/src/reflect/scala/reflect/internal/Types.scala
@@ -1600,7 +1600,14 @@ trait Types
private var normalized: Type = _
private def normalizeImpl = {
// TODO see comments around def intersectionType and def merge
- def flatten(tps: List[Type]): List[Type] = tps flatMap { case RefinedType(parents, ds) if ds.isEmpty => flatten(parents) case tp => List(tp) }
+ // SI-8575 The dealias is needed here to keep subtyping transitive, example in run/t8575b.scala
+ def flatten(tps: List[Type]): List[Type] = {
+ def dealiasRefinement(tp: Type) = if (tp.dealias.isInstanceOf[RefinedType]) tp.dealias else tp
+ tps map dealiasRefinement flatMap {
+ case RefinedType(parents, ds) if ds.isEmpty => flatten(parents)
+ case tp => List(tp)
+ }
+ }
val flattened = flatten(parents).distinct
if (decls.isEmpty && hasLength(flattened, 1)) {
flattened.head
diff --git a/test/files/run/t8575.scala b/test/files/run/t8575.scala
new file mode 100644
index 0000000000..6e3e57f2be
--- /dev/null
+++ b/test/files/run/t8575.scala
@@ -0,0 +1,33 @@
+class E[F]
+class A
+class B
+class C
+
+trait TypeMember {
+ type X
+
+ // This call throws an AbstractMethodError, because it invokes the erasure of
+ // consume(X): Unit that is consume(Object): Unit. But the corresponding
+ // bridge method is not generated.
+ consume(value)
+
+ def value: X
+ def consume(x: X): Unit
+}
+
+object Test extends TypeMember {
+ type F = A with B
+
+ // works if replaced by type X = E[A with B with C]
+ type X = E[F with C]
+
+ val value = new E[F with C]
+
+ // This call passes, since it invokes consume(E): Unit
+ consume(value)
+ def consume(x: X) {}
+
+ def main(args: Array[String]) {
+
+ }
+}
diff --git a/test/files/run/t8575b.scala b/test/files/run/t8575b.scala
new file mode 100644
index 0000000000..0d731ccf9f
--- /dev/null
+++ b/test/files/run/t8575b.scala
@@ -0,0 +1,17 @@
+class A
+class B
+class C
+
+object Test {
+ type F = A with B
+
+ def main(args: Array[String]) {
+ import reflect.runtime.universe._
+ val t1 = typeOf[F with C]
+ val t2 = typeOf[(A with B) with C]
+ val t3 = typeOf[A with B with C]
+ assert(t1 =:= t2)
+ assert(t2 =:= t3)
+ assert(t3 =:= t1)
+ }
+}
diff --git a/test/files/run/t8575c.scala b/test/files/run/t8575c.scala
new file mode 100644
index 0000000000..8219952299
--- /dev/null
+++ b/test/files/run/t8575c.scala
@@ -0,0 +1,23 @@
+class C
+
+trait TypeMember {
+ type X
+ type Y
+ type Z
+}
+
+object Test extends TypeMember {
+ type A = X with Y
+ type B = Z with A
+ type F = A with B
+
+ def main(args: Array[String]) {
+ import reflect.runtime.universe._
+ val t1 = typeOf[F with C]
+ val t2 = typeOf[(A with B) with C]
+ val t3 = typeOf[A with B with C]
+ assert(t1 =:= t2)
+ assert(t2 =:= t3)
+ assert(t3 =:= t1)
+ }
+}