Skip to content

Commit 6b9084d

Browse files
authored
Merge pull request #45 from scala/backport-lts-3.3-21750
Backport "Do not consider uninhabited constructors when performing exhaustive match checking" to 3.3 LTS
2 parents b7c454b + 6fc147e commit 6b9084d

File tree

5 files changed

+63
-8
lines changed

5 files changed

+63
-8
lines changed

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

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -625,16 +625,55 @@ object SpaceEngine {
625625
// For instance, from i15029, `decompose((X | Y).Field[T]) = [X.Field[T], Y.Field[T]]`.
626626
parts.map(tp.derivedAppliedType(_, targs))
627627

628-
case tp if tp.isDecomposableToChildren =>
629-
def getChildren(sym: Symbol): List[Symbol] =
628+
case tpOriginal if tpOriginal.isDecomposableToChildren =>
629+
// isDecomposableToChildren uses .classSymbol.is(Sealed)
630+
// But that classSymbol could be from an AppliedType
631+
// where the type constructor is a non-class type
632+
// E.g. t11620 where `?1.AA[X]` returns as "sealed"
633+
// but using that we're not going to infer A1[X] and A2[X]
634+
// but end up with A1[<?>] and A2[<?>].
635+
// So we widen (like AppliedType superType does) away
636+
// non-class type constructors.
637+
//
638+
// Can't use `tpOriginal.baseType(cls)` because it causes
639+
// i15893 to return exhaustivity warnings, because instead of:
640+
// <== refineUsingParent(N, class Succ, []) = Succ[<? <: NatT>]
641+
// <== isSub(Succ[<? <: NatT>] <:< Succ[Succ[<?>]]) = true
642+
// we get
643+
// <== refineUsingParent(NatT, class Succ, []) = Succ[NatT]
644+
// <== 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)
648+
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.argTypes.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] =
630663
sym.children.flatMap { child =>
631664
if child eq sym then List(sym) // i3145: sealed trait Baz, val x = new Baz {}, Baz.children returns Baz...
632665
else if tp.classSymbol == defn.TupleClass || tp.classSymbol == defn.NonEmptyTupleClass then
633666
List(child) // TupleN and TupleXXL classes are used for Tuple, but they aren't Tuple's children
634-
else if (child.is(Private) || child.is(Sealed)) && child.isOneOf(AbstractOrTrait) then getChildren(child)
635-
else List(child)
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)
636675
}
637-
val children = trace(i"getChildren($tp)")(getChildren(tp.classSymbol))
676+
val children = trace(i"getChildren($tp)")(getChildren(tp.classSymbol, typeArgs))
638677

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

tests/init-global/pos/i18629.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
object Foo {
2+
val bar = List() match {
3+
case List() => ???
4+
case null => ???
5+
}
6+
}

tests/patmat/i13931.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
class Test:
22
def test = Vector() match
33
case Seq() => println("empty")
4-
case _ => println("non-empty")
4+
case null => 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 _ => }

tests/plugins/run/scriptWrapper/LineNumberPlugin_1.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class LineNumberPlugin extends StandardPlugin {
1212
val name: String = "linenumbers"
1313
val description: String = "adjusts line numbers of script files"
1414

15-
override def initialize(options: List[String])(using Context): List[PluginPhase] = FixLineNumbers() :: Nil
15+
override def init(options: List[String]): List[PluginPhase] = FixLineNumbers() :: Nil
1616
}
1717

1818
// Loosely follows Mill linenumbers plugin (scan for marker with "original" source, adjust line numbers to match)
Lines changed: 10 additions & 0 deletions
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)