Skip to content

Allow Null in isInstanceOf checks #14897

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

Closed
wants to merge 1 commit into from
Closed
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
10 changes: 7 additions & 3 deletions compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
7 changes: 5 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}
Expand Down Expand Up @@ -370,5 +373,5 @@ object TypeTestsCasts {
case _ => EmptyTree
}
interceptWith(expr)
}
}//.showing(i"intercept $tree -> $result")
}
7 changes: 0 additions & 7 deletions tests/neg/i4004.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
@main def Test =
"a".isInstanceOf[Null] // error
null.isInstanceOf[Null] // error
"a".isInstanceOf[Nothing] // error
"a".isInstanceOf[Singleton] // error

Expand All @@ -9,8 +7,3 @@
case _: Nothing => () // error
case _: Singleton => () // error
case _ => ()

null match
case _: Null => () // error
case _ => ()

2 changes: 2 additions & 0 deletions tests/pos/i14896.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
object Ex { def unapply(p: Any): Option[_ <: Int] = null }
object Foo { val Ex(_) = null: @unchecked }
15 changes: 15 additions & 0 deletions tests/run/i4004.scala
Original file line number Diff line number Diff line change
@@ -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 => ()