Skip to content

Commit 40c587f

Browse files
authored
Merge pull request #12982 from dotty-staging/fix-12828
Refine overriding pairs in RefChecks
2 parents 153d8b7 + c238f82 commit 40c587f

16 files changed

+199
-82
lines changed

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -1021,7 +1021,7 @@ object Denotations {
10211021
* erasure (see i8615b, i9109b), Erasure takes care of adding any necessary
10221022
* bridge to make this work at runtime.
10231023
*/
1024-
def matchesLoosely(other: SingleDenotation)(using Context): Boolean =
1024+
def matchesLoosely(other: SingleDenotation, alwaysCompareTypes: Boolean = false)(using Context): Boolean =
10251025
if isType then true
10261026
else
10271027
val thisLanguage = SourceLanguage(symbol)
@@ -1031,7 +1031,7 @@ object Denotations {
10311031
val otherSig = other.signature(commonLanguage)
10321032
sig.matchDegree(otherSig) match
10331033
case FullMatch =>
1034-
true
1034+
!alwaysCompareTypes || info.matches(other.info)
10351035
case MethodNotAMethodMatch =>
10361036
!ctx.erasedTypes && {
10371037
// A Scala zero-parameter method and a Scala non-method always match.

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

+8-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(using Context) {
3636
!sym.isOneOf(MethodOrModule) || super.exclude(sym)
3737
}
3838

39-
//val site = root.thisType
39+
val site = root.thisType
4040

4141
private var toBeRemoved = immutable.Set[Symbol]()
4242
private val bridges = mutable.ListBuffer[Tree]()
@@ -77,7 +77,13 @@ class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(using Context) {
7777
|clashes with definition of the member itself; both have erased type ${info(member)(using elimErasedCtx)}."""",
7878
bridgePosFor(member))
7979
}
80-
else if (!bridgeExists)
80+
else if !inContext(preErasureCtx)(site.memberInfo(member).matches(site.memberInfo(other))) then
81+
// Neither symbol signatures nor pre-erasure types seen from root match; this means
82+
// according to Scala 2 semantics there is no override.
83+
// A bridge might introduce a classcast exception.
84+
// Example where this was observed: run/i12828a.scala and MapView in stdlib213
85+
report.log(i"suppress bridge in $root for ${member} in ${member.owner} and ${other.showLocated} since member infos ${site.memberInfo(member)} and ${site.memberInfo(other)} do not match")
86+
else if !bridgeExists then
8187
addBridge(member, other)
8288
}
8389

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

+31-25
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
@@ -33,11 +34,11 @@ object OverridingPairs {
3334
* pair has already been treated in a parent class.
3435
* This may be refined in subclasses. @see Bridges for a use case.
3536
*/
36-
protected def parents: Array[Symbol] = base.info.parents.toArray.map(_.typeSymbol)
37+
protected def parents: Array[Symbol] = base.info.parents.toArray.map(_.classSymbol)
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)))
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, parent: Symbol): 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

+65-48
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,14 @@ object RefChecks {
323323
overrideErrorMsg("no longer has compatible type"),
324324
(if (member.owner == clazz) member else clazz).srcPos))
325325

326+
/** Do types of term members `member` and `other` as seen from `self` match?
327+
* If not we treat them as not a real override and don't issue override
328+
* error messages. Also, bridges are not generated in this case.
329+
* Type members are always assumed to match.
330+
*/
331+
def trueMatch: Boolean =
332+
member.isType || memberTp(self).matches(otherTp(self))
333+
326334
def emitOverrideError(fullmsg: Message) =
327335
if (!(hasErrors && member.is(Synthetic) && member.is(Module))) {
328336
// suppress errors relating toi synthetic companion objects if other override
@@ -333,7 +341,7 @@ object RefChecks {
333341
}
334342

335343
def overrideError(msg: String, compareTypes: Boolean = false) =
336-
if (noErrorType)
344+
if trueMatch && noErrorType then
337345
emitOverrideError(overrideErrorMsg(msg, compareTypes))
338346

339347
def autoOverride(sym: Symbol) =
@@ -360,24 +368,6 @@ object RefChecks {
360368

361369
//Console.println(infoString(member) + " overrides " + infoString(other) + " in " + clazz);//DEBUG
362370

363-
// return if we already checked this combination elsewhere
364-
if (member.owner != clazz) {
365-
def deferredCheck = member.is(Deferred) || !other.is(Deferred)
366-
def subOther(s: Symbol) = s derivesFrom other.owner
367-
def subMember(s: Symbol) = s derivesFrom member.owner
368-
369-
if (subOther(member.owner) && deferredCheck)
370-
//Console.println(infoString(member) + " shadows1 " + infoString(other) " in " + clazz);//DEBUG
371-
return
372-
val parentSymbols = clazz.info.parents.map(_.typeSymbol)
373-
if (parentSymbols exists (p => subOther(p) && subMember(p) && deferredCheck))
374-
//Console.println(infoString(member) + " shadows2 " + infoString(other) + " in " + clazz);//DEBUG
375-
return
376-
if (parentSymbols forall (p => subOther(p) == subMember(p)))
377-
//Console.println(infoString(member) + " shadows " + infoString(other) + " in " + clazz);//DEBUG
378-
return
379-
}
380-
381371
/* Is the intersection between given two lists of overridden symbols empty? */
382372
def intersectionIsEmpty(syms1: Iterator[Symbol], syms2: Iterator[Symbol]) = {
383373
val set2 = syms2.toSet
@@ -412,7 +402,7 @@ object RefChecks {
412402
overrideError("cannot be used here - class definitions cannot be overridden")
413403
else if (!other.is(Deferred) && member.isClass)
414404
overrideError("cannot be used here - classes can only override abstract types")
415-
else if (other.isEffectivelyFinal) // (1.2)
405+
else if other.isEffectivelyFinal then // (1.2)
416406
overrideError(i"cannot override final member ${other.showLocated}")
417407
else if (member.is(ExtensionMethod) && !other.is(ExtensionMethod)) // (1.3)
418408
overrideError("is an extension method, cannot override a normal method")
@@ -433,9 +423,11 @@ object RefChecks {
433423
member.setFlag(Override)
434424
else if (member.isType && self.memberInfo(member) =:= self.memberInfo(other))
435425
() // OK, don't complain about type aliases which are equal
436-
else if (member.owner != clazz && other.owner != clazz &&
437-
!(other.owner derivesFrom member.owner))
438-
emitOverrideError(
426+
else if member.owner != clazz
427+
&& other.owner != clazz
428+
&& !other.owner.derivesFrom(member.owner)
429+
then
430+
overrideError(
439431
s"$clazz inherits conflicting members:\n "
440432
+ infoStringWithLocation(other) + " and\n " + infoStringWithLocation(member)
441433
+ "\n(Note: this can be resolved by declaring an override in " + clazz + ".)")
@@ -496,25 +488,51 @@ object RefChecks {
496488
}*/
497489
}
498490

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
511-
(sym1.isType || {
512-
val sd1 = sym1.asSeenFrom(clazz.thisType)
513-
val sd2 = sym2.asSeenFrom(clazz.thisType)
514-
sd1.matchesLoosely(sd2)
491+
/** We declare a match if either we have a full match including matching names
492+
* or we have a loose match with different target name but the types are the same.
493+
* This leaves two possible sorts of discrepancies to be reported as errors
494+
* in `checkOveride`:
495+
*
496+
* - matching names, target names, and signatures but different types
497+
* - matching names and types, but different target names
498+
*/
499+
def considerMatching(sym1: Symbol, sym2: Symbol, self: Type): Boolean =
500+
if sym1.owner.is(JavaDefined, butNot = Trait)
501+
&& sym2.owner.is(JavaDefined, butNot = Trait)
502+
then false // javac already handles these checks
503+
else if sym1.isType then true
504+
else
505+
val sd1 = sym1.asSeenFrom(self)
506+
val sd2 = sym2.asSeenFrom(self)
507+
sd1.matchesLoosely(sd2)
515508
&& (sym1.hasTargetName(sym2.targetName)
516509
|| compatibleTypes(sym1, sd1.info, sym2, sd2.info))
517-
})
510+
511+
val opc = new OverridingPairs.Cursor(clazz):
512+
override def matches(sym1: Symbol, sym2: Symbol): Boolean =
513+
considerMatching(sym1, sym2, self)
514+
515+
private def inLinearizationOrder(sym1: Symbol, sym2: Symbol, parent: Symbol): Boolean =
516+
val owner1 = sym1.owner
517+
val owner2 = sym2.owner
518+
def precedesIn(bcs: List[ClassSymbol]): Boolean = (bcs: @unchecked) match
519+
case bc :: bcs1 =>
520+
if owner1 eq bc then true
521+
else if owner2 eq bc then false
522+
else precedesIn(bcs1)
523+
case _ =>
524+
false
525+
precedesIn(parent.asClass.baseClasses)
526+
527+
// We can exclude pairs safely from checking only under two additional conditions
528+
// - their signatures also match in the parent class.
529+
// See neg/i12828.scala for an example where this matters.
530+
// - They overriding/overridden appear in linearization order.
531+
// See neg/i5094.scala for an example where this matters.
532+
override def canBeHandledByParent(sym1: Symbol, sym2: Symbol, parent: Symbol): Boolean =
533+
considerMatching(sym1, sym2, parent.thisType)
534+
.showing(i"already handled ${sym1.showLocated}: ${sym1.asSeenFrom(parent.thisType).signature}, ${sym2.showLocated}: ${sym2.asSeenFrom(parent.thisType).signature} = $result", refcheck)
535+
&& inLinearizationOrder(sym1, sym2, parent)
518536
end opc
519537

520538
while opc.hasNext do
@@ -528,13 +546,11 @@ object RefChecks {
528546
//
529547
// class A { type T = B }
530548
// class B extends A { override type T }
531-
for
532-
dcl <- clazz.info.decls.iterator
533-
if dcl.is(Deferred)
534-
other <- dcl.allOverriddenSymbols
535-
if !other.is(Deferred)
536-
do
537-
checkOverride(dcl, other)
549+
for dcl <- clazz.info.decls.iterator do
550+
if dcl.is(Deferred) then
551+
for other <- dcl.allOverriddenSymbols do
552+
if !other.is(Deferred) then
553+
checkOverride(dcl, other)
538554

539555
printMixinOverrideErrors()
540556

@@ -578,7 +594,8 @@ object RefChecks {
578594
def isConcrete(sym: Symbol) = sym.exists && !sym.isOneOf(NotConcrete)
579595
clazz.nonPrivateMembersNamed(mbr.name)
580596
.filterWithPredicate(
581-
impl => isConcrete(impl.symbol) && mbrDenot.matchesLoosely(impl))
597+
impl => isConcrete(impl.symbol)
598+
&& mbrDenot.matchesLoosely(impl, alwaysCompareTypes = true))
582599
.exists
583600

584601
/** The term symbols in this class and its baseclasses that are

tests/neg/OpaqueEscape.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ def unwrap(i:Wrapped):Int
77
def wrap(i:Int):Wrapped
88
}
99
class Escaper extends EscaperBase{ // error: needs to be abstract
10-
override def unwrap(i:Int):Int = i // error overriding method unwrap
10+
override def unwrap(i:Int):Int = i // was error overriding method unwrap, now OK
1111
override def wrap(i:Int):Int = i // error overriding method wrap
1212
}
1313
val e = new Escaper:EscaperBase

tests/neg/i11828.check

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- Error: tests/neg/i12828.scala:7:7 -----------------------------------------------------------------------------------
2+
7 |object Baz extends Bar[Int] // error overriding foo: incompatible type
3+
| ^
4+
| object creation impossible, since def foo(x: A): Unit in trait Foo is not defined
5+
| (Note that
6+
| parameter A in def foo(x: A): Unit in trait Foo does not match
7+
| parameter Int & String in def foo(x: A & String): Unit in trait Bar
8+
| )

tests/neg/i12828.check

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- Error: tests/neg/i12828.scala:7:7 -----------------------------------------------------------------------------------
2+
7 |object Baz extends Bar[Int] // error: not implemented
3+
| ^
4+
| object creation impossible, since def foo(x: A): Unit in trait Foo is not defined
5+
| (Note that
6+
| parameter A in def foo(x: A): Unit in trait Foo does not match
7+
| parameter Int & String in def foo(x: A & String): Unit in trait Bar
8+
| )

tests/neg/i12828.scala

+9
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: not implemented
8+
9+
@main def run() = Baz.foo(42)

tests/neg/i12828c.scala

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
abstract class Foo[A] {
2+
def foo(x: A): Unit
3+
}
4+
abstract class Bar[A] extends Foo[A] {
5+
def foo(x: A with String): Unit = println(x.toUpperCase)
6+
}
7+
object Baz extends Bar[Int] // error: not implemented (same as Scala 2)
8+
// Scala 2 gives: object creation impossible. Missing implementation for `foo`
9+
10+
object Test {
11+
def main(args: Array[String]) = Baz.foo(42)
12+
}

tests/neg/i12828d.scala

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
trait A[X] {
2+
def foo(x: X): Unit =
3+
println("A.foo")
4+
}
5+
trait B[X] extends A[X] {
6+
def foo(x: Int): Unit =
7+
println("B.foo")
8+
}
9+
object C extends B[Int] // error: conflicting members
10+
// Scala 2: same
11+
12+
object Test {
13+
def main(args: Array[String]) = {
14+
C.foo(1)
15+
val a: A[Int] = C
16+
a.foo(1)
17+
}
18+
}

tests/run/i5094.scala renamed to tests/neg/i5094.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ trait SOIO extends IO {
99
}
1010
trait SOSO extends SOIO with SO
1111
abstract class AS extends SO
12-
class L extends AS with SOSO
12+
class L extends AS with SOSO // error: cannot override final member
1313
object Test {
1414
def main(args: Array[String]): Unit = {
1515
new L

tests/neg/i7597.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ object Test extends App {
66
def apply(x: A): B
77
}
88

9-
class C[S <: String] extends Fn[String, Int] {
10-
def apply(s: S): Int = 0 // error
9+
class C[S <: String] extends Fn[String, Int] { // error
10+
def apply(s: S): Int = 0
1111
}
1212

1313
foo("")

0 commit comments

Comments
 (0)