Skip to content

Relax comparison between Null and reference types in explicit nulls #19258

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

noti0na1
Copy link
Member

@noti0na1 noti0na1 commented Dec 13, 2023

This PR removes the restriction for the double-equal (== and !=), reference (eq and ne) comparison, and pattern matching between Null and reference types.
Even if a type is non-nullable, we still need to consider the possibility of null value caused by the Java methods or uninitialized values.

val s: String = ???

s == null // ok now

s match 
  case null => // ok now
  case _ =>

Two changes are made in this PR:

  1. Remove the special case for SafeNulls in synthesizedCanEqual;
  2. Disable SafeNulls for SpaceEngine.checkRedundancy, because we want to use the after-erasure nullability for the space operations.

@noti0na1 noti0na1 self-assigned this Dec 13, 2023
@noti0na1 noti0na1 force-pushed the relaxed-equal-nulls branch from a90d826 to 828beb6 Compare December 13, 2023 15:03
@noti0na1 noti0na1 requested review from olhotak and odersky December 13, 2023 15:03
@olhotak
Copy link
Contributor

olhotak commented Dec 14, 2023

I would like to understand the underlying motivation for this. Is there some critical use case that has come up in community projects?

Disallowing comparison with null is a good way to catch definitions in existing projects that are really intended to be nullable (since the code compares with null frequently) but their type currently doesn't reflect that. If comparison with null is allowed, some of those definitions may erroneously stay with non-nullable types.

For nulls coming from Java, I would have expected that comparison of a FlexibleType with null would be allowed, but not comparison of the underlying type with null. So in the presence of flexible types, comparisons of nulls coming from Java would be allowed. If one turns off flexible types (presumably to get more soundness with respect to Java), then comparison would be disallowed.

For the hopefully rare case of dynamically testing whether a value is initialized or not, one can still do (s: String|Null) == null. But if it's the case that s may or may not be initialized, and the programmer doesn't know statically and needs a dynamic test, I would argue that it's better style to declare the type of s to be nullable in this case: it may or may not be null/uninitialized.

@odersky
Copy link
Contributor

odersky commented Dec 14, 2023

The problem is that our non-nullability assurances are fallible. Even if I have that e: String, e might still be null. For instance,

  • e might be uninitialized
  • e might be an array element

It's too annoying to forbid tests against null for such expressions.

@sjrd
Copy link
Member

sjrd commented Dec 14, 2023

Both of these use cases are checked by -Ycheck-init, though, aren't they? We're not supposed to read these things before they get properly initialized, even if to compare to null. If we did want to actively compare with null, and consider that a valid test, then we would declare them with an | Null type.

@olhotak
Copy link
Contributor

olhotak commented Dec 14, 2023

For arrays where some elements are expected to be null, could we agree that the recommended style would be to give them a nullable element type? Array[String|Null] Most arrays would be like this, but some would not, for example arrays created from collections of non-null values. The idea was that Array.apply(elem1, elem2) would have non-null element type, new Array(size) would be considered bad style, and a new Array.ofNulls[T](size) method would give an Array[T|Null].

I agree that we sometimes do need to test for null even when the static type is non-nullable, but that should be rare and exceptional. So I'd like something like @unchecked or the upcast (s: String|Null) to mark it. (And prefer documenting the possibility of null in the type in cases where such an annotation or upcast appears necessary.) Are such tests too common to require that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants