Skip to content
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

Fix case null on non-nullable type in unsafe nulls #13976

Merged
merged 4 commits into from
Feb 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ object SymDenotations {
/** Is this symbol a class of which `null` is a value? */
final def isNullableClass(using Context): Boolean =
if ctx.mode.is(Mode.SafeNulls) && !ctx.phase.erasedTypes
then symbol == defn.NullClass || symbol == defn.AnyClass
then symbol == defn.NullClass || symbol == defn.AnyClass || symbol == defn.MatchableClass
else isNullableClassAfterErasure

/** Is this symbol a class of which `null` is a value after erasure?
Expand Down
18 changes: 5 additions & 13 deletions compiler/src/dotty/tools/dotc/transform/patmat/Space.scala
Original file line number Diff line number Diff line change
Expand Up @@ -330,24 +330,15 @@ class SpaceEngine(using Context) extends SpaceLogic {

private val constantNullType = ConstantType(Constant(null))

/** Does the given tree stand for the literal `null`? */
def isNullLit(tree: Tree): Boolean = tree match {
case Literal(Constant(null)) => true
case _ => false
}

override def intersectUnrelatedAtomicTypes(tp1: Type, tp2: Type): Space = trace(s"atomic intersection: ${AndType(tp1, tp2).show}", debug) {
// Precondition: !isSubType(tp1, tp2) && !isSubType(tp2, tp1).
if (!ctx.explicitNulls && (tp1.isNullType || tp2.isNullType)) {
if !ctx.mode.is(Mode.SafeNulls) && (tp1.isNullType || tp2.isNullType) then
// Since projections of types don't include null, intersection with null is empty.
Empty
}
else {
else
val res = TypeComparer.provablyDisjoint(tp1, tp2)

if (res) Empty
if res then Empty
else Typ(AndType(tp1, tp2), decomposed = true)
}
}

/** Return the space that represents the pattern `pat` */
Expand Down Expand Up @@ -549,7 +540,8 @@ class SpaceEngine(using Context) extends SpaceLogic {

/** Is `tp1` a subtype of `tp2`? */
def isSubType(tp1: Type, tp2: Type): Boolean = trace(i"$tp1 <:< $tp2", debug, show = true) {
if tp1 == constantNullType && !ctx.explicitNulls then tp2 == constantNullType
if tp1 == constantNullType && !ctx.mode.is(Mode.SafeNulls)
then tp2 == constantNullType
else adaptType(tp1, tp2) <:< tp2
}

Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ class CompilationTests {
aggregateTests(
compileFilesInDir("tests/explicit-nulls/pos", explicitNullsOptions),
compileFilesInDir("tests/explicit-nulls/pos-separate", explicitNullsOptions),
compileFilesInDir("tests/explicit-nulls/pos-patmat", explicitNullsOptions and "-Xfatal-warnings"),
compileFilesInDir("tests/explicit-nulls/unsafe-common", explicitNullsOptions and "-language:unsafeNulls"),
)
}.checkCompile()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
class Foo {

val s: String = ???
s match {
case s: String => 100 // warning: type test will always succeed
case _ => 200 // error: unreachable
}

s match {
case s: String => 100 // warning: type test will always succeed
case s: String => 100
case _ => 200 // error: unreachable
}

Expand All @@ -15,20 +12,23 @@ class Foo {
case object Cat extends Animal

val a: Animal = ???

a match {
case Dog(name) => 100
case Cat => 200
case _ => 300 // error: unreachable
}

val a2: Animal|Null = ???
val a2: Animal | Null = ???

a2 match {
case Dog(_) => 100
case Cat => 200
case _ => 300
}

val a3: Animal|Null = ???
val a3: Animal | Null = ???

a3 match {
case Dog(_) => 100
case Cat => 200
Expand Down
14 changes: 14 additions & 0 deletions tests/explicit-nulls/pos-patmat/unsafe-match-null-pat.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import scala.language.unsafeNulls

def test1 =
val s: String = ???
s match
case _: String =>
// under unsafeNulls, we should not get Match case Unreachable Warning
case null => // ok

def test2 =
val s: String | Null = ???
s match
case _: String =>
case null =>
11 changes: 11 additions & 0 deletions tests/explicit-nulls/unsafe-common/unsafe-match-null.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
def test1 =
val s: String = ???
s match
case _: String =>
case null => // error: Values of types Null and String cannot be compared

def test2 =
val s: String | Null = ???
s match
case _: String =>
case null =>