Skip to content

Commit e1fc36f

Browse files
authored
Merge pull request #544 from scala/backport-lts-3.3-23403
Backport "Fix problems in checking that a constructor is uninhabited for exhaustive match checking" to 3.3 LTS
2 parents 442141d + 952e977 commit e1fc36f

File tree

4 files changed

+45
-28
lines changed

4 files changed

+45
-28
lines changed

compiler/src/dotty/tools/dotc/transform/patmat/Space.scala

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -642,49 +642,37 @@ object SpaceEngine {
642642
// we get
643643
// <== refineUsingParent(NatT, class Succ, []) = Succ[NatT]
644644
// <== isSub(Succ[NatT] <:< Succ[Succ[<?>]]) = false
645-
def getAppliedClass(tp: Type): (Type, List[Type]) = tp match
646-
case tp @ AppliedType(_: HKTypeLambda, _) => (tp, Nil)
647-
case tp @ AppliedType(tycon: TypeRef, _) if tycon.symbol.isClass => (tp, tp.args)
645+
def getAppliedClass(tp: Type): Type = tp match
646+
case tp @ AppliedType(_: HKTypeLambda, _) => tp
647+
case tp @ AppliedType(tycon: TypeRef, _) if tycon.symbol.isClass => tp
648648
case tp @ AppliedType(tycon: TypeProxy, _) => getAppliedClass(tycon.superType.applyIfParameterized(tp.args))
649-
case tp => (tp, Nil)
650-
val (tp, typeArgs) = getAppliedClass(tpOriginal)
651-
// This function is needed to get the arguments of the types that will be applied to the class.
652-
// This is necessary because if the arguments of the types contain Nothing,
653-
// then this can affect whether the class will be taken into account during the exhaustiveness check
654-
def getTypeArgs(parent: Symbol, child: Symbol, typeArgs: List[Type]): List[Type] =
655-
val superType = child.typeRef.superType
656-
if typeArgs.exists(_.isBottomType) && superType.isInstanceOf[ClassInfo] then
657-
val parentClass = superType.asInstanceOf[ClassInfo].declaredParents.find(_.classSymbol == parent).get
658-
val paramTypeMap = Map.from(parentClass.argInfos.map(_.typeSymbol).zip(typeArgs))
659-
val substArgs = child.typeRef.typeParamSymbols.map(param => paramTypeMap.getOrElse(param, WildcardType))
660-
substArgs
661-
else Nil
662-
def getChildren(sym: Symbol, typeArgs: List[Type]): List[Symbol] =
649+
case tp => tp
650+
val tp = getAppliedClass(tpOriginal)
651+
def getChildren(sym: Symbol): List[Symbol] =
663652
sym.children.flatMap { child =>
664653
if child eq sym then List(sym) // i3145: sealed trait Baz, val x = new Baz {}, Baz.children returns Baz...
665654
else if tp.classSymbol == defn.TupleClass || tp.classSymbol == defn.NonEmptyTupleClass then
666655
List(child) // TupleN and TupleXXL classes are used for Tuple, but they aren't Tuple's children
667-
else if (child.is(Private) || child.is(Sealed)) && child.isOneOf(AbstractOrTrait) then
668-
getChildren(child, getTypeArgs(sym, child, typeArgs))
669-
else
670-
val childSubstTypes = child.typeRef.applyIfParameterized(getTypeArgs(sym, child, typeArgs))
671-
// if a class contains a field of type Nothing,
672-
// then it can be ignored in pattern matching, because it is impossible to obtain an instance of it
673-
val existFieldWithBottomType = childSubstTypes.fields.exists(_.info.isBottomType)
674-
if existFieldWithBottomType then Nil else List(child)
656+
else if (child.is(Private) || child.is(Sealed)) && child.isOneOf(AbstractOrTrait) then getChildren(child)
657+
else List(child)
675658
}
676-
val children = trace(i"getChildren($tp)")(getChildren(tp.classSymbol, typeArgs))
659+
val children = trace(i"getChildren($tp)")(getChildren(tp.classSymbol))
677660

678661
val parts = children.map { sym =>
679662
val sym1 = if (sym.is(ModuleClass)) sym.sourceModule else sym
680663
val refined = trace(i"refineUsingParent($tp, $sym1, $mixins)")(TypeOps.refineUsingParent(tp, sym1, mixins))
681664

665+
def containsUninhabitedField(tp: Type): Boolean =
666+
tp.fields.exists { field =>
667+
!field.symbol.flags.is(Lazy) && field.info.dealias.isBottomType
668+
}
669+
682670
def inhabited(tp: Type): Boolean = tp.dealias match
683671
case AndType(tp1, tp2) => !TypeComparer.provablyDisjoint(tp1, tp2)
684672
case OrType(tp1, tp2) => inhabited(tp1) || inhabited(tp2)
685673
case tp: RefinedType => inhabited(tp.parent)
686-
case tp: TypeRef => inhabited(tp.prefix)
687-
case _ => true
674+
case tp: TypeRef => !containsUninhabitedField(tp) && inhabited(tp.prefix)
675+
case _ => !containsUninhabitedField(tp)
688676

689677
if inhabited(refined) then refined
690678
else NoType
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
8: Pattern Match Exhaustivity: Bar()
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
sealed trait Adt
2+
case class Foo() extends Adt
3+
case class Bar() extends Adt {
4+
lazy val x: Nothing = throw new Exception()
5+
}
6+
7+
def shouldThrowAWarning(x: Adt) =
8+
x match { // warn
9+
case Foo() => "Foo"
10+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
trait Phase {
2+
type FooTy
3+
type BarTy
4+
sealed trait Adt
5+
case class Foo(x: FooTy) extends Adt
6+
case class Bar(x: BarTy) extends Adt
7+
}
8+
9+
object Basic extends Phase {
10+
type FooTy = Unit
11+
type BarTy = Nothing
12+
}
13+
14+
15+
def test(a: Basic.Adt) = {
16+
a match
17+
case Basic.Foo(x) =>
18+
}

0 commit comments

Comments
 (0)