The contract isn’t broken. The contract is the contract: you can do Object easily and everything else is hard, so it does the easy thing. That’s the contract!
Do you want to try to do a harder thing? You can write it yourself! You have to call IArray.equals manually anyway. If you want Java deepEquals, just call it. Every IArray[T] is just Array[T] so you can asInstanceOf it over. You can write a helper function that does it:
extension [T <: AnyRef](a: IArray[T])
def deepEquals[U <: AnyRef](b: IArray[U]) =
java.util.Arrays.deepEquals(a.asInstanceOf[Array[AnyRef]], b.asInstanceOf[Array[AnyRef]])
Now you can deepEquals it. But wait! What if you want deepEquals on any type of array? It turns out that you can’t express that in Java so it doesn’t support it–save for System.arrayCopy which takes Object instead of Array because there’s no superclass for a general Array (!!)–so you have to do it manually. And then you have to decide if Array(1, 2, 3) is or is not equal to Array(1L, 2L, 3L). In Scala, 1 == 1L, 2 == 2L, and 3 == 3L, but in Java Array(1, 2, 3).equals(Array(1L, 2L, 3L)) is false, and there is no java.util.Arrays.equals method that takes Array[Int] and Array[Long]. Hence, deepEquals, unlike Scala collection equals, will return false if there are embedded Arrays with identical elements but a different primitive type.
But then you realize that Array[String] and Array[CharSequence] and Array[AnyRef] are actually all different class types, but they all return true for isInstanceOf[Array[AnyRef]] even though Array[AnyRef] is not a superclass of Array[CharSequence] etc.! So you can totally go
if xs.isInstanceOf[Array[AnyRef]] then
val ys = xs.asInstanceOf[Array[AnyRef]]
ys(0) = "Uh-oh"
and that will totally work even if it’s an Array[Option[Boolean]]. And crash later with a ClassCastException when you try to use the Option[Boolean] that is actually a string.
Around about this time, most people throw up their hands and declare arrays broken by design, and use Scala collections which have sensible policies for all these things.
But if you haven’t thrown up your hands, and you still want to keep at it, you can use asInstanceOf[Array[Char]] etc. to detect primitive arrays, or you can just accept that you can’t even call Array(1, 2).deepEquals(Array(1L, 2L)) (or, more likely, def foo[X, Y](xs: Array[X], ys: Array[Y]) = xs.deepEquals(ys)); or you can pattern match out all the primitives for comparisons like that and defer to java.util.Arrays.equals if the types match, even though it has different behavior than every other collection type, or write your own recursive routine.
But the designers of the Scala library decided, sensibly enough, that this is all a minefield and if you want well-behaved collections, you can use the collections library. Including wrapped Arrays, if that’s what you want–the wrapper smooths out all the wrinkles.
So, wherever you might be tempted to use IArray, think about whether collection.immutable.ArraySeq would do instead. Sometimes it doesn’t; ArraySeq is not guaranteed to hold primitives as primitives; ArraySeq(1, 2, 3) wraps a primitive Array(1, 2, 3) (Array[Int]) but ArraySeq("a", "bc", "def").map(_.length) is also typed as ArraySeq[Int] but stores the values as boxed because map acts generically.
Anyway, the bottom line of all of this is that Array and IArray allow high-performance primitive arrays to be easily accessible in Scala as well as Java, but they don’t shield you from all the weirdnesses of primitive arrays. If you want to build your own shields, go for it! You can write extension methods. But the rest of us have just given up and decided that we will use them very intentionally as the weird things that they are.