From e8e52109f8982da1f42b70f28c116bcb85998176 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 22 Jun 2025 16:52:13 +0300 Subject: [PATCH 1/2] constructor not uninhabited, if field with bottom type is lazy --- .../tools/dotc/transform/patmat/Space.scala | 44 +++++++------------ .../patmat-lazy-nothing-not-exhaustive.check | 1 + .../patmat-lazy-nothing-not-exhaustive.scala | 12 +++++ ...atmat-type-member-nothing-exhaustive.scala | 18 ++++++++ 4 files changed, 47 insertions(+), 28 deletions(-) create mode 100644 tests/warn/patmat-lazy-nothing-not-exhaustive.check create mode 100644 tests/warn/patmat-lazy-nothing-not-exhaustive.scala create mode 100644 tests/warn/patmat-type-member-nothing-exhaustive.scala diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index ab5885e6278c..6f3429ffbe45 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -661,49 +661,37 @@ object SpaceEngine { // we get // <== refineUsingParent(NatT, class Succ, []) = Succ[NatT] // <== isSub(Succ[NatT] <:< Succ[Succ[]]) = false - 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) + def getAppliedClass(tp: Type): Type = tp match + case tp @ AppliedType(_: HKTypeLambda, _) => tp + case tp @ AppliedType(tycon: TypeRef, _) if tycon.symbol.isClass => tp case tp @ AppliedType(tycon: TypeProxy, _) => getAppliedClass(tycon.superType.applyIfParameterized(tp.args)) - 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.argInfos.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] = + case tp => tp + val tp = getAppliedClass(tpOriginal) + def getChildren(sym: Symbol): 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, 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) + else if (child.is(Private) || child.is(Sealed)) && child.isOneOf(AbstractOrTrait) then getChildren(child) + else List(child) } - val children = trace(i"getChildren($tp)")(getChildren(tp.classSymbol, typeArgs)) + val children = trace(i"getChildren($tp)")(getChildren(tp.classSymbol)) val parts = children.map { sym => val sym1 = if (sym.is(ModuleClass)) sym.sourceModule else sym val refined = trace(i"refineUsingParent($tp, $sym1, $mixins)")(TypeOps.refineUsingParent(tp, sym1, mixins)) + def containsUninhabitedField(tp: Type): Boolean = + tp.fields.exists { field => + !field.symbol.flags.is(Lazy) && field.info.dealias.isBottomType + } + def inhabited(tp: Type): Boolean = tp.dealias match case AndType(tp1, tp2) => !TypeComparer.provablyDisjoint(tp1, tp2) case OrType(tp1, tp2) => inhabited(tp1) || inhabited(tp2) case tp: RefinedType => inhabited(tp.parent) - case tp: TypeRef => inhabited(tp.prefix) - case _ => true + case tp: TypeRef => !containsUninhabitedField(tp) && inhabited(tp.prefix) + case _ => !containsUninhabitedField(tp) if inhabited(refined) then refined else NoType diff --git a/tests/warn/patmat-lazy-nothing-not-exhaustive.check b/tests/warn/patmat-lazy-nothing-not-exhaustive.check new file mode 100644 index 000000000000..a0a2400cf5ad --- /dev/null +++ b/tests/warn/patmat-lazy-nothing-not-exhaustive.check @@ -0,0 +1 @@ +-- [E029] Pattern Match Exhaustivity Warning: tests\warn\patmat-lazy-nothing-not-exhaustive.scala:10:5 ----------------- 10 | id(x match { // warn | ^ | match may not be exhaustive. | | It would fail on pattern case: Bar() | | longer explanation available when compiling with `-explain` \ No newline at end of file diff --git a/tests/warn/patmat-lazy-nothing-not-exhaustive.scala b/tests/warn/patmat-lazy-nothing-not-exhaustive.scala new file mode 100644 index 000000000000..79eef226ad9f --- /dev/null +++ b/tests/warn/patmat-lazy-nothing-not-exhaustive.scala @@ -0,0 +1,12 @@ +sealed trait Adt +case class Foo() extends Adt +case class Bar() extends Adt { + lazy val x: Nothing = throw new Exception() +} + +inline def id[A](a: A): A = a + +def shouldThrowAWarning(x: Adt) = + id(x match { // warn + case Foo() => "Bar" + }) diff --git a/tests/warn/patmat-type-member-nothing-exhaustive.scala b/tests/warn/patmat-type-member-nothing-exhaustive.scala new file mode 100644 index 000000000000..c04270b78a3a --- /dev/null +++ b/tests/warn/patmat-type-member-nothing-exhaustive.scala @@ -0,0 +1,18 @@ +trait Phase { + type FooTy + type BarTy + sealed trait Adt + case class Foo(x: FooTy) extends Adt + case class Bar(x: BarTy) extends Adt +} + +object Basic extends Phase { + type FooTy = Unit + type BarTy = Nothing +} + + +def test(a: Basic.Adt) = { + a match + case Basic.Foo(x) => +} \ No newline at end of file From 0696aeca8bfeeab62cd85cf99a93e6480b4c82bd Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 22 Jun 2025 19:36:25 +0300 Subject: [PATCH 2/2] move test to patmat --- tests/patmat/patmat-lazy-nothing-not-exhaustive.check | 1 + .../patmat-lazy-nothing-not-exhaustive.scala | 8 +++----- tests/warn/patmat-lazy-nothing-not-exhaustive.check | 1 - 3 files changed, 4 insertions(+), 6 deletions(-) create mode 100644 tests/patmat/patmat-lazy-nothing-not-exhaustive.check rename tests/{warn => patmat}/patmat-lazy-nothing-not-exhaustive.scala (65%) delete mode 100644 tests/warn/patmat-lazy-nothing-not-exhaustive.check diff --git a/tests/patmat/patmat-lazy-nothing-not-exhaustive.check b/tests/patmat/patmat-lazy-nothing-not-exhaustive.check new file mode 100644 index 000000000000..aef9e1aed027 --- /dev/null +++ b/tests/patmat/patmat-lazy-nothing-not-exhaustive.check @@ -0,0 +1 @@ +8: Pattern Match Exhaustivity: Bar() \ No newline at end of file diff --git a/tests/warn/patmat-lazy-nothing-not-exhaustive.scala b/tests/patmat/patmat-lazy-nothing-not-exhaustive.scala similarity index 65% rename from tests/warn/patmat-lazy-nothing-not-exhaustive.scala rename to tests/patmat/patmat-lazy-nothing-not-exhaustive.scala index 79eef226ad9f..b66eb63c110c 100644 --- a/tests/warn/patmat-lazy-nothing-not-exhaustive.scala +++ b/tests/patmat/patmat-lazy-nothing-not-exhaustive.scala @@ -4,9 +4,7 @@ case class Bar() extends Adt { lazy val x: Nothing = throw new Exception() } -inline def id[A](a: A): A = a - def shouldThrowAWarning(x: Adt) = - id(x match { // warn - case Foo() => "Bar" - }) + x match { // warn + case Foo() => "Foo" + } diff --git a/tests/warn/patmat-lazy-nothing-not-exhaustive.check b/tests/warn/patmat-lazy-nothing-not-exhaustive.check deleted file mode 100644 index a0a2400cf5ad..000000000000 --- a/tests/warn/patmat-lazy-nothing-not-exhaustive.check +++ /dev/null @@ -1 +0,0 @@ --- [E029] Pattern Match Exhaustivity Warning: tests\warn\patmat-lazy-nothing-not-exhaustive.scala:10:5 ----------------- 10 | id(x match { // warn | ^ | match may not be exhaustive. | | It would fail on pattern case: Bar() | | longer explanation available when compiling with `-explain` \ No newline at end of file