Skip to content

Commit

Permalink
Do not consider uninhabited constructors when performing exhaustive m…
Browse files Browse the repository at this point in the history
…atch checking (scala#21750)
  • Loading branch information
dwijnand authored Oct 20, 2024
2 parents ad09ab8 + d459140 commit 6e32627
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 11 deletions.
35 changes: 26 additions & 9 deletions compiler/src/dotty/tools/dotc/transform/patmat/Space.scala
Original file line number Diff line number Diff line change
Expand Up @@ -648,21 +648,38 @@ object SpaceEngine {
// we get
// <== refineUsingParent(NatT, class Succ, []) = Succ[NatT]
// <== isSub(Succ[NatT] <:< Succ[Succ[<?>]]) = false
def getAppliedClass(tp: Type): Type = tp match
case tp @ AppliedType(_: HKTypeLambda, _) => tp
case tp @ AppliedType(tycon: TypeRef, _) if tycon.symbol.isClass => tp
def getAppliedClass(tp: Type): (Type, List[Type]) = tp match
case tp @ AppliedType(_: HKTypeLambda, _) => (tp, Nil)
case tp @ AppliedType(tycon: TypeRef, _) if tycon.symbol.isClass => (tp, tp.args)
case tp @ AppliedType(tycon: TypeProxy, _) => getAppliedClass(tycon.superType.applyIfParameterized(tp.args))
case tp => tp
val tp = getAppliedClass(tpOriginal)
def getChildren(sym: Symbol): List[Symbol] =
case tp => (tp, Nil)
val (tp, typeArgs) = getAppliedClass(tpOriginal)
// This function is needed to get the arguments of the types that will be applied to the class.
// This is necessary because if the arguments of the types contain Nothing,
// then this can affect whether the class will be taken into account during the exhaustiveness check
def getTypeArgs(parent: Symbol, child: Symbol, typeArgs: List[Type]): List[Type] =
val superType = child.typeRef.superType
if typeArgs.exists(_.isBottomType) && superType.isInstanceOf[ClassInfo] then
val parentClass = superType.asInstanceOf[ClassInfo].declaredParents.find(_.classSymbol == parent).get
val paramTypeMap = Map.from(parentClass.argTypes.map(_.typeSymbol).zip(typeArgs))
val substArgs = child.typeRef.typeParamSymbols.map(param => paramTypeMap.getOrElse(param, WildcardType))
substArgs
else Nil
def getChildren(sym: Symbol, typeArgs: List[Type]): List[Symbol] =
sym.children.flatMap { child =>
if child eq sym then List(sym) // i3145: sealed trait Baz, val x = new Baz {}, Baz.children returns Baz...
else if tp.classSymbol == defn.TupleClass || tp.classSymbol == defn.NonEmptyTupleClass then
List(child) // TupleN and TupleXXL classes are used for Tuple, but they aren't Tuple's children
else if (child.is(Private) || child.is(Sealed)) && child.isOneOf(AbstractOrTrait) then getChildren(child)
else List(child)
else if (child.is(Private) || child.is(Sealed)) && child.isOneOf(AbstractOrTrait) then
getChildren(child, getTypeArgs(sym, child, typeArgs))
else
val childSubstTypes = child.typeRef.applyIfParameterized(getTypeArgs(sym, child, typeArgs))
// if a class contains a field of type Nothing,
// then it can be ignored in pattern matching, because it is impossible to obtain an instance of it
val existFieldWithBottomType = childSubstTypes.fields.exists(_.info.isBottomType)
if existFieldWithBottomType then Nil else List(child)
}
val children = trace(i"getChildren($tp)")(getChildren(tp.classSymbol))
val children = trace(i"getChildren($tp)")(getChildren(tp.classSymbol, typeArgs))

val parts = children.map { sym =>
val sym1 = if (sym.is(ModuleClass)) sym.sourceModule else sym
Expand Down
2 changes: 1 addition & 1 deletion tests/init-global/pos/i18629.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
object Foo {
val bar = List() match {
case List() => ???
case _ => ???
case null => ???
}
}
2 changes: 1 addition & 1 deletion tests/patmat/i13931.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ class Test:
case Seq() => println("empty")
case _ => println("non-empty")

def test2 = IndexedSeq() match { case IndexedSeq() => case _ => }
def test2 = IndexedSeq() match { case IndexedSeq() => case null => }
def test3 = IndexedSeq() match { case IndexedSeq(1) => case _ => }
10 changes: 10 additions & 0 deletions tests/warn/patmat-nothing-exhaustive.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
enum TestAdt:
case Inhabited
case Uninhabited(no: Nothing)

def test1(t: TestAdt): Int = t match
case TestAdt.Inhabited => 1

def test2(o: Option[Option[Nothing]]): Int = o match
case Some(None) => 1
case None => 2

0 comments on commit 6e32627

Please sign in to comment.