From 18d1f68fe3331367dbbc200dced2f481daeea935 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 9 Apr 2022 15:06:36 +0200 Subject: [PATCH] Allow Null in isInstanceOf checks With -Yexplicit-nulls Null is a class like any other. No reason to treat it specially in type tests. Besides, as #14896 shows these type tests might not be user-written. Aside: Going through the issues I have noted much higher-breakage rates than usual in everything that concerns type test optimization and diagnostics. I don't think we can afford much more breakage in this area. In the future, we should try harder to err on the side of caution, which means no second guessing of what the programmer wrote or what a previous phase generated. Default to a runtime test, unless you know with 100% certainty that no possible scenario would be affected by that test's outcome. Fixes #14896 --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 10 +++++++--- .../src/dotty/tools/dotc/core/Definitions.scala | 2 +- .../tools/dotc/transform/TypeTestsCasts.scala | 7 +++++-- tests/neg/i4004.scala | 7 ------- tests/pos/i14896.scala | 2 ++ tests/run/i4004.scala | 15 +++++++++++++++ 6 files changed, 30 insertions(+), 13 deletions(-) create mode 100644 tests/pos/i14896.scala create mode 100644 tests/run/i4004.scala diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index fb8bd917ca20..60121f5fef6c 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1005,15 +1005,19 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { else if (!ctx.erasedTypes) cast(tp) else Erasure.Boxing.adaptToType(tree, tp) + /** `tree eq null` (might need a cast to be type correct) */ + def testNull(using Context): Tree = nullTest(defn.Object_eq) + /** `tree ne null` (might need a cast to be type correct) */ - def testNotNull(using Context): Tree = { + def testNotNull(using Context): Tree = nullTest(defn.Object_ne) + + private def nullTest(op: Symbol)(using Context) = val receiver = if (tree.tpe.isBottomType) // If the receiver is of type `Nothing` or `Null`, add an ascription so that the selection // succeeds: e.g. `null.ne(null)` doesn't type, but `(null: AnyRef).ne(null)` does. Typed(tree, TypeTree(defn.AnyRefType)) else tree.ensureConforms(defn.ObjectType) - receiver.select(defn.Object_ne).appliedTo(nullLiteral).withSpan(tree.span) - } + receiver.select(op).appliedTo(nullLiteral).withSpan(tree.span) /** If inititializer tree is `_`, the default value of its type, * otherwise the tree itself. diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index dc672690702c..86a3344bcccb 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1256,7 +1256,7 @@ class Definitions { @tu lazy val topClasses: Set[Symbol] = Set(AnyClass, MatchableClass, ObjectClass, AnyValClass) - @tu lazy val untestableClasses: Set[Symbol] = Set(NothingClass, NullClass, SingletonClass) + @tu lazy val untestableClasses: Set[Symbol] = Set(NothingClass, SingletonClass) @tu lazy val AbstractFunctionType: Array[TypeRef] = mkArityArray("scala.runtime.AbstractFunction", MaxImplementedFunctionArity, 0).asInstanceOf[Array[TypeRef]] val AbstractFunctionClassPerRun: PerRun[Array[Symbol]] = new PerRun(AbstractFunctionType.map(_.symbol.asClass)) diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index c2367e6bfde0..44f40fb4b7e2 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -245,9 +245,10 @@ object TypeTestsCasts { if (expr.tpe <:< testType) && inMatch then if expr.tpe.isNotNull then constant(expr, Literal(Constant(true))) + else if testCls == defn.NullClass then expr.testNull else expr.testNotNull else { - if expr.tpe.isBottomType then + if expr.tpe.widen.isNothingType then report.warning(TypeTestAlwaysDiverges(expr.tpe, testType), tree.srcPos) val nestedCtx = ctx.fresh.setNewTyperState() val foundClsSyms = foundClasses(expr.tpe.widen, Nil) @@ -263,6 +264,8 @@ object TypeTestsCasts { case _ => transformIsInstanceOf( expr, defn.boxedType(testCls.typeRef), testCls.typeRef, flagUnrelated) + else if testCls == defn.NullClass then + expr.testNull else derivedTree(expr, defn.Any_isInstanceOf, testType) } @@ -370,5 +373,5 @@ object TypeTestsCasts { case _ => EmptyTree } interceptWith(expr) - } + }//.showing(i"intercept $tree -> $result") } diff --git a/tests/neg/i4004.scala b/tests/neg/i4004.scala index bf757a0863a7..07afad0ce8a9 100644 --- a/tests/neg/i4004.scala +++ b/tests/neg/i4004.scala @@ -1,6 +1,4 @@ @main def Test = - "a".isInstanceOf[Null] // error - null.isInstanceOf[Null] // error "a".isInstanceOf[Nothing] // error "a".isInstanceOf[Singleton] // error @@ -9,8 +7,3 @@ case _: Nothing => () // error case _: Singleton => () // error case _ => () - - null match - case _: Null => () // error - case _ => () - diff --git a/tests/pos/i14896.scala b/tests/pos/i14896.scala new file mode 100644 index 000000000000..2ef595fbcaa4 --- /dev/null +++ b/tests/pos/i14896.scala @@ -0,0 +1,2 @@ +object Ex { def unapply(p: Any): Option[_ <: Int] = null } +object Foo { val Ex(_) = null: @unchecked } \ No newline at end of file diff --git a/tests/run/i4004.scala b/tests/run/i4004.scala new file mode 100644 index 000000000000..96ed375b13f9 --- /dev/null +++ b/tests/run/i4004.scala @@ -0,0 +1,15 @@ +@main def Test = + assert(!("a": Any).isInstanceOf[Null]) + assert(null.isInstanceOf[Null]) + assert((null: Any).isInstanceOf[Null]) + + ("a": Any) match + case _: Null => assert(false) + case _ => + + null match + case _: Null => () + + (null: Any) match + case _: Null => () +