def applyIfApplicable[T](f: T => Unit, x: Any): Unit = x match
case t: T => f(t)
applyIfApplicable[A | B](f, C())
It provokes another warning, but we still reach the “unreachable” code. (A proper applyIfApplicable
can be done using scala.reflect.Typeable
.)
Do you think anything has to be done about this at all? I think something has to be done, since the warning message is inconsistent with reality. The simplest solution would be to change the warning message by adding “maybe”. Also, I guess it is possible and would make sense to throw an exception if an execution reaches an unreachable case in a match. Is there any reason to allow reaching unreachable case?
Thanks for reading. 
Perhaps the words used in the message could better distinguish static types from runtime.
The ambiguity is between “unreachable” in the sense of “dead code at runtime” and unreachable in an abstract graph of static cases.
Possible wording: “all applicable patterns have been exhausted” or “the pattern space has been exhausted”.
Maybe there is a word that is both technically precise and in wide usage by users.
The runtime behavior is due to the subversion of the type system.
The usual explanation is that a compiler message is just a shorthand: either the user understands it from previous experience, or the user must become educated, in this instance, about the meaning of “unreachable” in this context and ultimately why asInstanceOf
is dangerous, or how other warnings must be heeded.
There is a ticket about braceless syntax which turns on whether a warning is heeded. Probably -Werror
should be recommended for the risk-averse.
I believe we should write error messages assuming the type system was not subverted, otherwise the problem becomes untractable
Therefore, I think the current message is perfectly fine
Maybe “the type test for T cannot be checked at runtime because it refers to an abstract type member or type parameter” should be an error instead of a warning
The gravity of the situation is not obvious as it uses quite a lot of advenced terminology
Notably “type test” which is not easy to find the meaning of, as you tend to find TypeTest
which is not exactly the same thing
Thanks for the reply!
First, I don’t think that the warning “Unreachable case” makes sense as long as we are still able to reach the case (which is what happens also in the second snippet). I also don’t think that the existence of other warnings in the program should justify this, especially since the first part of code, i.e., definition of the classes and f
, can be in a very different place than the second part, i.e., the code which uses them.
Second, both snippets are “lying” to the compiler, but in other similar cases, we don’t reach an unreachable case. For example (Scastie link), using List we also get unreachability warning, but it is true — we don’t reach the case:
case class A()
case class B()
def f(l: List[A]): Unit = l match
case x: List[A] => println(s"$x: ok")
case x => println(s"$x: unreachable except for null")
f(List(B()).asInstanceOf[List[A]])
There is still a consequence of the “lie”, i.e., that under A in the list we are actually passing B, but it will result in an exception as soon as we will try to access the element.
The compiler cannot be safe in the presence of asInstanceOf
, the whole point of it is to remove compiler checks !
That was actually the reason it has such a long name, it means “Trust me bro, I really know what I’m doing”
Compared to (x: Int)
which means “I think x
is an Int
, could you check ?”
Thank you for your replies!
My problem with both asInstanceOf
and abstract class / type parameter is that their effect is not local, so the error happens in one place, but the consequences are faced in maybe a very different place.
And speaking about specifically asInstanceOf
, it is not all-trusting, and does some checks in the runtime it is all-trusting, but its usage is still usually limited, so this results in an exception:
B().asInstanceOf[A]
I guess the same should be done in case of an unreachable case to really make it unreachable. One particular thing I am worried about is that if the unreachability message is interpreted as completely true, an optimization might rely on this assumption and produce incorrect code.
aversey:
And speaking about specifically asInstanceOf
, it is not all-trusting, and does some checks in the runtime, so this results in an exception:
B().asInstanceOf[A]
It is only non-trusting insofar as is necessary for the target VM not to complain that its bytecode is invalid. The JVM requires explicit checks for the monomorphic class, so the compiler emits that. Scala.js does not check anything in fullLink
mode, not even the monomorphic class. At the language level, asInstanceOf
is all-trusting. The language does not want to check, but some VMs require some checks. It does not mean you can rely on them.
There was a PR just now to remove the illustrative implementation of asInstanceOf
from the spec.
Instead of leaving it deferred, it could say:
def asInstanceOf[A]: A = undefined
so that only the most intrepid would dare use it.
Or possibly:
def asInstanceOf[A]: A =

I think the (potentially) surprising thing is that you don’t get a ClassCastException
during C().asInstanceOf[A | B]
. If you did, the unreachable
case would indeed be unreachable – exactly as if you replaced A | B
with just A
in the definition and type signature of f
. There’s an inconsistency introduced by the detail that A | B
erases to Object
(and thus the cast is successful and the method call is allowed)