diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index c42c09a875b0..dab46cacce27 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,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 } 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/patmat/i12408.check b/tests/patmat/i12408.check index 60acc2cba84e..ada7b8c21fa8 100644 --- a/tests/patmat/i12408.check +++ b/tests/patmat/i12408.check @@ -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 diff --git a/tests/pos/i15029.bootstrap-reg.scala b/tests/pos/i15029.bootstrap-reg.scala new file mode 100644 index 000000000000..980781aada07 --- /dev/null +++ b/tests/pos/i15029.bootstrap-reg.scala @@ -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 diff --git a/tests/pos/i15029.more.scala b/tests/pos/i15029.more.scala new file mode 100644 index 000000000000..71b80211b717 --- /dev/null +++ b/tests/pos/i15029.more.scala @@ -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 => diff --git a/tests/pos/i15029.orig.scala b/tests/pos/i15029.orig.scala new file mode 100644 index 000000000000..f671f4fa9184 --- /dev/null +++ b/tests/pos/i15029.orig.scala @@ -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") diff --git a/tests/pos/i15029.scala b/tests/pos/i15029.scala new file mode 100644 index 000000000000..eeaa1613ad9b --- /dev/null +++ b/tests/pos/i15029.scala @@ -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 =>