Skip to content

Commit 1d88777

Browse files
committed
Extend pattern type constraining to closed hierarchies
1 parent dffeda7 commit 1d88777

File tree

5 files changed

+49
-30
lines changed

5 files changed

+49
-30
lines changed

compiler/src/dotty/tools/dotc/core/Flags.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,8 @@ object Flags {
563563
val JavaOrPrivateOrSynthetic: FlagSet = Artifact | JavaDefined | Private | Synthetic
564564
val PrivateOrSynthetic: FlagSet = Artifact | Private | Synthetic
565565
val EnumCase: FlagSet = Case | Enum
566+
val CaseOrFinalOrSealed: FlagSet = Case | Final | Sealed
567+
val CaseOrSealed: FlagSet = Case | Sealed
566568
val CovariantLocal: FlagSet = Covariant | Local // A covariant type parameter
567569
val ContravariantLocal: FlagSet = Contravariant | Local // A contravariant type parameter
568570
val EffectivelyErased = ConstructorProxy | Erased

compiler/src/dotty/tools/dotc/core/PatternTypeConstrainer.scala

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -221,29 +221,19 @@ trait PatternTypeConstrainer { self: TypeComparer =>
221221
*
222222
* It'd be unsound for us to say that `t <: T`, even though that follows from `D[t] <: C[T]`.
223223
* Note, however, that if `D` was a final class, we *could* rely on that relationship.
224-
* To support typical case classes, we also assume that this relationship holds for them and their parent traits.
225-
* This is enforced by checking that classes inheriting from case classes do not extend the parent traits of those
226-
* case classes without also appropriately extending the relevant case class
227-
* (see `RefChecks#checkCaseClassInheritanceInvariant`).
224+
* Case classes and sealed traits (and sealed classes) are supported,
225+
* by assuming that this relationship holds for them and their parent traits.
226+
* This is enforced by checking no subclass of them mixes in any parent trait with a different type argument.
227+
* (see `RefChecks#checkVariantInheritanceProblems`).
228228
*/
229229
def constrainSimplePatternType(patternTp: Type, scrutineeTp: Type, forceInvariantRefinement: Boolean): Boolean = {
230230
def refinementIsInvariant(tp: Type): Boolean = tp match {
231231
case tp: SingletonType => true
232-
case tp: ClassInfo => tp.cls.is(Final) || tp.cls.is(Case)
232+
case tp: ClassInfo => tp.cls.isOneOf(CaseOrFinalOrSealed)
233233
case tp: TypeProxy => refinementIsInvariant(tp.superType)
234234
case _ => false
235235
}
236236

237-
def widenVariantParams(tp: Type) = tp match {
238-
case tp @ AppliedType(tycon, args) =>
239-
val args1 = args.zipWithConserve(tycon.typeParams)((arg, tparam) =>
240-
if (tparam.paramVarianceSign != 0) TypeBounds.empty else arg
241-
)
242-
tp.derivedAppliedType(tycon, args1)
243-
case tp =>
244-
tp
245-
}
246-
247237
val patternCls = patternTp.classSymbol
248238
val scrutineeCls = scrutineeTp.classSymbol
249239

compiler/src/dotty/tools/dotc/typer/RefChecks.scala

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -901,20 +901,26 @@ object RefChecks {
901901
}
902902
}
903903

904-
/** Check that inheriting a case class does not constitute a variant refinement
904+
/** Check that inheriting a case class or a sealed trait (or a sealed class) does not constitute a variant refinement
905905
* of a base type of the case class. It is because of this restriction that we
906-
* can assume invariant refinement for case classes in `constrainPatternType`.
906+
* can assume invariant refinement for these classes in `constrainSimplePatternType`.
907907
*/
908-
def checkCaseClassInheritanceInvariant() =
908+
def checkVariantInheritanceProblems() =
909909
for
910-
caseCls <- clazz.info.baseClasses.tail.find(_.is(Case))
911-
baseCls <- caseCls.info.baseClasses.tail
910+
middle <- clazz.info.baseClasses.tail
911+
if middle.isOneOf(CaseOrSealed)
912+
baseCls <- middle.info.baseClasses.tail
912913
if baseCls.typeParams.exists(_.paramVarianceSign != 0)
913-
problem <- variantInheritanceProblems(baseCls, caseCls, i"base $baseCls", "case ")
914-
withExplain = problem.appendExplanation:
915-
"""Refining a basetype of a case class is not allowed.
916-
|This is a limitation that enables better GADT constraints in case class patterns""".stripMargin
917-
do report.errorOrMigrationWarning(withExplain, clazz.srcPos, MigrationVersion.Scala2to3)
914+
problem <- {
915+
val middleStr = if middle.is(Case) then "case " else if middle.is(Sealed) then "sealed " else ""
916+
variantInheritanceProblems(baseCls, middle, "", middleStr)
917+
}
918+
do
919+
val withExplain = problem.appendExplanation:
920+
"""Refining a basetype of a case class or a sealed trait (or a sealed class) is not allowed.
921+
|This is a limitation that enables better GADT constraints in case class and sealed hierarchy patterns""".stripMargin
922+
report.errorOrMigrationWarning(withExplain, clazz.srcPos, MigrationVersion.Scala2to3)
923+
918924
checkNoAbstractMembers()
919925
if (abstractErrors.isEmpty)
920926
checkNoAbstractDecls(clazz)
@@ -923,7 +929,7 @@ object RefChecks {
923929
report.error(abstractErrorMessage, clazz.srcPos)
924930

925931
checkMemberTypesOK()
926-
checkCaseClassInheritanceInvariant()
932+
checkVariantInheritanceProblems()
927933
}
928934

929935
if (!clazz.is(Trait) && checker.checkInheritedTraitParameters) {
@@ -943,7 +949,7 @@ object RefChecks {
943949
for {
944950
cls <- clazz.info.baseClasses.tail
945951
if cls.paramAccessors.nonEmpty && !mixins.contains(cls)
946-
problem <- variantInheritanceProblems(cls, clazz.asClass.superClass, i"parameterized base $cls", "super")
952+
problem <- variantInheritanceProblems(cls, clazz.asClass.superClass, "parameterized ", "super")
947953
}
948954
report.error(problem, clazz.srcPos)
949955
}
@@ -966,7 +972,7 @@ object RefChecks {
966972
if (combinedBT =:= thisBT) None // ok
967973
else
968974
Some(
969-
em"""illegal inheritance: $clazz inherits conflicting instances of $baseStr.
975+
em"""illegal inheritance: $clazz inherits conflicting instances of ${baseStr}base $baseCls.
970976
|
971977
| Direct basetype: $thisBT
972978
| Basetype via $middleStr$middle: $combinedBT""")

tests/neg/i18552.check

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
|---------------------------------------------------------------------------------------------------------------------
99
| Explanation (enabled by `-explain`)
1010
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
11-
| Refining a basetype of a case class is not allowed.
12-
| This is a limitation that enables better GADT constraints in case class patterns
11+
| Refining a basetype of a case class or a sealed trait (or a sealed class) is not allowed.
12+
| This is a limitation that enables better GADT constraints in case class and sealed hierarchy patterns
1313
---------------------------------------------------------------------------------------------------------------------

tests/pos/i4790.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
class Test:
2+
def foo(as: Seq[Int]) =
3+
val List(_, bs: _*) = as: @unchecked
4+
val cs: Seq[Int] = bs
5+
6+
class Test2:
7+
def foo(as: SSeq[Int]) =
8+
val LList(_, tail) = as: @unchecked
9+
val cs: SSeq[Int] = tail
10+
11+
trait SSeq[+A]
12+
sealed trait LList[+A] extends SSeq[A]
13+
final case class CCons[+A](head: A, tail: LList[A]) extends LList[A]
14+
case object NNil extends LList[Nothing]
15+
object LList:
16+
def unapply[A](xs: LList[A]): Extractor[A] = Extractor[A](xs)
17+
final class Extractor[A](private val xs: LList[A]) extends AnyVal:
18+
def get: this.type = this
19+
def isEmpty: Boolean = xs.isInstanceOf[CCons[?]]
20+
def _1: A = xs.asInstanceOf[CCons[A]].head
21+
def _2: SSeq[A] = xs.asInstanceOf[CCons[A]].tail

0 commit comments

Comments
 (0)