-Yexplicit-nulls, strictEquality and null checking

Today an issue was raised on Discord that caught my attention. As you might know, Scala supports a kind of flow typing for nullable types, which is why this works:

def emptyOr(x: String | Null): String =
  if x ne null then
    x // x is String here, not String | Null
  else
    ""

But as soon as any type is involved that isn’t a subtype of AnyRef, it no longer compiles:

def zeroOr(x: Int | Null): Int =
  if x ne null then
    x
  else
    0

//-- [E008] Not Found Error: -----------------------------------------------------
//  |  if x ne null then
//  |     ^^^^
//  |     value ne is not a member of Int | Null.

An alternative would be to use != instead of ne, but then it works for neither of the two examples above because a CanEqual[String | Null, Null] or CanEqual[Int | Null, Null] is not available.

Values of types String | Null and Null cannot be compared with == or !=

How do we fix this? Do we just need a given CanEqual[Any, Null] in the standard library?

scala.Predef already defines an ne extension method for AnyRef | Null, I guess it would not be a stretch to define one for Any | Null

Is this issue resolved by Relax comparison between Null and reference types in explicit nulls by SuperCl4sh · Pull Request #23308 · scala/scala3 · GitHub or does the problem still exist even with that PR?

I have tried this in Scala 3.7.2, which includes that patch.

Any | Null is equivalent to Any, so we’d have to live with the fact that we can now call eq on anything including non-reference types. But I can’t think of a better solution either, my CanEqual suggestion from above has the same problem.

@sjrd said on Discord that null tests shold be done with ==/!= rather than eq/ne, and for that we would need the CanEqual instance. But we could also just provide both CanEqual and eq/ne. I think of == as value equality and eq as reference equality, and since we’re testing for equality with the null reference, I personally prefer eq, but I’m not too dogmatic about it.

There is one case that null tests are done via == in scala.

val data: Null | Int = ???
data match
  case null => 13
  case _ => data

In pattern matching, the case null is checked via ==. And if we check using case _: Null then it won’t work since Null is not a runtime type. Maybe when explicit-nulls enabled, we can make csae _: Null a special case for type checking just via eq null or == null (I don’t know which is better)