Skip to content

Commit fa6a257

Browse files
committed
Refine overriding pairs in RefChecks
We now exclude a pair of overriding / overridden symbols that have a common subclass parent of the base class only under the additional condition that their signatures also match. #12828 shows a case where this matters: ```scala trait Foo[A]: def foo(x: A): Unit trait Bar[A] extends Foo[A]: def foo(x: A & String): Unit = println(x.toUpperCase) object Baz extends Bar[Int] // error overriding foo: incompatible type @main def run() = Baz.foo(42) ``` When checking `Baz`, there is a common subclass parent (namely `Bar`), but the signatures of the two `foo` definitions as seen from `Bar` are different, so we cannot assume that the pair has already been checked. Fixes #12828
1 parent 0722bf4 commit fa6a257

File tree

4 files changed

+70
-41
lines changed

4 files changed

+70
-41
lines changed

compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
package dotty.tools.dotc
1+
package dotty.tools
2+
package dotc
23
package transform
34

45
import core._
5-
import Flags._, Symbols._, Contexts._, Scopes._, Decorators._
6+
import Flags._, Symbols._, Contexts._, Scopes._, Decorators._, Types.Type
67
import collection.mutable
78
import collection.immutable.BitSet
89
import scala.annotation.tailrec
@@ -35,9 +36,9 @@ object OverridingPairs {
3536
*/
3637
protected def parents: Array[Symbol] = base.info.parents.toArray.map(_.typeSymbol)
3738

38-
/** Does `sym1` match `sym2` so that it qualifies as overriding.
39-
* Types always match. Term symbols match if their membertypes
40-
* relative to <base>.this do
39+
/** Does `sym1` match `sym2` so that it qualifies as overriding when both symbols are
40+
* seen as members of `self`? Types always match. Term symbols match if their membertypes
41+
* relative to `self` do.
4142
*/
4243
protected def matches(sym1: Symbol, sym2: Symbol): Boolean =
4344
sym1.isType || sym1.asSeenFrom(self).matches(sym2.asSeenFrom(self))
@@ -85,11 +86,22 @@ object OverridingPairs {
8586
then bits += i
8687
subParents(bc) = bits
8788

88-
private def hasCommonParentAsSubclass(cls1: Symbol, cls2: Symbol): Boolean =
89-
(subParents(cls1) intersect subParents(cls2)).nonEmpty
89+
/** Is the override of `sym1` and `sym2` already handled when checking
90+
* a parent of `self`?
91+
*/
92+
private def isHandledByParent(sym1: Symbol, sym2: Symbol): Boolean =
93+
val commonParents = subParents(sym1.owner).intersect(subParents(sym2.owner))
94+
commonParents.nonEmpty
95+
&& commonParents.exists(i => canBeHandledByParent(sym1, sym2, parents(i).thisType))
96+
97+
/** Can pair `sym1`/`sym2` be handled by parent `parentType` which is a common subtype
98+
* of both symbol's owners? Assumed to be true by default, but overridden in RefChecks.
99+
*/
100+
protected def canBeHandledByParent(sym1: Symbol, sym2: Symbol, parentType: Type): Boolean =
101+
true
90102

91103
/** The scope entries that have already been visited as overridden
92-
* (maybe excluded because of hasCommonParentAsSubclass).
104+
* (maybe excluded because of already handled by a parent).
93105
* These will not appear as overriding
94106
*/
95107
private val visited = util.HashSet[Symbol]()
@@ -134,28 +146,22 @@ object OverridingPairs {
134146
* overridden = overridden member of the pair, provided hasNext is true
135147
*/
136148
@tailrec final def next(): Unit =
137-
if (nextEntry ne null) {
149+
if nextEntry != null then
138150
nextEntry = decls.lookupNextEntry(nextEntry)
139-
if (nextEntry ne null)
140-
try {
151+
if nextEntry != null then
152+
try
141153
overridden = nextEntry.sym
142-
if (overriding.owner != overridden.owner && matches(overriding, overridden)) {
154+
if overriding.owner != overridden.owner && matches(overriding, overridden) then
143155
visited += overridden
144-
if (!hasCommonParentAsSubclass(overriding.owner, overridden.owner)) return
145-
}
146-
}
147-
catch {
148-
case ex: TypeError =>
149-
// See neg/i1750a for an example where a cyclic error can arise.
150-
// The root cause in this example is an illegal "override" of an inner trait
151-
report.error(ex, base.srcPos)
152-
}
153-
else {
156+
if !isHandledByParent(overriding, overridden) then return
157+
catch case ex: TypeError =>
158+
// See neg/i1750a for an example where a cyclic error can arise.
159+
// The root cause in this example is an illegal "override" of an inner trait
160+
report.error(ex, base.srcPos)
161+
else
154162
curEntry = curEntry.prev
155163
nextOverriding()
156-
}
157164
next()
158-
}
159165

160166
nextOverriding()
161167
next()

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

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -370,10 +370,13 @@ object RefChecks {
370370
//Console.println(infoString(member) + " shadows1 " + infoString(other) " in " + clazz);//DEBUG
371371
return
372372
val parentSymbols = clazz.info.parents.map(_.typeSymbol)
373-
if (parentSymbols exists (p => subOther(p) && subMember(p) && deferredCheck))
373+
def matchIn(parent: Symbol): Boolean = considerMatching(member, other, parent.thisType)
374+
if parentSymbols.exists(p =>
375+
subOther(p) && subMember(p) && deferredCheck && matchIn(p))
376+
then
374377
//Console.println(infoString(member) + " shadows2 " + infoString(other) + " in " + clazz);//DEBUG
375378
return
376-
if (parentSymbols forall (p => subOther(p) == subMember(p)))
379+
if parentSymbols.forall(p => subOther(p) == subMember(p) && matchIn(p)) then
377380
//Console.println(infoString(member) + " shadows " + infoString(other) + " in " + clazz);//DEBUG
378381
return
379382
}
@@ -496,26 +499,30 @@ object RefChecks {
496499
}*/
497500
}
498501

499-
val opc = new OverridingPairs.Cursor(clazz):
500-
501-
/** We declare a match if either we have a full match including matching names
502-
* or we have a loose match with different target name but the types are the same.
503-
* This leaves two possible sorts of discrepancies to be reported as errors
504-
* in `checkOveride`:
505-
*
506-
* - matching names, target names, and signatures but different types
507-
* - matching names and types, but different target names
508-
*/
509-
override def matches(sym1: Symbol, sym2: Symbol): Boolean =
510-
!(sym1.owner.is(JavaDefined, butNot = Trait) && sym2.owner.is(JavaDefined, butNot = Trait)) && // javac already handles these checks
502+
/** We declare a match if either we have a full match including matching names
503+
* or we have a loose match with different target name but the types are the same.
504+
* This leaves two possible sorts of discrepancies to be reported as errors
505+
* in `checkOveride`:
506+
*
507+
* - matching names, target names, and signatures but different types
508+
* - matching names and types, but different target names
509+
*/
510+
def considerMatching(sym1: Symbol, sym2: Symbol, self: Type): Boolean =
511+
!(sym1.owner.is(JavaDefined, butNot = Trait) && sym2.owner.is(JavaDefined, butNot = Trait)) && // javac already handles these checks
511512
(sym1.isType || {
512-
val sd1 = sym1.asSeenFrom(clazz.thisType)
513-
val sd2 = sym2.asSeenFrom(clazz.thisType)
513+
val sd1 = sym1.asSeenFrom(self)
514+
val sd2 = sym2.asSeenFrom(self)
514515
sd1.matchesLoosely(sd2)
515516
&& (sym1.hasTargetName(sym2.targetName)
516517
|| compatibleTypes(sym1, sd1.info, sym2, sd2.info))
517518
})
518-
end opc
519+
520+
val opc = new OverridingPairs.Cursor(clazz):
521+
override def matches(sym1: Symbol, sym2: Symbol): Boolean =
522+
considerMatching(sym1, sym2, self)
523+
override def canBeHandledByParent(sym1: Symbol, sym2: Symbol, parentType: Type): Boolean =
524+
considerMatching(sym1, sym2, parentType)
525+
.showing(i"already handled ${sym1.showLocated}: ${sym1.asSeenFrom(parentType).signature}, ${sym2.showLocated}: ${sym2.asSeenFrom(parentType).signature} = $result", refcheck)
519526

520527
while opc.hasNext do
521528
checkOverride(opc.overriding, opc.overridden)

tests/neg/i12828.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- [E163] Declaration Error: tests/neg/i12828.scala:7:7 ----------------------------------------------------------------
2+
7 |object Baz extends Bar[Int] // error overriding foo: incompatible type
3+
| ^
4+
| error overriding method foo in trait Foo of type (x: Int): Unit;
5+
| method foo in trait Bar of type (x: Int & String): Unit has incompatible type
6+
7+
longer explanation available when compiling with `-explain`

tests/neg/i12828.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
trait Foo[A]:
2+
def foo(x: A): Unit
3+
4+
trait Bar[A] extends Foo[A]:
5+
def foo(x: A & String): Unit = println(x.toUpperCase)
6+
7+
object Baz extends Bar[Int] // error overriding foo: incompatible type
8+
9+
@main def run() = Baz.foo(42)

0 commit comments

Comments
 (0)