@@ -159,15 +159,17 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
159159 cmpWithBoxed(cls2, cls1)
160160 else if ctx.mode.is(Mode .SafeNulls ) then
161161 // If explicit nulls is enabled, and unsafeNulls is not enabled,
162+ // and the types don't have `@CanEqualNull` annotation,
162163 // we want to disallow comparison between Object and Null.
163164 // If we have to check whether a variable with a non-nullable type has null value
164165 // (for example, a NotNull java method returns null for some reasons),
165- // we can still cast it to a nullable type then compare its value.
166+ // we can still use `eq/ne null` or cast it to a nullable type then compare its value.
166167 //
167168 // Example:
168169 // val x: String = null.asInstanceOf[String]
169170 // if (x == null) {} // error: x is non-nullable
170171 // if (x.asInstanceOf[String|Null] == null) {} // ok
172+ // if (x eq null) {} // ok
171173 cls1 == defn.NullClass && cls1 == cls2
172174 else if cls1 == defn.NullClass then
173175 cls1 == cls2 || cls2.derivesFrom(defn.ObjectClass )
@@ -182,6 +184,14 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
182184 * interpret.
183185 */
184186 def canComparePredefined (tp1 : Type , tp2 : Type ) =
187+ // In explicit nulls, when one of type has `@CanEqualNull` annotation,
188+ // we use unsafe nulls semantic to check, which allows reference types
189+ // to be compared with `Null`.
190+ // Example:
191+ // val s1: String = ???
192+ // s1 == null // error
193+ // val s2: String @CanEqualNull = ???
194+ // s2 == null // ok
185195 val checkCtx = if ctx.explicitNulls
186196 && (tp1.hasAnnotation(defn.CanEqualNullAnnot ) || tp2.hasAnnotation(defn.CanEqualNullAnnot ))
187197 then ctx.retractMode(Mode .SafeNulls ) else ctx
0 commit comments