Skip to content

Commit 4feda4e

Browse files
committed
Enforce non-variant refinement for parameterized base classes and traits
1 parent 1082eb5 commit 4feda4e

File tree

4 files changed

+76
-15
lines changed

4 files changed

+76
-15
lines changed

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,8 @@ object Inferencing {
190190
def refinementIsInvariant(tp: Type): Boolean = tp match {
191191
case tp: ClassInfo => tp.cls.is(Final) || tp.cls.is(Case)
192192
case tp: TypeProxy => refinementIsInvariant(tp.underlying)
193-
case tp: AndOrType => refinementIsInvariant(tp.tp1) && refinementIsInvariant(tp.tp2)
193+
case tp: AndType => refinementIsInvariant(tp.tp1) && refinementIsInvariant(tp.tp2)
194+
case tp: OrType => refinementIsInvariant(tp.tp1) && refinementIsInvariant(tp.tp2)
194195
case _ => false
195196
}
196197

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

+49-14
Original file line numberDiff line numberDiff line change
@@ -647,21 +647,11 @@ object RefChecks {
647647
*/
648648
def checkCaseClassInheritanceInvariant() = {
649649
for (caseCls <- clazz.info.baseClasses.tail.find(_.is(Case)))
650-
for (bc <- caseCls.info.baseClasses.tail)
651-
if (bc.typeParams.exists(_.paramVariance != 0)) {
652-
val caseBT = self.baseType(caseCls)
653-
val thisBT = self.baseType(bc)
654-
val combinedBT = caseBT.baseType(bc)
655-
if (!(thisBT =:= combinedBT))
656-
ctx.errorOrMigrationWarning(
657-
em"""illegal inheritance: $clazz inherits case $caseCls
658-
|but the two have different base type instances for $bc.
659-
|
660-
| Basetype for $clazz: $thisBT
661-
| Basetype via $caseCls: $combinedBT""", clazz.pos)
662-
}
650+
for (baseCls <- caseCls.info.baseClasses.tail)
651+
if (baseCls.typeParams.exists(_.paramVariance != 0))
652+
for (problem <- variantInheritanceProblems(baseCls, caseCls, "non-variant", "case "))
653+
ctx.errorOrMigrationWarning(problem(), clazz.pos)
663654
}
664-
665655
checkNoAbstractMembers()
666656
if (abstractErrors.isEmpty)
667657
checkNoAbstractDecls(clazz)
@@ -684,6 +674,51 @@ object RefChecks {
684674
}
685675
}
686676

677+
if (!clazz.is(Trait)) {
678+
// check that parameterized base classes and traits are typed in the same way as from the superclass
679+
// I.e. say we have
680+
//
681+
// Sub extends Super extends* Base
682+
//
683+
// where `Base` has value parameters. Enforce that
684+
//
685+
// Sub.thisType.baseType(Base) =:= Sub.thisType.baseType(Super).baseType(Base)
686+
//
687+
// This is necessary because parameter values are determined directly or indirectly
688+
// by `Super`. So we cannot pretend they have a different type when seen from `Sub`.
689+
def checkParameterizedTraitsOK() = {
690+
val mixins = clazz.mixins
691+
for {
692+
cls <- clazz.info.baseClasses.tail
693+
if cls.paramAccessors.nonEmpty && !mixins.contains(cls)
694+
problem <- variantInheritanceProblems(cls, clazz.asClass.superClass, "parameterized", "super")
695+
} ctx.error(problem(), clazz.pos)
696+
}
697+
698+
checkParameterizedTraitsOK()
699+
}
700+
701+
/** Check that `site` does not inherit conflicting generic instances of `baseCls`,
702+
* when doing a direct base type or going via intermediate class `middle`. I.e, we require:
703+
*
704+
* site.baseType(baseCls) =:= site.baseType(middle).baseType(baseCls)
705+
*
706+
* Return an optional by name error message if this test fails.
707+
*/
708+
def variantInheritanceProblems(
709+
baseCls: Symbol, middle: Symbol, baseStr: String, middleStr: String): Option[() => String] = {
710+
val superBT = self.baseType(middle)
711+
val thisBT = self.baseType(baseCls)
712+
val combinedBT = superBT.baseType(baseCls)
713+
if (combinedBT =:= thisBT) None // ok
714+
else
715+
Some(() =>
716+
em"""illegal inheritance: $clazz inherits conflicting instances of $baseStr base $baseCls.
717+
|
718+
| Direct basetype: $thisBT
719+
| Basetype via $middleStr$middle: $combinedBT""")
720+
}
721+
687722
/* Returns whether there is a symbol declared in class `inclazz`
688723
* (which must be different from `clazz`) whose name and type
689724
* seen as a member of `class.thisType` matches `member`'s.

tests/neg/i3989f.scala

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
object Test extends App {
2+
trait A[+X](val x: X)
3+
class B[+X](val y: X) extends A[X](y)
4+
class C extends B(5) with A[String] // error: illegal inheritance
5+
6+
class D extends B(5) with A[Any] // ok
7+
8+
def f(a: A[Int]): String = a match {
9+
case c: C => c.x
10+
case _ => "hello"
11+
}
12+
f(new C)
13+
}

tests/neg/i3989g.scala

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
object Test extends App {
2+
class A[+X](val x: X)
3+
class B[+X](val y: X) extends A[X](y)
4+
trait T[+X] extends A[X]
5+
class C extends B(5) with T[String] // error: illegal inheritance
6+
7+
def f(a: A[Int]): String = a match {
8+
case c: C => c.x
9+
case _ => "hello"
10+
}
11+
f(new C)
12+
}

0 commit comments

Comments
 (0)