Skip to content

Commit 2a8cc49

Browse files
committed
Disallow comparison between object and null
1 parent 539051a commit 2a8cc49

File tree

6 files changed

+41
-85
lines changed

6 files changed

+41
-85
lines changed

compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ object JavaNullInterop {
5252
*
5353
* But the selection can throw an NPE if the returned value is `null`.
5454
*/
55-
def nullifyMember(sym: Symbol, tp: Type, isEnum: Boolean)(implicit ctx: Context): Type = {
55+
def nullifyMember(sym: Symbol, tp: Type, isEnumValueDef: Boolean)(implicit ctx: Context): Type = {
5656
assert(ctx.explicitNulls)
5757
assert(sym.is(JavaDefined), "can only nullify java-defined members")
5858

5959
// Some special cases when nullifying the type
60-
if (isEnum || sym.name == nme.TYPE_)
60+
if (isEnumValueDef || sym.name == nme.TYPE_)
6161
// Don't nullify the `TYPE` field in every class and Java enum instances
6262
tp
6363
else if (sym.name == nme.toString_ || sym.isConstructor || hasNotNullAnnot(sym))

compiler/src/dotty/tools/dotc/typer/Implicits.scala

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -813,9 +813,20 @@ trait Implicits { self: Typer =>
813813
else if (cls2.isPrimitiveValueClass)
814814
cmpWithBoxed(cls2, cls1)
815815
else if (cls1 == defn.NullClass)
816-
cls1 == cls2 || cls2.derivesFrom(defn.ObjectClass)
817-
else if (cls2 == defn.NullClass)
818-
cls1.derivesFrom(defn.ObjectClass)
816+
cls1 == cls2
817+
else if (!ctx.explicitNulls)
818+
// If explicit nulls is enabled, we want to disallow comparison between Object and Null.
819+
// If a nullable value has a non-nullable type, we can still cast it to nullable type
820+
// then compare.
821+
//
822+
// Example:
823+
// val x: String = null.asInstanceOf[String]
824+
// if (x == null) {} // error: x is non-nullable
825+
// if (x.asInstanceOf[String|Null] == null) {} // ok
826+
if (cls1 == defn.NullClass)
827+
cls2.derivesFrom(defn.ObjectClass)
828+
else
829+
cls2 == defn.NullClass && cls1.derivesFrom(defn.ObjectClass)
819830
else
820831
false
821832
}
@@ -1464,7 +1475,7 @@ trait Implicits { self: Typer =>
14641475
case alt1: SearchSuccess =>
14651476
var diff = compareCandidate(alt1, alt2.ref, alt2.level)
14661477
assert(diff <= 0) // diff > 0 candidates should already have been eliminated in `rank`
1467-
1478+
14681479
if diff == 0 then
14691480
// Fall back: if both results are extension method applications,
14701481
// compare the extension methods instead of their wrappers.

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ class CompilationTests extends ParallelTesting {
5959
compileFile("tests/pos-special/typeclass-scaling.scala", defaultOptions.and("-Xmax-inlines", "40")),
6060
compileFile("tests/pos-special/indent-colons.scala", defaultOptions.and("-Yindent-colons")),
6161
compileFile("tests/pos-special/i7296.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings")),
62-
compileFile("tests/pos-special/nullable.scala", defaultOptions.and("-Yexplicit-nulls")),
6362
compileFile("tests/pos-special/notNull.scala", defaultOptions.and("-Yexplicit-nulls")),
6463
compileDir("tests/pos-special/adhoc-extension", defaultOptions.and("-strict", "-feature", "-Xfatal-warnings")),
6564
compileFile("tests/pos-special/i7575.scala", defaultOptions.and("-language:dynamics")),

tests/explicit-nulls/neg/eq.scala

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,36 @@
22
class Foo {
33
// Null itself
44
val x0: Null = null
5+
x0 != x0
56
x0 == null
67
x0 != null
78
null == x0
89
null == null
10+
null != null
911

10-
// Nullable types: OK
11-
val x1: String|Null = null
12-
x1 == null
13-
null == x1
14-
x1 == x0
15-
x1 != x0
16-
x0 == x1
12+
// Non-nullable types: error
13+
val x1: String = "hello"
14+
x1 != null // error
15+
x1 == null // error
16+
null == x1 // error
17+
null != x1 // error
18+
x1 == x0 // error
19+
x0 != x1 // error
20+
x1.asInstanceOf[String|Null] == null
21+
x1.asInstanceOf[String|Null] == x0
22+
x1.asInstanceOf[Any] == null
23+
x1.asInstanceOf[Any] == x0
1724

18-
// Reference types, even non-nullable ones: OK.
19-
// Allowed as an escape hatch.
20-
val x2: String = "hello"
21-
x2 != null
25+
// Nullable types: OK
26+
val x2: String|Null = null
2227
x2 == null
2328
null == x2
29+
x2 == x0
30+
x2 != x0
31+
x0 == x2
32+
x2 == x1
33+
x2 != x1
34+
x1 == x2
2435

2536
// Value types: not allowed.
2637
1 == null // error

tests/explicit-nulls/run/eq.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ object Test {
1414
assert(x != "xx")
1515
assert(x != y)
1616
assert(y == y)
17+
assert(z.asInstanceOf[String|Null] != null)
18+
assert(z.asInstanceOf[Any] != null)
1719

1820
assert(x != null)
1921
assert(null != x)

tests/pos-special/nullable.scala

Lines changed: 0 additions & 67 deletions
This file was deleted.

0 commit comments

Comments
 (0)