@@ -161,15 +161,17 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
161
161
cmpWithBoxed(cls2, cls1)
162
162
else if ctx.mode.is(Mode .SafeNulls ) then
163
163
// If explicit nulls is enabled, and unsafeNulls is not enabled,
164
+ // and the types don't have `@CanEqualNull` annotation,
164
165
// we want to disallow comparison between Object and Null.
165
166
// If we have to check whether a variable with a non-nullable type has null value
166
167
// (for example, a NotNull java method returns null for some reasons),
167
- // we can still cast it to a nullable type then compare its value.
168
+ // we can still use `eq/ne null` or cast it to a nullable type then compare its value.
168
169
//
169
170
// Example:
170
171
// val x: String = null.asInstanceOf[String]
171
172
// if (x == null) {} // error: x is non-nullable
172
173
// if (x.asInstanceOf[String|Null] == null) {} // ok
174
+ // if (x eq null) {} // ok
173
175
cls1 == defn.NullClass && cls1 == cls2
174
176
else if cls1 == defn.NullClass then
175
177
cls1 == cls2 || cls2.derivesFrom(defn.ObjectClass )
@@ -184,6 +186,14 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
184
186
* interpret.
185
187
*/
186
188
def canComparePredefined (tp1 : Type , tp2 : Type ) =
189
+ // In explicit nulls, when one of type has `@CanEqualNull` annotation,
190
+ // we use unsafe nulls semantic to check, which allows reference types
191
+ // to be compared with `Null`.
192
+ // Example:
193
+ // val s1: String = ???
194
+ // s1 == null // error
195
+ // val s2: String @CanEqualNull = ???
196
+ // s2 == null // ok
187
197
val checkCtx = if ctx.explicitNulls
188
198
&& (tp1.hasAnnotation(defn.CanEqualNullAnnot ) || tp2.hasAnnotation(defn.CanEqualNullAnnot ))
189
199
then ctx.retractMode(Mode .SafeNulls ) else ctx
0 commit comments