Dotty: Inconsistent behavior of String literal type between pattern matching and isInstanceOf

Created on 6 Aug 2019  路  8Comments  路  Source: lampepfl/dotty

minimized code

Tried with dotr in Dotty compiler version 0.17.0-RC1 .

def isAType(arg: String): Unit = arg match {    
  case _ : "a" => println("This is `a` type")
  case _ => println("not `a`")
}

isAType("a")
//This is `a` type

isAType(new String("a"))
//This is `a` type

new String("a").isInstanceOf["a"]
//val res0: Boolean = false

expectation

new String("a") should not match with case _ : "a" as it doesn't with isInstanceOf.

bug

Most helpful comment

Yes, I believe that x.isInstanceOf["a"] should compile down to "a".equals(x).

All 8 comments

The fact that pattern matching and isInstanceOf don't agree is definitely a bug, but (new Integer(1)).isInstanceOf[1] returns true in both scalac and dotty (because it erases to 1.equals(new Integer(1))), so I would argue that x.isInstanceOf["a"] should similarly erase to "a".equals(x). In fact, SIP-23 already says:

Pattern matching against typed patterns (see 8.1.2 Typed Patterns) where the TypePat is a literal type is translated as a match against the subsuming non-singleton type followed by an equality test with the value corresponding to the literal type.

/cc @milessabin @sjrd, do you agree ?

Yes, I believe that x.isInstanceOf["a"] should compile down to "a".equals(x).

Hm, now that you mention it, I think we did talk about it already w/ @sjrd. My sole argument as to why this should not be the case is that all AnyRef singleton types work based on object identity, and making String work based on equality instead is inconsistent.

Something to consider - how should the following compile?

val x: "a" = "a"
val y: Object = x

foo.isInstanceOf[x.type]
foo.isInstanceOf[y.type]

That is a concern, indeed. But it is not limited to strings. You can also construct something with Ints:

val x: 1 = 1
val y: Integer = x

foo.isInstanceOf[x.type]
foo.isInstanceOf[y.type]

Ok, I just checked and apparently there's ample precedent for interesting behaviour when upcasting/boxing AnyVals:

scala> val a: Any = 1
val a: Any = 1

scala> 1.isInstanceOf[a.type]
val res0: Boolean = true

scala> a.isInstanceOf[1]
val res1: Boolean = true

scala> (new Integer(1)).isInstanceOf[a.type]
val res2: Boolean = true

scala> val i: Integer = new Integer(1)
val i: Integer = 1

scala> val j: Integer = new Integer(1)
val j: Integer = 1

scala> i.isInstanceOf[a.type]
val res3: Boolean = true

scala> j.isInstanceOf[a.type]
val res4: Boolean = true

scala> i.isInstanceOf[j.type]
val res5: Boolean = false

scala> i eq j
val res6: Boolean = false

scala> i.isInstanceOf[1]                                                                                                                                                                                                                                                                  
val res7: Boolean = true

scala> 1.isInstanceOf[i.type]
val res8: Boolean = false

scala> val o: Object = i
val o: Object = 1

scala> o.isInstanceOf[a.type]
val res9: Boolean = true

scala> a.isInstanceOf[o.type]
val res10: Boolean = false

Note in particular that upcasting an Int to Any preserves isInstanceOf behaviour, but boxing it in Integer/Object does not.

Note in particular that upcasting an Int to Any preserves isInstanceOf behaviour, but boxing it in Integer/Object does not.

upcasting ends up calling Integer.valueOf which caches small integers (< 1024), try a big one.

My sole argument as to why this should not be the case is that all AnyRef singleton types work based on object identity, and making String work based on equality instead is inconsistent.

Maybe we need to reconsider the concept of literal _singleton_ types, if we stop insisting that they're singletons, then I think that using == is completely natural.

Seems this needs more deliberations.

Was this page helpful?
0 / 5 - 0 ratings