Skip to content

Commit 13b8d7d

Browse files
authored
Handle sealed prefixes in exh checking (#16621)
2 parents ad8d3bb + 0dae27d commit 13b8d7d

File tree

7 files changed

+88
-5
lines changed

7 files changed

+88
-5
lines changed

compiler/src/dotty/tools/dotc/core/TypeOps.scala

+6-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import ast.tpd._
1313
import reporting.trace
1414
import config.Printers.typr
1515
import config.Feature
16+
import transform.SymUtils.*
1617
import typer.ProtoTypes._
1718
import typer.ForceDegree
1819
import typer.Inferencing._
@@ -846,13 +847,15 @@ object TypeOps:
846847
var prefixTVar: Type | Null = null
847848
def apply(tp: Type): Type = tp match {
848849
case ThisType(tref: TypeRef) if !tref.symbol.isStaticOwner =>
849-
if (tref.symbol.is(Module))
850-
TermRef(this(tref.prefix), tref.symbol.sourceModule)
850+
val symbol = tref.symbol
851+
if (symbol.is(Module))
852+
TermRef(this(tref.prefix), symbol.sourceModule)
851853
else if (prefixTVar != null)
852854
this(tref)
853855
else {
854856
prefixTVar = WildcardType // prevent recursive call from assigning it
855-
val tref2 = this(tref.applyIfParameterized(tref.typeParams.map(_ => TypeBounds.empty)))
857+
val tvars = tref.typeParams.map { tparam => newTypeVar(tparam.paramInfo.bounds) }
858+
val tref2 = this(tref.applyIfParameterized(tvars))
856859
prefixTVar = newTypeVar(TypeBounds.upper(tref2))
857860
prefixTVar.uncheckedNN
858861
}

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

+14-1
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,17 @@ class SpaceEngine(using Context) extends SpaceLogic {
633633
Typ(ConstantType(Constant(())), true) :: Nil
634634
case tp if tp.classSymbol.isAllOf(JavaEnumTrait) =>
635635
tp.classSymbol.children.map(sym => Typ(sym.termRef, true))
636+
637+
case tp @ AppliedType(tycon, targs) if tp.classSymbol.children.isEmpty && canDecompose(tycon) =>
638+
// It might not obvious that it's OK to apply the type arguments of a parent type to child types.
639+
// But this is guarded by `tp.classSymbol.children.isEmpty`,
640+
// meaning we'll decompose to the same class, just not the same type.
641+
// For instance, from i15029, `decompose((X | Y).Field[T]) = [X.Field[T], Y.Field[T]]`.
642+
rec(tycon, Nil).map(typ => Typ(tp.derivedAppliedType(typ.tp, targs)))
643+
644+
case tp: NamedType if canDecompose(tp.prefix) =>
645+
rec(tp.prefix, Nil).map(typ => Typ(tp.derivedSelect(typ.tp)))
646+
636647
case tp =>
637648
def getChildren(sym: Symbol): List[Symbol] =
638649
sym.children.flatMap { child =>
@@ -674,9 +685,11 @@ class SpaceEngine(using Context) extends SpaceLogic {
674685
/** Abstract sealed types, or-types, Boolean and Java enums can be decomposed */
675686
def canDecompose(tp: Type): Boolean =
676687
val res = tp.dealias match
688+
case AppliedType(tycon, _) if canDecompose(tycon) => true
689+
case tp: NamedType if canDecompose(tp.prefix) => true
677690
case _: SingletonType => false
678691
case _: OrType => true
679-
case and: AndType => canDecompose(and.tp1) || canDecompose(and.tp2)
692+
case AndType(tp1, tp2) => canDecompose(tp1) || canDecompose(tp2)
680693
case _ =>
681694
val cls = tp.classSymbol
682695
cls.is(Sealed)

tests/patmat/i12408.check

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
13: Pattern Match Exhaustivity: A(_), C(_)
1+
13: Pattern Match Exhaustivity: X[<?>] & (X.this : X[T]).A(_), X[<?>] & (X.this : X[T]).C(_)
22
21: Pattern Match

tests/pos/i15029.bootstrap-reg.scala

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// scalac: -Werror
2+
// minimisation of a regression that occurred in bootstrapping
3+
class Test:
4+
def t(a: Boolean, b: Boolean) = (a, b) match
5+
case (false, false) => 1
6+
case (false, true ) => 2
7+
case (true, false) => 3
8+
case (true, true ) => 4

tests/pos/i15029.more.scala

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// scalac: -Werror
2+
3+
// Like tests/pos/i15029.scala,
4+
// but with a more complicated prefix
5+
// and Schema[String]
6+
7+
sealed trait Schema[A]
8+
9+
sealed class Universe:
10+
sealed trait Instances[B]:
11+
case class Field() extends Schema[B]
12+
case object Thing extends Schema[B]
13+
14+
object Universe1 extends Universe
15+
object Universe2 extends Universe
16+
17+
object Ints extends Universe1.Instances[Int]
18+
object Strs extends Universe2.Instances[String]
19+
20+
// Match not exhaustive error! (with fatal warnings :P)
21+
class Test:
22+
def handle(schema: Schema[String]) =
23+
schema match // was: match may not be exhaustive
24+
case Strs.Field() =>
25+
case Strs.Thing =>

tests/pos/i15029.orig.scala

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// scalac: -Werror
2+
sealed trait Schema[A]
3+
4+
object Schema extends RecordInstances
5+
6+
sealed trait RecordInstances:
7+
case class Field[A]() extends Schema[A]
8+
case object Thing extends Schema[Int]
9+
10+
import Schema._
11+
12+
// Match not exhaustive error! (with fatal warnings :P)
13+
def handle[A](schema: Schema[A]) =
14+
schema match
15+
case Field() => println("field")
16+
case Thing => println("thing")

tests/pos/i15029.scala

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// scalac: -Werror
2+
sealed trait Schema[A]
3+
4+
sealed trait RecordInstances:
5+
case class Field[B]() extends Schema[B]
6+
case object Thing extends Schema[Int]
7+
8+
object X extends RecordInstances
9+
object Y extends RecordInstances
10+
11+
// Match not exhaustive error! (with fatal warnings :P)
12+
class Test:
13+
def handle[T](schema: Schema[T]) =
14+
schema match // was: match may not be exhaustive
15+
case X.Field() =>
16+
case X.Thing =>
17+
case Y.Field() =>
18+
case Y.Thing =>

0 commit comments

Comments
 (0)