Why StringBuilder.contains of String/Int argument throws no compilation error?


consider the code as follows:

scala> val sb = StringBuilder("abc")
val sb: StringBuilder = abc

scala> sb.contains('a')
val res0: Boolean = true

scala> sb.contains("ab")
val res1: Boolean = false

scala> sb.contains(1)
val res2: Boolean = false

I wonder why contains with an argument of String or Int returns false instead of compilation error? Since the contains is defined at SeqOps (scala.StringBuilder is derived from AbstractSeq[Char]) as def contains[A1 >: A](elem: A1): Boolean = exists (_ == elem), and neither String, nor Int are supertypes of Char.

The signature contains[A1 >: A](elem: A1) does say, that A1 must be a supertype of Char in this case. But the parameter elem does not have to be that exact type, it may also be any subtype of A1.

If you do not explicitly specify A1, the compiler will infer it not to the type of elem, but to the least common supertype of Char and elem's type. In case of Int, this would be AnyVal, for String it would be Any. As Any can always be used as common supertype, you’ll never get a type error, without explicitly specifying A1.

@ val sb = new StringBuilder("abc")
sb: StringBuilder = IndexedSeq('a', 'b', 'c')

@ sb.contains("a")
res11: Boolean = false

@ sb.contains[Any]("a")
res12: Boolean = false

@ sb.contains[String]("a")
cmd13.sc:1: type arguments [String] do not conform to method contains's type parameter bounds [A1 >: Char]
val res13 = sb.contains[String]("a")
Compilation Failed

Also note, that Int is “kind of a supertype” for Char in the sense that operations like comparisons and arithmetic containing both will work. Char is an unsigned integer with lower range, so it can be converted to an Int without losing information. So, if you pass the integer value of a character in the StringBuilder, you can even get true as result:

@ sb.contains(97)  //97 is the ascii code point for 'a'
res16: Boolean = true
1 Like

See also this discussion about a more typesafe contains method and why it is difficult.

One thing, that helps with most cases of unintended type-unsafeness, is using the WartRemover compiler plugin with the Any, AnyVal, Product and JavaSerializable warts enabled, which will cause the compiler to warn or error, if one of these types is inferred by the compiler.