@@ -164,15 +164,17 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
164164 cmpWithBoxed(cls2, cls1)
165165 else if ctx.mode.is(Mode .SafeNulls ) then
166166 // If explicit nulls is enabled, and unsafeNulls is not enabled,
167+ // and the types don't have `@CanEqualNull` annotation,
167168 // we want to disallow comparison between Object and Null.
168169 // If we have to check whether a variable with a non-nullable type has null value
169170 // (for example, a NotNull java method returns null for some reasons),
170- // we can still cast it to a nullable type then compare its value.
171+ // we can still use `eq/ne null` or cast it to a nullable type then compare its value.
171172 //
172173 // Example:
173174 // val x: String = null.asInstanceOf[String]
174175 // if (x == null) {} // error: x is non-nullable
175176 // if (x.asInstanceOf[String|Null] == null) {} // ok
177+ // if (x eq null) {} // ok
176178 cls1 == defn.NullClass && cls1 == cls2
177179 else if cls1 == defn.NullClass then
178180 cls1 == cls2 || cls2.derivesFrom(defn.ObjectClass )
@@ -187,6 +189,14 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
187189 * interpret.
188190 */
189191 def canComparePredefined (tp1 : Type , tp2 : Type ) =
192+ // In explicit nulls, when one of type has `@CanEqualNull` annotation,
193+ // we use unsafe nulls semantic to check, which allows reference types
194+ // to be compared with `Null`.
195+ // Example:
196+ // val s1: String = ???
197+ // s1 == null // error
198+ // val s2: String @CanEqualNull = ???
199+ // s2 == null // ok
190200 val checkCtx = if ctx.explicitNulls
191201 && (tp1.hasAnnotation(defn.CanEqualNullAnnot ) || tp2.hasAnnotation(defn.CanEqualNullAnnot ))
192202 then ctx.retractMode(Mode .SafeNulls ) else ctx
0 commit comments