Skip to content

Commit

Permalink
Handle sealed prefixes in exh checking
Browse files Browse the repository at this point in the history
  • Loading branch information
dwijnand committed Jan 19, 2023
1 parent 805dda8 commit 0dae27d
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 5 deletions.
9 changes: 6 additions & 3 deletions compiler/src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import ast.tpd._
import reporting.trace
import config.Printers.typr
import config.Feature
import transform.SymUtils.*
import typer.ProtoTypes._
import typer.ForceDegree
import typer.Inferencing._
Expand Down Expand Up @@ -846,13 +847,15 @@ object TypeOps:
var prefixTVar: Type | Null = null
def apply(tp: Type): Type = tp match {
case ThisType(tref: TypeRef) if !tref.symbol.isStaticOwner =>
if (tref.symbol.is(Module))
TermRef(this(tref.prefix), tref.symbol.sourceModule)
val symbol = tref.symbol
if (symbol.is(Module))
TermRef(this(tref.prefix), symbol.sourceModule)
else if (prefixTVar != null)
this(tref)
else {
prefixTVar = WildcardType // prevent recursive call from assigning it
val tref2 = this(tref.applyIfParameterized(tref.typeParams.map(_ => TypeBounds.empty)))
val tvars = tref.typeParams.map { tparam => newTypeVar(tparam.paramInfo.bounds) }
val tref2 = this(tref.applyIfParameterized(tvars))
prefixTVar = newTypeVar(TypeBounds.upper(tref2))
prefixTVar.uncheckedNN
}
Expand Down
15 changes: 14 additions & 1 deletion compiler/src/dotty/tools/dotc/transform/patmat/Space.scala
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,17 @@ class SpaceEngine(using Context) extends SpaceLogic {
Typ(ConstantType(Constant(())), true) :: Nil
case tp if tp.classSymbol.isAllOf(JavaEnumTrait) =>
tp.classSymbol.children.map(sym => Typ(sym.termRef, true))

case tp @ AppliedType(tycon, targs) if tp.classSymbol.children.isEmpty && canDecompose(tycon) =>
// It might not obvious that it's OK to apply the type arguments of a parent type to child types.
// But this is guarded by `tp.classSymbol.children.isEmpty`,
// meaning we'll decompose to the same class, just not the same type.
// For instance, from i15029, `decompose((X | Y).Field[T]) = [X.Field[T], Y.Field[T]]`.
rec(tycon, Nil).map(typ => Typ(tp.derivedAppliedType(typ.tp, targs)))

case tp: NamedType if canDecompose(tp.prefix) =>
rec(tp.prefix, Nil).map(typ => Typ(tp.derivedSelect(typ.tp)))

case tp =>
def getChildren(sym: Symbol): List[Symbol] =
sym.children.flatMap { child =>
Expand Down Expand Up @@ -669,9 +680,11 @@ class SpaceEngine(using Context) extends SpaceLogic {
/** Abstract sealed types, or-types, Boolean and Java enums can be decomposed */
def canDecompose(tp: Type): Boolean =
val res = tp.dealias match
case AppliedType(tycon, _) if canDecompose(tycon) => true
case tp: NamedType if canDecompose(tp.prefix) => true
case _: SingletonType => false
case _: OrType => true
case and: AndType => canDecompose(and.tp1) || canDecompose(and.tp2)
case AndType(tp1, tp2) => canDecompose(tp1) || canDecompose(tp2)
case _ =>
val cls = tp.classSymbol
cls.is(Sealed)
Expand Down
2 changes: 1 addition & 1 deletion tests/patmat/i12408.check
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
13: Pattern Match Exhaustivity: A(_), C(_)
13: Pattern Match Exhaustivity: X[<?>] & (X.this : X[T]).A(_), X[<?>] & (X.this : X[T]).C(_)
21: Pattern Match
8 changes: 8 additions & 0 deletions tests/pos/i15029.bootstrap-reg.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// scalac: -Werror
// minimisation of a regression that occurred in bootstrapping
class Test:
def t(a: Boolean, b: Boolean) = (a, b) match
case (false, false) => 1
case (false, true ) => 2
case (true, false) => 3
case (true, true ) => 4
25 changes: 25 additions & 0 deletions tests/pos/i15029.more.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// scalac: -Werror

// Like tests/pos/i15029.scala,
// but with a more complicated prefix
// and Schema[String]

sealed trait Schema[A]

sealed class Universe:
sealed trait Instances[B]:
case class Field() extends Schema[B]
case object Thing extends Schema[B]

object Universe1 extends Universe
object Universe2 extends Universe

object Ints extends Universe1.Instances[Int]
object Strs extends Universe2.Instances[String]

// Match not exhaustive error! (with fatal warnings :P)
class Test:
def handle(schema: Schema[String]) =
schema match // was: match may not be exhaustive
case Strs.Field() =>
case Strs.Thing =>
16 changes: 16 additions & 0 deletions tests/pos/i15029.orig.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// scalac: -Werror
sealed trait Schema[A]

object Schema extends RecordInstances

sealed trait RecordInstances:
case class Field[A]() extends Schema[A]
case object Thing extends Schema[Int]

import Schema._

// Match not exhaustive error! (with fatal warnings :P)
def handle[A](schema: Schema[A]) =
schema match
case Field() => println("field")
case Thing => println("thing")
18 changes: 18 additions & 0 deletions tests/pos/i15029.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// scalac: -Werror
sealed trait Schema[A]

sealed trait RecordInstances:
case class Field[B]() extends Schema[B]
case object Thing extends Schema[Int]

object X extends RecordInstances
object Y extends RecordInstances

// Match not exhaustive error! (with fatal warnings :P)
class Test:
def handle[T](schema: Schema[T]) =
schema match // was: match may not be exhaustive
case X.Field() =>
case X.Thing =>
case Y.Field() =>
case Y.Thing =>

0 comments on commit 0dae27d

Please sign in to comment.