Skip to content

Commit 4c3e170

Browse files
authored
Merge pull request #5075 from abeln/pattern-match-nothing
Fix #5067: handle scrutinee of bottom type in pattern matcher
2 parents 25de286 + bf2fd57 commit 4c3e170

File tree

6 files changed

+109
-7
lines changed

6 files changed

+109
-7
lines changed

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -891,9 +891,15 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
891891
else Erasure.Boxing.adaptToType(tree, tp)
892892

893893
/** `tree ne null` (might need a cast to be type correct) */
894-
def testNotNull(implicit ctx: Context): Tree =
895-
tree.ensureConforms(defn.ObjectType)
896-
.select(defn.Object_ne).appliedTo(Literal(Constant(null)))
894+
def testNotNull(implicit ctx: Context): Tree = {
895+
val receiver = if (defn.isBottomType(tree.tpe)) {
896+
// If the receiver is of type `Nothing` or `Null`, add an ascription so that the selection
897+
// succeeds: e.g. `null.ne(null)` doesn't type, but `(null: AnyRef).ne(null)` does.
898+
Typed(tree, TypeTree(defn.AnyRefType))
899+
}
900+
else tree.ensureConforms(defn.ObjectType)
901+
receiver.select(defn.Object_ne).appliedTo(Literal(Constant(null)))
902+
}
897903

898904
/** If inititializer tree is `_', the default value of its type,
899905
* otherwise the tree itself.

compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -335,10 +335,16 @@ object PatternMatcher {
335335
patternPlan(casted, pat, onSuccess)
336336
})
337337
case UnApply(extractor, implicits, args) =>
338-
val mt @ MethodType(_) = extractor.tpe.widen
339-
var unapp = extractor.appliedTo(ref(scrutinee).ensureConforms(mt.paramInfos.head))
340-
if (implicits.nonEmpty) unapp = unapp.appliedToArgs(implicits)
341-
val unappPlan = unapplyPlan(unapp, args)
338+
val unappPlan = if (defn.isBottomType(scrutinee.info)) {
339+
// Generate a throwaway but type-correct plan.
340+
// This plan will never execute because it'll be guarded by a `NonNullTest`.
341+
ResultPlan(tpd.Throw(tpd.Literal(Constant(null))))
342+
} else {
343+
val mt @ MethodType(_) = extractor.tpe.widen
344+
var unapp = extractor.appliedTo(ref(scrutinee).ensureConforms(mt.paramInfos.head))
345+
if (implicits.nonEmpty) unapp = unapp.appliedToArgs(implicits)
346+
unapplyPlan(unapp, args)
347+
}
342348
if (scrutinee.info.isNotNull || nonNull(scrutinee)) unappPlan
343349
else TestPlan(NonNullTest, scrutinee, tree.span, unappPlan)
344350
case Bind(name, body) =>

tests/run/i5067.check

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
matches null literal
2+
matches null literal
3+
not implemented
4+
match error
5+
not implemented
6+
match error

tests/run/i5067.scala

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Test that we correctly handle scrutinees with type `Null` or `Nothing`.
2+
object Test {
3+
def main(args: Array[String]): Unit = {
4+
null match {
5+
case Some(_) => println("matches Some")
6+
case (_, _) => println("matches Pair")
7+
case null => println("matches null literal")
8+
}
9+
10+
type X = Null
11+
(null: X) match {
12+
case Some(_) => println("matches Some")
13+
case (_, _) => println("matches Pair")
14+
case null => println("matches null literal")
15+
}
16+
17+
type Y = Nothing
18+
try {
19+
(??? : Y) match {
20+
case Some(_) => println("matches Some")
21+
case _ => println("matches anything")
22+
}
23+
} catch {
24+
case e: NotImplementedError => println("not implemented")
25+
}
26+
27+
28+
try {
29+
val Some(_) = null
30+
} catch {
31+
case e: MatchError => println("match error")
32+
}
33+
34+
try {
35+
val Some(_) = ???
36+
} catch {
37+
case e: NotImplementedError => println("not implemented")
38+
}
39+
40+
val x: X = null
41+
try {
42+
val Some(_) = x
43+
} catch {
44+
case e: MatchError => println("match error")
45+
}
46+
}
47+
}

tests/run/i5067b.check

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
match error
2+
match error nested
3+
not implemented error

tests/run/i5067b.scala

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Test that we correctly handle scrutinees with type `Null` or `Nothing`.
2+
object Test {
3+
def main(args: Array[String]): Unit = {
4+
class B[T] {}
5+
object B {
6+
def unapply[T](x: Any): Option[B[T]] = None
7+
}
8+
try {
9+
val B(_) = null
10+
} catch {
11+
case e: MatchError => println("match error")
12+
}
13+
14+
null match {
15+
case null =>
16+
try {
17+
null match {
18+
case Some(_) => ()
19+
}
20+
} catch {
21+
case e: MatchError => println("match error nested")
22+
}
23+
}
24+
25+
try {
26+
??? match {
27+
case (_, _) => ()
28+
case _ => ()
29+
}
30+
} catch {
31+
case e: NotImplementedError => println("not implemented error")
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)