Skip to content

Union of function types leads to ClassCastException #5202

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

Closed
Jasper-M opened this issue Oct 4, 2018 · 6 comments
Closed

Union of function types leads to ClassCastException #5202

Jasper-M opened this issue Oct 4, 2018 · 6 comments

Comments

@Jasper-M
Copy link
Contributor

Jasper-M commented Oct 4, 2018

I don't know why or how, but:

scala> val f: (Int => Int) | (String => Int) = (a: Int) => a + 3                
val f: (Int => Int) | (String => Int) = Lambda$1427/1583001542@5583098b

scala> f(5)                                                                     
val res0: Int = 8

scala> f("c")
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
	at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:101)
	at scala.compat.java8.JFunction1$mcII$sp.apply(JFunction1$mcII$sp.java:12)
@Jasper-M
Copy link
Contributor Author

Jasper-M commented Oct 4, 2018

Variance and library free example:

scala> abstract class A[B] { def foo(b: B): B }
// defined class A

scala> class AInt extends A[Int] { def foo(b: Int) = b + 1 }
// defined class AInt

scala> val a: A[Int] | A[String] = new AInt
val a: A[Int] | A[String] = AInt@47fb7ec9

scala> a.foo(3)
val res9: Any = 4

scala> a.foo("bar")
java.lang.ClassCastException

@jducoeur
Copy link
Contributor

jducoeur commented Oct 4, 2018

What should this do? Frankly, a ClassCastException is pretty much what I'd expect. This strikes me more as a spec issue than anything else...

@Jasper-M
Copy link
Contributor Author

Jasper-M commented Oct 4, 2018

Honestly I don't know... But a ClassCastException should never be the expected behavior for code that doesn't contain explicit casts imho.

@smarter
Copy link
Member

smarter commented Oct 4, 2018

A ClassCastException means that we have a soudness bug, we want this kind of things to be detected at compile-time, not runtime. Here's a minimization:

class Foo[A] {
  def foo(a: A): Unit = {}
}

object Test {
  def main(args: Array[String]): Unit = {
    val x: Foo[Int] | Foo[String] = new Foo[Int]
    x.foo("") // Should not compile !
  }
}

Here, the compiler assigns (a: Int | String): Unit as the type signature of x.foo, which is clearly wrong. Because we're in contravariant position this should at least be (a: Int & String): Unit but I'm not 100% sure that's good enough given that Foo is invariant in A, the more radical approach would be to simply disallow any call to foo on x because of its type.

@sjrd
Copy link
Member

sjrd commented Oct 4, 2018

Easy. The parameter should be typed as Int & String, not Int | String. That's it. If the actual argument is known to be an instance of all types, then it won't matter what particular function is called (the one taking an Int or the one taking a String), it will happily accept the value of type Int & String.

odersky added a commit to dotty-staging/dotty that referenced this issue Oct 7, 2018
argForParam was overly naive for the cases where the prefix was an & or | type.
@odersky
Copy link
Contributor

odersky commented Oct 7, 2018

@srjd Yes, that's about it. The reason why it did not do this is and how to fix it was quite tricky, though.

odersky added a commit to dotty-staging/dotty that referenced this issue Oct 26, 2018
argForParam was overly naive for the cases where the prefix was an & or | type.
smarter added a commit that referenced this issue Oct 26, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants