Why does isInstanceOf only return a warning when used on a parametric type?

I understand the JVM cannot distinguish at runtime between a List[String] and a List[Int] because of type erasure. But I wonder why do we only get a warning instead of a compiler error? For example,

List(1,2,3).isInstanceOf[List[String]] == true
// warning: fruitless type test: a value of type
// List[Int] cannot also be a List[String]

However, if I implement an overloaded method that takes either a List[Int] or a List[String], then I get a compile-time error, e.g.

def foo(xs: List[Int]): String = ???
def foo(xs: List[String]): String = ???
// error: double definition [foo] and [foo] have the same type after erasure

Thanks

Useless isInstanceOf won’t be rejected by JVM, while a class having two methods with the same signature will be rejected by JVM.

More precisely:
(x: List[Int]).isInstanceOf[List[String]] gets erased to (x: List[_]).isInstanceOf[List[_]]) so obviously it’s always true, thus checking that is useless. OTOH:

def foo(xs: List[Int]): String = ???
def foo(xs: List[String]): String = ???

gets erased to:

def foo(xs: List[_]): String = ???
def foo(xs: List[_]): String = ???

so that would lead to inherent ambiguity and it has to be rejected.

Generally you should pay attention not only to errors, but also to warnings. But why couldn’t we make both situations an error? If we give an opt-out from that then code would still compile after annotating it. Java has annotation @SuppressWarnings("xxx") but that doesn’t sound scary enough for me. Scala could have something like @AllowSoundnessHoles annotation to annotate code with broken generics usage.

2 Likes

I would support this. Someone want to PR it? (in Scala 2, and in Scala 3)

The escape hatch you want already exists: @unchecked

scala 2.13.1> (List(1,2,3): Any).isInstanceOf[List[String]]
                                             ^
              warning: non-variable type argument String in type List[String] (the underlying of List[String]) is unchecked since it is eliminated by erasure
res7: Boolean = true

scala 2.13.1> (List(1,2,3): Any).isInstanceOf[List[String] @unchecked]
res8: Boolean = true

scala 2.13.1> (List(1): Any) match { case xs: List[String] => }
                                              ^
              warning: non-variable type argument String in type pattern List[String] (the underlying of List[String]) is unchecked since it is eliminated by erasure

scala 2.13.1> (List(1): Any) match { case xs: List[String] @unchecked => }

scala 2.13.1>
4 Likes

Scala 2.13.2 (which we hope to release tomorrow) has an equivalent: Configurable warnings, warning suppression

1 Like

Good to hear, although both @unchecked and @nowarn are still too convenient (short) and innocent. Long and scary annotation like @AllowSoundnessHoles would more effectively deter people from using it automatically.

1 Like

:100: :+1: Making the match warning into an error would close the biggest soundness hole in the language. I see beginners drive through this one full-speed because they don’t understand what the warning is trying to say.

2 Likes

@nowarn is the right phrase, because it tells you exactly what it does, but it makes sense to replace @unchecked with something clearer.

This is what -Werror is for.

This is also what -language is for.

The feature ought to require import language.mechanics.

The way we speak of “type astronauts”, we should speak of “language mechanics”, like the person in coveralls with grease under their nails.

I recently learned (perhaps mentioned on a ticket about outer pointers):

scala> class C { class D }
defined class C

scala> val x, y = new C
x: C = C@250a9031
y: C = C@1665fa54

scala> (new x.D: Any) match { case _: x.D => 0 case _: y.D => 1 }
res0: Int = 0

scala> (new x.D: Any) match { case _: y.D => 0 case _: x.D => 1 }
res1: Int = 1

scala> (new x.D).isInstanceOf[x.D]
res2: Boolean = true

scala> (new x.D).isInstanceOf[y.D]
res3: Boolean = true

That is per spec.

The problem is that it’s opt-in (wholesale), but it should be opt-out (on narrow scope).