diff options
author | Jason Zaugg <jzaugg@gmail.com> | 2013-03-26 09:39:01 +0100 |
---|---|---|
committer | Jason Zaugg <jzaugg@gmail.com> | 2013-03-27 00:16:27 +0100 |
commit | 8703e00b3876cad8750abf82949737cac8bde6fe (patch) | |
tree | 826668aa84f078e9e3e01275bec1c86ef631feb0 | |
parent | fcc22e22248992abffe2506ddef7c0f7c0b23044 (diff) | |
download | scala-8703e00b3876cad8750abf82949737cac8bde6fe.tar.gz scala-8703e00b3876cad8750abf82949737cac8bde6fe.tar.bz2 scala-8703e00b3876cad8750abf82949737cac8bde6fe.zip |
SI-7200 Test case for fixed type inference error.
Broken in 2.9.2 and 2.10.0, but working in 2.10.1
--- sandbox/2.10.0.log
+++ sandbox/2.10.1.log
def coflatMap[A >: Nothing <: Any, B >: Nothing <: Any](f: Test.Nel[A] => B): Test.Nel[A] => Test.Nel[B] = ((l: Test.Nel[A]) => Test.this.Nel.apply[B](f.apply(l), l.tail match {
case immutable.this.Nil => immutable.this.Nil
- case (hd: A, tl: List[A])scala.collection.immutable.::[A]((h @ _), (t @ _)) => {
- val r: Test.Nel[Nothing] = NelFoo.this.coflatMap[A, Nothing](f).apply(Test.this.Nel.apply[A](h, t));
+ case (hd: A, tl: List[A])scala.collection.immutable.::[?A1]((h @ _), (t @ _)) => {
+ val r: Test.Nel[B] = NelFoo.this.coflatMap[A, B](f).apply(Test.this.Nel.apply[A](h, t));
{
- <synthetic> val x$1: Nothing = r.head;
- r.tail.::[Nothing](x$1)
+ <synthetic> val x$1: B = r.head;
+ r.tail.::[B](x$1)
}
}
}))
b74c33eb86 represents the exact moment of progression. Comments
in pos/t7200b.scala, a minimal test that demonstrates the problem
without type constructors or code execution, pinpoint the line of
code responsible for the fix.
Incidentally, I'm currently on a train somewhere between Solothurn
and Biel, and am consequently without the power of scala-bisector.
Undeterred, and inspired by a line I saw in Skyfall last night
("sometimes the olds ways are better"), I just pulled off a two-hop
bisection. Take that, O(log N)!
The one remaining worry is the appearance of the type variable
?A1 in the output of -Xprint:typer for run/t7200.scala.
-rw-r--r-- | test/files/pos/t7200b.scala | 50 | ||||
-rw-r--r-- | test/files/run/t7200.scala | 34 |
2 files changed, 84 insertions, 0 deletions
diff --git a/test/files/pos/t7200b.scala b/test/files/pos/t7200b.scala new file mode 100644 index 0000000000..9d579c6ef9 --- /dev/null +++ b/test/files/pos/t7200b.scala @@ -0,0 +1,50 @@ +import language.higherKinds + +trait T { + def t = 0 +} +trait Foo { + def coflatMap[A <: T](f: A): A +} + +object O extends Foo { + def coflatMap[A <: T](f: A) = { + val f2 = coflatMap(f) // inferred in 2.9.2 / 2.10.0 as [Nothing] + f2.t // so this does't type check. + f2 + } +} + +// Why? When a return type is inherited, the derived method +// symbol first gets a preliminary type assigned, based on the +// 1) method type of a unique matching super member +// 2) viewed as a member type of the inheritor (to substitute, +// e.g. class type parameters) +// 3) substituted to replace the super-method's type parameters +// with those of the inheritor +// 4) dissected to take just the return type wrapped in thisMethodType(). +// +// In Scala 2.10.0 and earlier, this preliminary method type +// +// 1) [A#11329 <: <empty>#3.this.T#7068](<param> f#11333: A#11329)A#11329 +// 2) [A#11329 <: <empty>#3.this.T#7068](<param> f#11333: A#11329)A#11329 +// 3) (<param> f#12556: A#11336)A#11336 +// 4) [A#11336 <: <empty>#3.this.T#7068](<param> f#12552: A#11337&0)A#11336 +// +// The type #4 from the old version is problematic: the parameter is typed with +// a skolem for the type parameter `A`. It won't be considered to match the +// method it overrides, instead they are seen as being overloaded, and type inference +// goes awry (Nothing is inferred as the type argument for the recursive call +// to coflatMap. +// +// The Namers patch adds one step here: it subsitutes the type parameter symbols +// for the skolems: +// +// https://github.com/scala/scala/commit/b74c33eb#L2R1014 +// +// So we end up with a method symbol info: +// +// 5) [A#11336 <: <empty>#3.this.T#7068](<param> f#12505: A#11336)A#11336 +// +// This *does* match the method in the super class, and type inference +// chooses the correct type argument.
\ No newline at end of file diff --git a/test/files/run/t7200.scala b/test/files/run/t7200.scala new file mode 100644 index 0000000000..ba342df14d --- /dev/null +++ b/test/files/run/t7200.scala @@ -0,0 +1,34 @@ +import language.higherKinds + +object Test extends App { + + // Slice of comonad is where this came up + trait Foo[F[_]] { + def coflatMap[A, B](f: F[A] => B): F[A] => F[B] + } + + // A non-empty list + case class Nel[A](head: A, tail: List[A]) + + object NelFoo extends Foo[Nel] { + + // It appears that the return type for recursive calls is not inferred + // properly, yet no warning is issued. Providing a return type or + // type arguments for the recursive call fixes the problem. + + def coflatMap[A, B](f: Nel[A] => B) = // ok w/ return type + l => Nel(f(l), l.tail match { + case Nil => Nil + case h :: t => { + val r = coflatMap(f)(Nel(h, t)) // ok w/ type args + r.head :: r.tail + } + }) + } + + // Without a recursive call all is well, but with recursion we get a + // ClassCastException from Integer to Nothing + NelFoo.coflatMap[Int, Int](_.head + 1)(Nel(1, Nil)) // Ok + NelFoo.coflatMap[Int, Int](_.head + 1)(Nel(1, List(2))) // CCE + +} |