Closed
Description
Compiler version
3.5.1-RC1-bin-20240602-c6fbe6f-NIGHTLY
Minimized code
import scala.language.experimental.namedTuples
import NamedTuple.From
case class Foo[+T](elem: T)
trait Base[T]:
def dep(foo: T): From[T]
class SubAny[T <: Foo[Any]] extends Base[T]:
def dep(foo: T): From[T] = (elem = "")
object Test:
@main def run =
val f: Foo[Int] = Foo(elem = 1)
val b: Base[Foo[Int]] = SubAny[Foo[Int]]()
val nt: (elem: Int) = b.dep(f)
val x: Int = nt.elem // ClassCastException
Output
Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:99)
at Test$.run(nt-from.scala:20)
at run.main(nt-from.scala:16)
Expectation
If From
behaved like a match type then:
def dep(foo: T): From[T] = (elem = "")
would be a type error as From[T]
would not reduce.
And just like with match types, we can also run into troubles because of singleton types (at least those coming from dependent methods parameters as in #19746, though I had to make the test case more complex to delay reduction because NamedTuple.From
doesn't compose with asSeenFrom
like match types do):
import scala.language.experimental.namedTuples
import NamedTuple.From
case class Foo[+T](elem: T)
trait Base[M[_]]:
def dep(foo: Foo[Any]): M[foo.type]
class SubAny extends Base[From]:
def dep(foo: Foo[Any]): From[foo.type] = (elem = "")
object Test:
@main def run =
val f: Foo[Int] = Foo(elem = 1)
val b: Base[From] = SubAny()
val nt: (elem: Int) = b.dep(f)
val x: Int = nt.elem // ClassCastException
So it seems that in general, NamedTuple.From
and match types should share a lot more logic to prevent unsoundness.