Skip to content

Commit 6e32627

Browse files
authored
Do not consider uninhabited constructors when performing exhaustive match checking (scala#21750)
2 parents ad09ab8 + d459140 commit 6e32627

File tree

4 files changed

+38
-11
lines changed

4 files changed

+38
-11
lines changed

Diff for: compiler/src/dotty/tools/dotc/transform/patmat/Space.scala

+26-9
Original file line numberDiff line numberDiff line change
@@ -648,21 +648,38 @@ object SpaceEngine {
648648
// we get
649649
// <== refineUsingParent(NatT, class Succ, []) = Succ[NatT]
650650
// <== isSub(Succ[NatT] <:< Succ[Succ[<?>]]) = false
651-
def getAppliedClass(tp: Type): Type = tp match
652-
case tp @ AppliedType(_: HKTypeLambda, _) => tp
653-
case tp @ AppliedType(tycon: TypeRef, _) if tycon.symbol.isClass => tp
651+
def getAppliedClass(tp: Type): (Type, List[Type]) = tp match
652+
case tp @ AppliedType(_: HKTypeLambda, _) => (tp, Nil)
653+
case tp @ AppliedType(tycon: TypeRef, _) if tycon.symbol.isClass => (tp, tp.args)
654654
case tp @ AppliedType(tycon: TypeProxy, _) => getAppliedClass(tycon.superType.applyIfParameterized(tp.args))
655-
case tp => tp
656-
val tp = getAppliedClass(tpOriginal)
657-
def getChildren(sym: Symbol): List[Symbol] =
655+
case tp => (tp, Nil)
656+
val (tp, typeArgs) = getAppliedClass(tpOriginal)
657+
// This function is needed to get the arguments of the types that will be applied to the class.
658+
// This is necessary because if the arguments of the types contain Nothing,
659+
// then this can affect whether the class will be taken into account during the exhaustiveness check
660+
def getTypeArgs(parent: Symbol, child: Symbol, typeArgs: List[Type]): List[Type] =
661+
val superType = child.typeRef.superType
662+
if typeArgs.exists(_.isBottomType) && superType.isInstanceOf[ClassInfo] then
663+
val parentClass = superType.asInstanceOf[ClassInfo].declaredParents.find(_.classSymbol == parent).get
664+
val paramTypeMap = Map.from(parentClass.argTypes.map(_.typeSymbol).zip(typeArgs))
665+
val substArgs = child.typeRef.typeParamSymbols.map(param => paramTypeMap.getOrElse(param, WildcardType))
666+
substArgs
667+
else Nil
668+
def getChildren(sym: Symbol, typeArgs: List[Type]): List[Symbol] =
658669
sym.children.flatMap { child =>
659670
if child eq sym then List(sym) // i3145: sealed trait Baz, val x = new Baz {}, Baz.children returns Baz...
660671
else if tp.classSymbol == defn.TupleClass || tp.classSymbol == defn.NonEmptyTupleClass then
661672
List(child) // TupleN and TupleXXL classes are used for Tuple, but they aren't Tuple's children
662-
else if (child.is(Private) || child.is(Sealed)) && child.isOneOf(AbstractOrTrait) then getChildren(child)
663-
else List(child)
673+
else if (child.is(Private) || child.is(Sealed)) && child.isOneOf(AbstractOrTrait) then
674+
getChildren(child, getTypeArgs(sym, child, typeArgs))
675+
else
676+
val childSubstTypes = child.typeRef.applyIfParameterized(getTypeArgs(sym, child, typeArgs))
677+
// if a class contains a field of type Nothing,
678+
// then it can be ignored in pattern matching, because it is impossible to obtain an instance of it
679+
val existFieldWithBottomType = childSubstTypes.fields.exists(_.info.isBottomType)
680+
if existFieldWithBottomType then Nil else List(child)
664681
}
665-
val children = trace(i"getChildren($tp)")(getChildren(tp.classSymbol))
682+
val children = trace(i"getChildren($tp)")(getChildren(tp.classSymbol, typeArgs))
666683

667684
val parts = children.map { sym =>
668685
val sym1 = if (sym.is(ModuleClass)) sym.sourceModule else sym

Diff for: tests/init-global/pos/i18629.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
object Foo {
22
val bar = List() match {
33
case List() => ???
4-
case _ => ???
4+
case null => ???
55
}
66
}

Diff for: tests/patmat/i13931.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ class Test:
33
case Seq() => println("empty")
44
case _ => println("non-empty")
55

6-
def test2 = IndexedSeq() match { case IndexedSeq() => case _ => }
6+
def test2 = IndexedSeq() match { case IndexedSeq() => case null => }
77
def test3 = IndexedSeq() match { case IndexedSeq(1) => case _ => }

Diff for: tests/warn/patmat-nothing-exhaustive.scala

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
enum TestAdt:
2+
case Inhabited
3+
case Uninhabited(no: Nothing)
4+
5+
def test1(t: TestAdt): Int = t match
6+
case TestAdt.Inhabited => 1
7+
8+
def test2(o: Option[Option[Nothing]]): Int = o match
9+
case Some(None) => 1
10+
case None => 2

0 commit comments

Comments
 (0)