From e2fb56a45b8247cf7448a81f4a3fa6b85cf59d46 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 27 Oct 2021 02:08:48 -0400 Subject: [PATCH 1/3] Fix comparing AnyVal | Null with Null --- .../src/dotty/tools/dotc/core/Types.scala | 19 ++++++++++++------- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/explicit-nulls/pos/AnyValOrNull.scala | 16 ++++++++++++++++ 3 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 tests/explicit-nulls/pos/AnyValOrNull.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 6650771963f9..ae7ec91e7108 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -853,18 +853,23 @@ object Types { def goAnd(l: Type, r: Type) = go(l).meet(go(r), pre, safeIntersection = ctx.base.pendingMemberSearches.contains(name)) - def goOr(tp: OrType) = tp match { - case OrNull(tp1) if Nullables.unsafeNullsEnabled => - // Selecting `name` from a type `T | Null` is like selecting `name` from `T`, if - // unsafeNulls is enabled. This can throw at runtime, but we trade soundness for usability. - tp1.findMember(name, pre.stripNull, required, excluded) - case _ => + def goOr(tp: OrType) = + inline def searchAfterJoin = // we need to keep the invariant that `pre <: tp`. Branch `union-types-narrow-prefix` // achieved that by narrowing `pre` to each alternative, but it led to merge errors in // lots of places. The present strategy is instead of widen `tp` using `join` to be a // supertype of `pre`. go(tp.join) - } + + if Nullables.unsafeNullsEnabled then tp match + case OrNull(tp1) if tp1 <:< defn.ObjectType => + // Selecting `name` from a type `T | Null` is like selecting `name` from `T`, if + // unsafeNulls is enabled and T is a subtype of AnyRef. + // This can throw at runtime, but we trade soundness for usability. + tp1.findMember(name, pre.stripNull, required, excluded) + case _ => + searchAfterJoin + else searchAfterJoin val recCount = ctx.base.findMemberCount if (recCount >= Config.LogPendingFindMemberThreshold) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 433075f73fd9..ff8641f4b9c3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -626,7 +626,7 @@ class Typer extends Namer val qual = typedExpr(tree.qualifier, shallowSelectionProto(tree.name, pt, this)) val qual1 = if Nullables.unsafeNullsEnabled then qual.tpe match { - case OrNull(tpe1) => + case OrNull(tpe1) if tpe1 <:< defn.ObjectType => qual.cast(AndType(qual.tpe, tpe1)) case tp => if tp.isNullType diff --git a/tests/explicit-nulls/pos/AnyValOrNull.scala b/tests/explicit-nulls/pos/AnyValOrNull.scala new file mode 100644 index 000000000000..ea3d419d8d77 --- /dev/null +++ b/tests/explicit-nulls/pos/AnyValOrNull.scala @@ -0,0 +1,16 @@ +def test1 = + val v: AnyVal | Null = null + if v == null then + println("null") + +def test2 = + val v: Int | Null = 1 + if v != null then + println(v) + +case class MyVal(val i: Boolean) extends AnyVal + +def test3 = + val v: MyVal | Null = MyVal(false) + if v != null then + println(v) From 2cfafb1687d250b8dcace86aeb26baf2875158db Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 27 Oct 2021 02:24:19 -0400 Subject: [PATCH 2/3] Add test for select --- .../src/dotty/tools/dotc/typer/TypeAssigner.scala | 2 +- tests/explicit-nulls/neg/AnyValOrNullSelect.scala | 13 +++++++++++++ tests/explicit-nulls/pos/AnyValOrNull.scala | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 tests/explicit-nulls/neg/AnyValOrNullSelect.scala diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index f7e33bd4a5f7..3dcec413540f 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -162,7 +162,7 @@ trait TypeAssigner { val qualType = qual.tpe.widenIfUnstable def kind = if tree.isType then "type" else "value" val foundWithoutNull = qualType match - case OrNull(qualType1) => + case OrNull(qualType1) if qualType1 <:< defn.ObjectType => val name = tree.name val pre = maybeSkolemizePrefix(qualType1, name) reallyExists(qualType1.findMember(name, pre)) diff --git a/tests/explicit-nulls/neg/AnyValOrNullSelect.scala b/tests/explicit-nulls/neg/AnyValOrNullSelect.scala new file mode 100644 index 000000000000..44e8b4e7edfb --- /dev/null +++ b/tests/explicit-nulls/neg/AnyValOrNullSelect.scala @@ -0,0 +1,13 @@ +case class MyVal(i: Int) extends AnyVal: + def printVal: Unit = + println(i) + +class Test: + val v: MyVal | Null = MyVal(1) + + def f1 = + v.printVal // error: value printVal is not a member of MyVal | Null + + def f1 = + import scala.language.unsafeNulls + v.printVal // error: value printVal is not a member of MyVal | Null diff --git a/tests/explicit-nulls/pos/AnyValOrNull.scala b/tests/explicit-nulls/pos/AnyValOrNull.scala index ea3d419d8d77..7fbe691901c3 100644 --- a/tests/explicit-nulls/pos/AnyValOrNull.scala +++ b/tests/explicit-nulls/pos/AnyValOrNull.scala @@ -8,7 +8,7 @@ def test2 = if v != null then println(v) -case class MyVal(val i: Boolean) extends AnyVal +case class MyVal(i: Boolean) extends AnyVal def test3 = val v: MyVal | Null = MyVal(false) From 279d2f52dc54ec69801eb297f6b5b620cdd3d408 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 27 Oct 2021 13:55:35 -0400 Subject: [PATCH 3/3] Update test --- tests/explicit-nulls/pos/AnyValOrNull.scala | 46 +++++++++++++++------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/tests/explicit-nulls/pos/AnyValOrNull.scala b/tests/explicit-nulls/pos/AnyValOrNull.scala index 7fbe691901c3..098d3eba973d 100644 --- a/tests/explicit-nulls/pos/AnyValOrNull.scala +++ b/tests/explicit-nulls/pos/AnyValOrNull.scala @@ -1,16 +1,36 @@ -def test1 = - val v: AnyVal | Null = null - if v == null then - println("null") +case class MyVal(i: Boolean) extends AnyVal -def test2 = - val v: Int | Null = 1 - if v != null then - println(v) +class Test1: -case class MyVal(i: Boolean) extends AnyVal + def test1 = + val v: AnyVal | Null = null + if v == null then + println("null") + + def test2 = + val v: Int | Null = 1 + if v != null then + println(v) + + def test3 = + val v: MyVal | Null = MyVal(false) + if v != null then + println(v) + +class Test2: + import scala.language.unsafeNulls + + def test1 = + val v: AnyVal | Null = null + if v == null then + println("null") + + def test2 = + val v: Int | Null = 1 + if v != null then + println(v) -def test3 = - val v: MyVal | Null = MyVal(false) - if v != null then - println(v) + def test3 = + val v: MyVal | Null = MyVal(false) + if v != null then + println(v)