Skip to content

Commit e872aee

Browse files
committed
Fix #3989: Check that members of concrete classes are type-correct
This fixes the second half of #3989. Some tests had to be updated or re-classified because they were unsound before.
1 parent a7c6ff7 commit e872aee

File tree

6 files changed

+65
-7
lines changed

6 files changed

+65
-7
lines changed

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

+49
Original file line numberDiff line numberDiff line change
@@ -594,12 +594,61 @@ object RefChecks {
594594
checkNoAbstractDecls(bc.asClass.superClass)
595595
}
596596

597+
// Check that every term member of this concrete class has a symbol that matches the member's type
598+
// Member types are computed by intersecting the types of all members that have the same name
599+
// and signature. But a member selection will pick one particular implementation, according to
600+
// the rules of overriding and linearization. This method checks that the implementation has indeed
601+
// a type that subsumes the full member type.
602+
def checkMemberTypesOK() = {
603+
604+
// First compute all member names we need to check in `membersToCheck`.
605+
// We do not check
606+
// - types
607+
// - synthetic members or bridges
608+
// - members in other concrete classes, since these have been checked before
609+
// (this is done for efficiency)
610+
// - members in a prefix of inherited parents that all come from Java or Scala2
611+
// (this is done to avoid false negatives since Scala2's rules for checking are different)
612+
val membersToCheck = mutable.Set[Name]()
613+
val seenClasses = mutable.Set[Symbol]()
614+
def addDecls(cls: Symbol): Unit =
615+
if (!seenClasses.contains(cls)) {
616+
seenClasses.+=(cls)
617+
for (mbr <- cls.info.decls)
618+
if (mbr.isTerm && !mbr.is(Synthetic | Bridge) && mbr.memberCanMatchInheritedSymbols &&
619+
!membersToCheck.contains(mbr.name))
620+
membersToCheck.+=(mbr.name)
621+
cls.info.parents.map(_.classSymbol)
622+
.filter(_.is(AbstractOrTrait))
623+
.dropWhile(_.is(JavaDefined | Scala2x))
624+
.foreach(addDecls)
625+
}
626+
addDecls(clazz)
627+
628+
// For each member, check that the type of its symbol, as seen from `self`
629+
// can override the info of this member
630+
for (name <- membersToCheck) {
631+
for (mbrd <- self.member(name).alternatives) {
632+
val mbr = mbrd.symbol
633+
val mbrType = mbr.info.asSeenFrom(self, mbr.owner)
634+
if (!mbrType.overrides(mbrd.info, matchLoosely = true))
635+
ctx.errorOrMigrationWarning(
636+
em"""${mbr.showLocated} is not a legal implementation of `$name' in $clazz
637+
| its type $mbrType
638+
| does not conform to ${mbrd.info}""",
639+
(if (mbr.owner == clazz) mbr else clazz).pos)
640+
}
641+
}
642+
}
643+
597644
checkNoAbstractMembers()
598645
if (abstractErrors.isEmpty)
599646
checkNoAbstractDecls(clazz)
600647

601648
if (abstractErrors.nonEmpty)
602649
ctx.error(abstractErrorMessage, clazz.pos)
650+
651+
checkMemberTypesOK()
603652
} else if (clazz.is(Trait) && !(clazz derivesFrom defn.AnyValClass)) {
604653
// For non-AnyVal classes, prevent abstract methods in interfaces that override
605654
// final members in Object; see #4431

tests/neg/i1240b.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ abstract class A[X] extends T[X] {
99
trait U[X] extends T[X] {
1010
abstract override def foo(x: X): X = super.foo(x)
1111
}
12-
object Test extends A[String] with U[String] // error: accidental override
12+
object Test extends A[String] with U[String] // error: accidental override // error: merge error

tests/neg/i3989.scala

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
object Test extends App {
2+
trait A[+X] { def get: X }
3+
case class B[X](x: X) extends A[X] { def get: X = x }
4+
class C[X](x: Any) extends B[Any](x) with A[X] // error: not a legal implementation of `get'
5+
def g(a: A[Int]): Int = a.get
6+
g(new C[Int]("foo"))
7+
}
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import java.util.Comparator
22

3-
trait Trait1[T] { def foo(arg: Comparator[T]): Unit }
3+
trait Trait1[T] { def foo(arg: Comparator[T]): String }
44

55
trait Trait2[T] extends Trait1[T] { def foo(arg: Comparator[String]): Int = 0 }
66

7-
class Class1 extends Trait2[String] { }
7+
class Class1 extends Trait2[String] { } // error: not a legal implementation of `foo'
88

99
object Test {
1010
def main(args: Array[String]): Unit = {
1111
val c = new Class1
1212
c.foo(Ordering[String])
13+
val t: Trait1[String] = c
14+
val x: String = t.foo(Ordering[String])
1315
}
1416
}

tests/pos-special/strawman-collections/CollectionStrawMan6.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ object CollectionStrawMan6 extends LowPriority {
415415
/** Concrete collection type: List */
416416
sealed trait List[+A]
417417
extends LinearSeq[A]
418-
with SeqLike[A, List]
418+
with LinearSeqLike[A, List]
419419
with Buildable[A, List[A]] {
420420

421421
def fromIterable[B](c: Iterable[B]): List[B] = List.fromIterable(c)
@@ -604,7 +604,7 @@ object CollectionStrawMan6 extends LowPriority {
604604
}
605605

606606
class LazyList[+A](expr: => LazyList.Evaluated[A])
607-
extends LinearSeq[A] with SeqLike[A, LazyList] {
607+
extends LinearSeq[A] with LinearSeqLike[A, LazyList] {
608608
private[this] var evaluated = false
609609
private[this] var result: LazyList.Evaluated[A] = _
610610

tests/run/colltest6/CollectionStrawMan6_1.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ object CollectionStrawMan6 extends LowPriority {
416416
/** Concrete collection type: List */
417417
sealed trait List[+A]
418418
extends LinearSeq[A]
419-
with SeqLike[A, List]
419+
with LinearSeqLike[A, List]
420420
with Buildable[A, List[A]] {
421421

422422
def fromIterable[B](c: Iterable[B]): List[B] = List.fromIterable(c)
@@ -605,7 +605,7 @@ object CollectionStrawMan6 extends LowPriority {
605605
}
606606

607607
class LazyList[+A](expr: => LazyList.Evaluated[A])
608-
extends LinearSeq[A] with SeqLike[A, LazyList] {
608+
extends LinearSeq[A] with LinearSeqLike[A, LazyList] {
609609
private[this] var evaluated = false
610610
private[this] var result: LazyList.Evaluated[A] = _
611611

0 commit comments

Comments
 (0)