diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 514377802e57..10f3fe634495 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -889,9 +889,15 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { else Erasure.Boxing.adaptToType(tree, tp) /** `tree ne null` (might need a cast to be type correct) */ - def testNotNull(implicit ctx: Context): Tree = - tree.ensureConforms(defn.ObjectType) - .select(defn.Object_ne).appliedTo(Literal(Constant(null))) + def testNotNull(implicit ctx: Context): Tree = { + val receiver = if (defn.isBottomType(tree.tpe)) { + // 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(Literal(Constant(null))) + } /** If inititializer tree is `_', the default value of its type, * otherwise the tree itself. diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 65aadb86ccb9..00ebc03cff46 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -335,10 +335,16 @@ object PatternMatcher { patternPlan(casted, pat, onSuccess) }) case UnApply(extractor, implicits, args) => - val mt @ MethodType(_) = extractor.tpe.widen - var unapp = extractor.appliedTo(ref(scrutinee).ensureConforms(mt.paramInfos.head)) - if (implicits.nonEmpty) unapp = unapp.appliedToArgs(implicits) - val unappPlan = unapplyPlan(unapp, args) + val unappPlan = if (defn.isBottomType(scrutinee.info)) { + // Generate a throwaway but type-correct plan. + // This plan will never execute because it'll be guarded by a `NonNullTest`. + ResultPlan(tpd.Throw(tpd.Literal(Constant(null)))) + } else { + val mt @ MethodType(_) = extractor.tpe.widen + var unapp = extractor.appliedTo(ref(scrutinee).ensureConforms(mt.paramInfos.head)) + if (implicits.nonEmpty) unapp = unapp.appliedToArgs(implicits) + unapplyPlan(unapp, args) + } if (scrutinee.info.isNotNull || nonNull(scrutinee)) unappPlan else TestPlan(NonNullTest, scrutinee, tree.span, unappPlan) case Bind(name, body) => diff --git a/tests/run/i5067.check b/tests/run/i5067.check new file mode 100644 index 000000000000..7f5dbdea750f --- /dev/null +++ b/tests/run/i5067.check @@ -0,0 +1,6 @@ +matches null literal +matches null literal +not implemented +match error +not implemented +match error diff --git a/tests/run/i5067.scala b/tests/run/i5067.scala new file mode 100644 index 000000000000..663ccb15e39a --- /dev/null +++ b/tests/run/i5067.scala @@ -0,0 +1,47 @@ +// Test that we correctly handle scrutinees with type `Null` or `Nothing`. +object Test { + def main(args: Array[String]): Unit = { + null match { + case Some(_) => println("matches Some") + case (_, _) => println("matches Pair") + case null => println("matches null literal") + } + + type X = Null + (null: X) match { + case Some(_) => println("matches Some") + case (_, _) => println("matches Pair") + case null => println("matches null literal") + } + + type Y = Nothing + try { + (??? : Y) match { + case Some(_) => println("matches Some") + case _ => println("matches anything") + } + } catch { + case e: NotImplementedError => println("not implemented") + } + + + try { + val Some(_) = null + } catch { + case e: MatchError => println("match error") + } + + try { + val Some(_) = ??? + } catch { + case e: NotImplementedError => println("not implemented") + } + + val x: X = null + try { + val Some(_) = x + } catch { + case e: MatchError => println("match error") + } + } +} diff --git a/tests/run/i5067b.check b/tests/run/i5067b.check new file mode 100644 index 000000000000..be60186f7c25 --- /dev/null +++ b/tests/run/i5067b.check @@ -0,0 +1,3 @@ +match error +match error nested +not implemented error diff --git a/tests/run/i5067b.scala b/tests/run/i5067b.scala new file mode 100644 index 000000000000..cd1f3898cb7e --- /dev/null +++ b/tests/run/i5067b.scala @@ -0,0 +1,34 @@ +// Test that we correctly handle scrutinees with type `Null` or `Nothing`. +object Test { + def main(args: Array[String]): Unit = { + class B[T] {} + object B { + def unapply[T](x: Any): Option[B[T]] = None + } + try { + val B(_) = null + } catch { + case e: MatchError => println("match error") + } + + null match { + case null => + try { + null match { + case Some(_) => () + } + } catch { + case e: MatchError => println("match error nested") + } + } + + try { + ??? match { + case (_, _) => () + case _ => () + } + } catch { + case e: NotImplementedError => println("not implemented error") + } + } +}