diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 47f36255de81..c00afa3f06d2 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2586,6 +2586,11 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling provablyDisjoint(tp1, gadtBounds(tp2.symbol).hi) || provablyDisjoint(tp1, tp2.superType) case (tp1: TermRef, tp2: TermRef) if isEnumValueOrModule(tp1) && isEnumValueOrModule(tp2) => tp1.termSymbol != tp2.termSymbol + case (tp1: TermRef, tp2: TypeRef) if isEnumValueOrModule(tp1) && !tp1.classSymbols.exists(_.derivesFrom(tp2.classSymbol)) => + // Note: enum values may have multiple parents + true + case (tp1: TypeRef, tp2: TermRef) if isEnumValueOrModule(tp2) && !tp2.classSymbols.exists(_.derivesFrom(tp1.classSymbol)) => + true case (tp1: Type, tp2: Type) if defn.isTupleType(tp1) => provablyDisjoint(tp1.toNestedPairs, tp2) case (tp1: Type, tp2: Type) if defn.isTupleType(tp2) => diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index a842c5e58261..86c8446750c0 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -692,15 +692,24 @@ object TypeOps: */ private def instantiateToSubType(tp1: NamedType, tp2: Type)(using Context): Type = { // In order for a child type S to qualify as a valid subtype of the parent - // T, we need to test whether it is possible S <: T. Therefore, we replace - // type parameters in T with tvars, and see if the subtyping is true. - val approximateTypeParams = new TypeMap { + // T, we need to test whether it is possible S <: T. + // + // The check is different from subtype checking due to type parameters and + // `this`. We perform the following operations to approximate the parameters: + // + // 1. Replace type parameters in T with tvars + // 2. Replace `A.this.C` with `A#C` (see tests/patmat/i12681.scala) + // + val approximateParent = new TypeMap { val boundTypeParams = util.HashMap[TypeRef, TypeVar]() def apply(tp: Type): Type = tp.dealias match { case _: MatchType => tp // break cycles + case ThisType(tref: TypeRef) if !tref.symbol.isStaticOwner => + tref + case tp: TypeRef if !tp.symbol.isClass => def lo = LazyRef.of(apply(tp.underlying.loBound)) def hi = LazyRef.of(apply(tp.underlying.hiBound)) @@ -787,7 +796,7 @@ object TypeOps: // we manually patch subtyping check instead of changing TypeComparer. // See tests/patmat/i3645b.scala def parentQualify(tp1: Type, tp2: Type) = tp1.classSymbol.info.parents.exists { parent => - parent.argInfos.nonEmpty && approximateTypeParams(parent) <:< tp2 + parent.argInfos.nonEmpty && approximateParent(parent) <:< tp2 } def instantiate(): Type = { @@ -797,8 +806,8 @@ object TypeOps: if (protoTp1 <:< tp2) instantiate() else { - val protoTp2 = approximateTypeParams(tp2) - if (protoTp1 <:< protoTp2 || parentQualify(protoTp1, protoTp2)) instantiate() + val approxTp2 = approximateParent(tp2) + if (protoTp1 <:< approxTp2 || parentQualify(protoTp1, approxTp2)) instantiate() else NoType } } diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index eae6241ed6e8..f8b276d52088 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -100,6 +100,8 @@ object SymUtils: def whyNotGenericSum(declScope: Symbol)(using Context): String = if (!self.is(Sealed)) s"it is not a sealed ${self.kindString}" + else if (!self.isOneOf(AbstractOrTrait)) + s"it is not an abstract class" else { val children = self.children val companionMirror = self.useCompanionAsMirror diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 7a0ef358fc40..569497b3d50d 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -105,7 +105,7 @@ trait SpaceLogic { def signature(unapp: TermRef, scrutineeTp: Type, argLen: Int): List[Type] /** Get components of decomposable types */ - def decompose(tp: Type): List[Space] + def decompose(tp: Type): List[Typ] /** Whether the extractor covers the given type */ def covers(unapp: TermRef, scrutineeTp: Type): Boolean @@ -176,6 +176,8 @@ trait SpaceLogic { ss.forall(isSubspace(_, b)) case (Typ(tp1, _), Typ(tp2, _)) => isSubType(tp1, tp2) + || canDecompose(tp1) && tryDecompose1(tp1) + || canDecompose(tp2) && tryDecompose2(tp2) case (Typ(tp1, _), Or(ss)) => // optimization: don't go to subtraction too early ss.exists(isSubspace(a, _)) || tryDecompose1(tp1) case (_, Or(_)) => @@ -337,9 +339,7 @@ class SpaceEngine(using Context) extends SpaceLogic { val res = TypeComparer.provablyDisjoint(tp1, tp2) if (res) Empty - else if (tp1.isSingleton) Typ(tp1, true) - else if (tp2.isSingleton) Typ(tp2, true) - else Typ(AndType(tp1, tp2), true) + else Typ(AndType(tp1, tp2), decomposed = true) } } @@ -591,14 +591,27 @@ class SpaceEngine(using Context) extends SpaceLogic { } /** Decompose a type into subspaces -- assume the type can be decomposed */ - def decompose(tp: Type): List[Space] = + def decompose(tp: Type): List[Typ] = tp.dealias match { case AndType(tp1, tp2) => - intersect(Typ(tp1, false), Typ(tp2, false)) match { - case Or(spaces) => spaces.toList - case Empty => Nil - case space => List(space) - } + def decomposeComponent(tpA: Type, tpB: Type): List[Typ] = + decompose(tpA).flatMap { + case Typ(tp, _) => + if tp <:< tpB then + Typ(tp, decomposed = true) :: Nil + else if tpB <:< tp then + Typ(tpB, decomposed = true) :: Nil + else if TypeComparer.provablyDisjoint(tp, tpB) then + Nil + else + Typ(AndType(tp, tpB), decomposed = true) :: Nil + } + + if canDecompose(tp1) then + decomposeComponent(tp1, tp2) + else + decomposeComponent(tp2, tp1) + case OrType(tp1, tp2) => List(Typ(tp1, true), Typ(tp2, true)) case tp if tp.isRef(defn.BooleanClass) => List( @@ -833,6 +846,9 @@ class SpaceEngine(using Context) extends SpaceLogic { if (!exhaustivityCheckable(sel)) return + debug.println("checking " + _match.show) + debug.println("selTyp = " + selTyp.show) + val patternSpace = Or(cases.foldLeft(List.empty[Space]) { (acc, x) => val space = if (x.guard.isEmpty) project(x.pat) else Empty debug.println(s"${x.pat.show} ====> ${show(space)}") diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index ca61a47ef5b7..5eda3ad7f5bc 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -27,14 +27,8 @@ trait TypesSupport: case ThisType(tpe) => inner(tpe) case AnnotatedType(tpe, _) => inner(tpe) case AppliedType(tpe, _) => inner(tpe) - case tp @ TermRef(qual, typeName) => - qual match - case _: TypeRepr | _: NoPrefix => Some(tp.termSymbol) - case other => None - case tp @ TypeRef(qual, typeName) => - qual match - case _: TypeRepr | _: NoPrefix => Some(tp.typeSymbol) - case other => None + case tp @ TermRef(qual, typeName) => Some(tp.termSymbol) + case tp @ TypeRef(qual, typeName) => Some(tp.typeSymbol) val typeSymbol = extractTypeSymbol(method.returnTpt) @@ -204,8 +198,7 @@ trait TypesSupport: case tp @ TypeRef(qual, typeName) => qual match { case r: RecursiveThis => texts(s"this.$typeName") - case _: TypeRepr | _: NoPrefix => link(tp.typeSymbol) - case other => noSupported(s"TypeRepr: $tp") + case _: TypeRepr => link(tp.typeSymbol) } // convertTypeOrBoundsToReference(reflect)(qual) match { // case TypeReference(label, link, xs, _) => TypeReference(typeName, link + "/" + label, xs, true) diff --git a/tests/patmat/i10667.scala b/tests/patmat/i10667.scala new file mode 100644 index 000000000000..fde5021929fc --- /dev/null +++ b/tests/patmat/i10667.scala @@ -0,0 +1,15 @@ +sealed trait A + +enum Nums { + case One + case Two extends Nums with A + case Three +} + +object Test { + val list = List[Nums & A](Nums.Two) + + list.map { + case Nums.Two => () + } +} diff --git a/tests/patmat/i12475.scala b/tests/patmat/i12475.scala new file mode 100644 index 000000000000..11ff389df318 --- /dev/null +++ b/tests/patmat/i12475.scala @@ -0,0 +1,20 @@ +sealed trait Ty { + type T +} + +class TUnit() extends Ty { + type T = Unit +} + +case object TUnit extends TUnit() + +final case class TFun(dom: Ty, cod: Ty) extends Ty { + type T = dom.T => cod.T +} + +def default(ty: Ty): ty.T = (ty: ty.type & Ty) match { + case a: (ty.type & TUnit) => (): a.T + case a: (ty.type & TFun) => + val f = { (x: a.dom.T) => default(a.cod) } + f: a.T +} diff --git a/tests/patmat/i12475b.scala b/tests/patmat/i12475b.scala new file mode 100644 index 000000000000..799d95c0477b --- /dev/null +++ b/tests/patmat/i12475b.scala @@ -0,0 +1,14 @@ +trait SomeRestriction + +enum ADT { + case A + case B extends ADT with SomeRestriction +} + +object MinimalExample { + val b: ADT & SomeRestriction = ADT.B + + b match { + case ADT.B => ??? + } +} diff --git a/tests/patmat/i12546.scala b/tests/patmat/i12546.scala new file mode 100644 index 000000000000..23cbfaa77c06 --- /dev/null +++ b/tests/patmat/i12546.scala @@ -0,0 +1,14 @@ +trait SomeRestriction + +enum ADT { + case A extends ADT + case B extends ADT with SomeRestriction +} + +object MinimalExample { + val b: ADT & SomeRestriction = ADT.B + + b match { + case ADT.B => ??? + } +} diff --git a/tests/patmat/i12559.check b/tests/patmat/i12559.check new file mode 100644 index 000000000000..34be8d4104f9 --- /dev/null +++ b/tests/patmat/i12559.check @@ -0,0 +1,2 @@ +10: Match case Unreachable +27: Match case Unreachable diff --git a/tests/patmat/i12559.scala b/tests/patmat/i12559.scala new file mode 100644 index 000000000000..1f043d21e5bd --- /dev/null +++ b/tests/patmat/i12559.scala @@ -0,0 +1,35 @@ +package akka.event + +object TestA: + sealed trait LogEvent + + object LogEvent: + def myOrdinal(e: LogEvent): Int = e match + case e: Error => 0 + // case e: Warning => 1 + case e: LogEventWithMarker => 2 + + + class Error() extends LogEvent + class Error2() extends Error() with LogEventWithMarker + + // case class Warning() extends LogEvent + + sealed trait LogEventWithMarker extends LogEvent + +object TestB: + sealed trait LogEvent + + object LogEvent: + def myOrdinal(e: LogEvent): Int = e match + case e: Error => 0 + case e: Warning => 1 + case e: LogEventWithMarker => 2 + + + case class Error() extends LogEvent + class Error2() extends Error() with LogEventWithMarker + + case class Warning() extends LogEvent + + sealed trait LogEventWithMarker extends LogEvent \ No newline at end of file diff --git a/tests/patmat/i12602.scala b/tests/patmat/i12602.scala new file mode 100644 index 000000000000..3dac513a930c --- /dev/null +++ b/tests/patmat/i12602.scala @@ -0,0 +1,2 @@ +sealed class Foo[T] +object Foo extends Foo[Nothing] diff --git a/tests/patmat/i12681.scala b/tests/patmat/i12681.scala new file mode 100644 index 000000000000..b367e1563611 --- /dev/null +++ b/tests/patmat/i12681.scala @@ -0,0 +1,20 @@ +object Examples { + + case class Leaf1() extends i.Root + case class Leaf2() extends i.Branch + + val i = new Inner() + + class Inner { + + sealed trait Root + sealed trait Branch extends Root + + // simulate ordinal method of a Mirror.SumOf generated at this call site + def myOrdinal(r: Root): Int = r match { + case _: Examples.Leaf1 => 0 + case _: Inner.this.Branch => 1 + } + } + +} \ No newline at end of file