Skip to content

Commit

Permalink
SI-7200 Test case for fixed type inference error.
Browse files Browse the repository at this point in the history
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)
           }
         }
       }))

b74c33e 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.
  • Loading branch information
retronym committed Mar 26, 2013
1 parent fcc22e2 commit 8703e00
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 0 deletions.
50 changes: 50 additions & 0 deletions 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.
34 changes: 34 additions & 0 deletions 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

}

0 comments on commit 8703e00

Please sign in to comment.