Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improperly inferred return type for recursive calls in abstract method implementation #7200

Closed
scabug opened this issue Mar 1, 2013 · 3 comments
Assignees
Labels
Milestone

Comments

@scabug
Copy link

scabug commented Mar 1, 2013

Snippet below fails with ClassCastException at runtime.

import language.higherKinds

object FooExample 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

}
@scabug
Copy link
Author

scabug commented Mar 1, 2013

Imported From: https://issues.scala-lang.org/browse/SI-7200?orig=1
Reporter: Rob Norris (rnorris)
Affected Versions: 2.10.0

@scabug
Copy link
Author

scabug commented Mar 26, 2013

@retronym said:
This bug has gone away in 2.10.1.

retronym/scala@scala:2.10.x...retronym:ticket/7200

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. We should
investigate that a little further so we can fully connect the dots.

But that diff is a bit troublesome: what it the TypeVar ?A1 doing in the post-typer tree? I will pull on that thread to see what I can find.

I chatted to Lukas today about his patch to Namers that (ostensibly) fixed this, and it seems to me that our current scheme for allowing recursive methods to use inherited inferred return types is quite risky.

scala> trait A { def foo: Any }; object B extends A { def foo = {if(false) foo; "" } }; B.foo
defined trait A
defined module B
res0: String = ""

During typechecking of B#foo, the method has the return type Any, which is later refined to String.
We can see this by trying to ascribe the more specific type:

scala> trait A { def foo: Any }; object B extends A { def foo = {if(false) foo: String; "" } }; B.foo
<console>:9: error: type mismatch;
 found   : Any
 required: String
              trait A { def foo: Any }; object B extends A { def foo = {if(false) foo: String; "" } };;
                                                                                  ^

@scabug
Copy link
Author

scabug commented Mar 26, 2013

@retronym said:
Marking as fixed in 2.10.1; test case and analysis here:

scala/scala#2322

@scabug scabug closed this as completed Apr 20, 2013
@scabug scabug added the infer label Apr 7, 2017
@scabug scabug added this to the 2.10.1 milestone Apr 7, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants