From 31428b21497320e3e90561376a9804e6c0669224 Mon Sep 17 00:00:00 2001 From: Seyon Sivatharan Date: Fri, 30 May 2025 01:09:27 -0400 Subject: [PATCH 1/2] Modify pattern matcher to raise an error instead of generating illegal isInstanceOf[Null] and isInstanceOf[Nothing] Added a check to the pattern matcher to raise an error when Null or Nothing is used in a type pattern instead of generating isInstanceOf[Null] and isInstanceOf[Nothing] and raising an error later in the erasure phase --- .../src/dotty/tools/dotc/core/Types.scala | 26 +++++++++++++++++++ .../tools/dotc/transform/PatternMatcher.scala | 3 +++ tests/neg/i23243.check | 4 +++ tests/neg/i23243.scala | 9 +++++++ tests/neg/i23243a.scala | 8 ++++++ tests/neg/i4004.scala | 5 ---- tests/neg/i4004a.scala | 10 +++++++ 7 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 tests/neg/i23243.check create mode 100644 tests/neg/i23243.scala create mode 100644 tests/neg/i23243a.scala create mode 100644 tests/neg/i4004a.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 728f742ea1ad..9ee4001e015c 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -370,6 +370,32 @@ object Types extends TypeUtils { loop(this) } + /* Is this type exactly Nothing or is an AndOrType with a type that is exactly Nothing? */ + def hasNothing(using Context): Boolean = { + def loop(tp: Type): Boolean = tp match { + case tp: TypeRef => + tp.isExactlyNothing + case tp: AndOrType => + loop(tp.tp1) || loop(tp.tp2) + case _ => + false + } + loop(this) + } + + /* Is this type exactly Null or is an AndOrType with a type that is exactly Null? */ + def hasNull(using Context): Boolean = { + def loop(tp: Type): Boolean = tp match { + case tp: TypeRef => + tp.isExactlyNull + case tp: AndOrType => + loop(tp.tp1) || loop(tp.tp2) + case _ => + false + } + loop(this) + } + /** Is this type guaranteed not to have `null` as a value? */ final def isNotNull(using Context): Boolean = this match { case tp: ConstantType => tp.value.value != null diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index e2505144abda..e2a7df1e05f4 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -418,6 +418,9 @@ object PatternMatcher { && !hasExplicitTypeArgs(extractor) case _ => false } + + if (tpt.tpe.hasNull || tpt.tpe.hasNothing) report.error(em"${tpt.tpe} cannot be used in runtime type tests", tpt) + TestPlan(TypeTest(tpt, isTrusted), scrutinee, tree.span, letAbstract(ref(scrutinee).cast(tpt.tpe)) { casted => nonNull += casted diff --git a/tests/neg/i23243.check b/tests/neg/i23243.check new file mode 100644 index 000000000000..9a73f6ef280d --- /dev/null +++ b/tests/neg/i23243.check @@ -0,0 +1,4 @@ +-- Error: tests\neg\i23243.scala:8:20 ---------------------------------------------------------------------------------- +8 | case Extractor() => println("foo matched") // error + | ^ + | String | Null cannot be used in runtime type tests diff --git a/tests/neg/i23243.scala b/tests/neg/i23243.scala new file mode 100644 index 000000000000..9dd748da53ed --- /dev/null +++ b/tests/neg/i23243.scala @@ -0,0 +1,9 @@ +object Extractor: + def unapply(s: String|Null): Boolean = true + +class A + +def main = + ("foo": (A|String)) match + case Extractor() => println("foo matched") // error + case _ => println("foo didn't match") diff --git a/tests/neg/i23243a.scala b/tests/neg/i23243a.scala new file mode 100644 index 000000000000..c6330e49c17e --- /dev/null +++ b/tests/neg/i23243a.scala @@ -0,0 +1,8 @@ +def main = { + ("foo": String) match { + case a: (String | Nothing) => // error + println("bar") + case _ => + println("foo") + } +} diff --git a/tests/neg/i4004.scala b/tests/neg/i4004.scala index bf757a0863a7..37b55891fd83 100644 --- a/tests/neg/i4004.scala +++ b/tests/neg/i4004.scala @@ -1,13 +1,8 @@ @main def Test = - "a".isInstanceOf[Null] // error - null.isInstanceOf[Null] // error - "a".isInstanceOf[Nothing] // error - "a".isInstanceOf[Singleton] // error "a" match case _: Null => () // error case _: Nothing => () // error - case _: Singleton => () // error case _ => () null match diff --git a/tests/neg/i4004a.scala b/tests/neg/i4004a.scala new file mode 100644 index 000000000000..10201409c908 --- /dev/null +++ b/tests/neg/i4004a.scala @@ -0,0 +1,10 @@ +@main def Test = + "a".isInstanceOf[Null] // error + null.isInstanceOf[Null] // error + "a".isInstanceOf[Nothing] // error + "a".isInstanceOf[Singleton] // error + + "a" match { + case _: Singleton => () // error + case _ => () + } From 725bf5ea1d69cad81f81bdc49f5e9211c8aef20b Mon Sep 17 00:00:00 2001 From: Seyon Sivatharan Date: Sat, 31 May 2025 19:55:59 -0400 Subject: [PATCH 2/2] Revert previous changes and simplify Nothing | T to T and Null | T to if T is nullable --- .../src/dotty/tools/dotc/core/Types.scala | 26 ------------------- .../tools/dotc/transform/PatternMatcher.scala | 2 -- .../tools/dotc/transform/TypeTestsCasts.scala | 10 ++++--- 3 files changed, 7 insertions(+), 31 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 9ee4001e015c..728f742ea1ad 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -370,32 +370,6 @@ object Types extends TypeUtils { loop(this) } - /* Is this type exactly Nothing or is an AndOrType with a type that is exactly Nothing? */ - def hasNothing(using Context): Boolean = { - def loop(tp: Type): Boolean = tp match { - case tp: TypeRef => - tp.isExactlyNothing - case tp: AndOrType => - loop(tp.tp1) || loop(tp.tp2) - case _ => - false - } - loop(this) - } - - /* Is this type exactly Null or is an AndOrType with a type that is exactly Null? */ - def hasNull(using Context): Boolean = { - def loop(tp: Type): Boolean = tp match { - case tp: TypeRef => - tp.isExactlyNull - case tp: AndOrType => - loop(tp.tp1) || loop(tp.tp2) - case _ => - false - } - loop(this) - } - /** Is this type guaranteed not to have `null` as a value? */ final def isNotNull(using Context): Boolean = this match { case tp: ConstantType => tp.value.value != null diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index e2a7df1e05f4..4ad31d65550c 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -418,8 +418,6 @@ object PatternMatcher { && !hasExplicitTypeArgs(extractor) case _ => false } - - if (tpt.tpe.hasNull || tpt.tpe.hasNothing) report.error(em"${tpt.tpe} cannot be used in runtime type tests", tpt) TestPlan(TypeTest(tpt, isTrusted), scrutinee, tree.span, letAbstract(ref(scrutinee).cast(tpt.tpe)) { casted => diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index a8c8ec8ce1d8..4798683da68b 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -330,13 +330,17 @@ object TypeTestsCasts { expr.isInstance(testType).withSpan(tree.span) case OrType(tp1, tp2) => evalOnce(expr) { e => - transformTypeTest(e, tp1, flagUnrelated = false) - .or(transformTypeTest(e, tp2, flagUnrelated = false)) + lazy val tp1Tree = transformTypeTest(e, tp1, flagUnrelated = false) + lazy val tp2Tree = transformTypeTest(e, tp2, flagUnrelated = false) + + if (tp1.isNothingType || (tp1.isNullType && !tp2.isNotNull)) tp2Tree + else if (tp2.isNothingType || (tp2.isNullType && !tp1.isNotNull)) tp1Tree + else tp1Tree.or(tp2Tree) } case AndType(tp1, tp2) => evalOnce(expr) { e => transformTypeTest(e, tp1, flagUnrelated) - .and(transformTypeTest(e, tp2, flagUnrelated)) + .and(transformTypeTest(e, tp2, flagUnrelated)) } case defn.MultiArrayOf(elem, ndims) if isGenericArrayElement(elem, isScala2 = false) => def isArrayTest(arg: Tree) =