From 391b4334d0df733fbcd78a367ed17ae4c1593877 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 5 Jan 2023 14:53:48 +0000 Subject: [PATCH] Handle sealed prefixes in exh checking --- .../src/dotty/tools/dotc/core/TypeOps.scala | 17 ++++++++++--- .../tools/dotc/transform/patmat/Space.scala | 15 ++++++++++- tests/pos/i15029.scala | 25 +++++++++++++++++++ 3 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 tests/pos/i15029.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index c42c09a875b0..0b848209babb 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -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._ @@ -846,14 +847,22 @@ 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))) - prefixTVar = newTypeVar(TypeBounds.upper(tref2)) + prefixTVar = if symbol.is(Sealed) && symbol.isOneOf(AbstractOrTrait) && symbol.children.sizeIs > 0 && !symbol.hasAnonymousChild then + symbol.children.foldLeft(WildcardType: Type) { (acc, childSym) => + val child = childSym.namedType + val tp = this(child.applyIfParameterized(child.typeParams.map(_ => TypeBounds.empty))) + acc | tp + } + else + val tref2 = this(tref.applyIfParameterized(tref.typeParams.map(_ => TypeBounds.empty))) + newTypeVar(TypeBounds.upper(tref2)) prefixTVar.uncheckedNN } case tp => mapOver(tp) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 7bd18b466518..0a292abf7b83 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -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 => @@ -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) diff --git a/tests/pos/i15029.scala b/tests/pos/i15029.scala new file mode 100644 index 000000000000..bdf2204d9ce5 --- /dev/null +++ b/tests/pos/i15029.scala @@ -0,0 +1,25 @@ +// 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 => + + 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