strictEquality with explicit-nulls do not work well together

With -Yexplicit-nulls

import language.strictEquality

val x: String | Null = null

val check = x == null //error

Should the explicit nulls feature introduce CanEqual[T | Null, Null] and CanEqual[T | Null, T]?

Could be relevant for the discussion, how to generally handle strict equality for unions:

1 Like

This seems required for these two features to interact in a safe way

Is there a way to allow that, but disallow "null" == null (Since "null" is a String | null) ?

I’ve tried trivial fix with

given [A, B](using CanEqual[A, B]): CanEqual[A | Null, B | Null] = CanEqual.derived

And it seems to work in most obvious cases

Except it allows null == ""

That’s actually useful. Even if things are declared with non-null types they can still be null at runtime because they might not be initialized. So, a test like null == s where s is a String is sometimes necessary and it would be annoying if it was not allowed.

In my humble opinion, any access to an uninitialized field is a bug. It is in the same category as accessing an array out of bounds. This isn’t something that one should need to branch on, and I wouldn’t let any code that does this pass code review.

And regarding the original issue, I don’t think it’s a real problem because while x == null doesn’t compile, x eq null works fine, and it makes more sense too because checking for null is a check for reference equality, not for value equality, which is what == is meant to be used for.

There is only one potential issue here: many users don’t know eq because it’s not something that is needed very often. If this is of real concern, it can be fixed with a simple compiler hack. When the compiler sees an == null check and a suitable CanEqual cannot be found, the error message should suggest trying eq.

4 Likes

Maybe we can add methods isNull and isNotNull in Any.

Not even in an assert(s != null)? In code that you don’t control fully? I agree eq/ne would be an alternative, but we want to make it easy and straightforward to write such asserts. Someone who adds such an assert in a desperate debug session would not appreciate the technicalities of CanEqual here.

1 Like

Well, assert is a special case because it is a tool specifically made to detect bugs at run-time. In pretty much all other cases, I don’t think one should ever branch on a condition that can only ever be true when there’s a bug in the program. And adding a language feature that is always available but whose only legitimate use is inside an assert statement doesn’t seem reasonable to me, especially given that eq and ne work just fine.
Again, I think a compiler hint to use eq or ne is fine.

3 Likes

We had many issues with language.strictEquality before; unfortunately, we couldn’t find anyone who is actively maintaining this feature and discuss the expected behaviour.

Hence, the current behaviour of explicit nulls is based on rules without strictEquality.