Skip to content
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

Do not consider uninhabited constructors when performing exhaustive match checking #21750

Merged
merged 1 commit into from
Oct 20, 2024
Merged
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
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
Loading