Skip to content

Handle sealed prefixes in exh checking #16621

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 =>