Skip to content

Commit 521590b

Browse files
committed
Fix #5433: check that the implemented super-accessor is valid
Relying on `matchingDenotation` is not enough as demonstrated by i5433.scala: in `Fail`, `B#foo` matches `C$$super$foo` but it cannot implement it since `X` is a supertype of `Y` Note that scalac seems had the same bug (but at least in Dotty this is detected by -Ycheck:all), now fixed based on the logic in this PR by scala/scala#7641. For reference, here's what the spec says on resolving super accessors: A reference super.m refers statically to a method or type m in the least proper supertype of the innermost template containing the reference. It evaluates to the member m′ in the actual supertype of that template which is equal to m or which overrides m.
1 parent df8f79f commit 521590b

File tree

3 files changed

+89
-2
lines changed

3 files changed

+89
-2
lines changed

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

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,17 +93,60 @@ object ResolveSuper {
9393
*
9494
* @param base The class in which everything is mixed together
9595
* @param acc The symbol statically referred to by the superaccessor in the trait
96+
* @param mixin The trait where the superaccessor is defined
9697
*/
9798
def rebindSuper(base: Symbol, acc: Symbol)(implicit ctx: Context): Symbol = {
9899
var bcs = base.info.baseClasses.dropWhile(acc.owner != _).tail
99100
var sym: Symbol = NoSymbol
100101
val SuperAccessorName(memberName) = acc.name.unexpandedName
101102
ctx.debuglog(i"starting rebindsuper from $base of ${acc.showLocated}: ${acc.info} in $bcs, name = $memberName")
102103
while (bcs.nonEmpty && sym == NoSymbol) {
103-
val other = bcs.head.info.nonPrivateDecl(memberName)
104+
val cur = bcs.head
105+
val other = cur.info.nonPrivateDecl(memberName)
104106
if (ctx.settings.Ydebug.value)
105107
ctx.log(i"rebindsuper ${bcs.head} $other deferred = ${other.symbol.is(Deferred)}")
106-
sym = other.matchingDenotation(base.thisType, base.thisType.memberInfo(acc)).symbol
108+
val otherMember = other.matchingDenotation(base.thisType, base.thisType.memberInfo(acc))
109+
if (otherMember.exists) {
110+
sym = otherMember.symbol
111+
// Having a matching denotation is not enough: it should also be a subtype
112+
// of the superaccessor's type, see i5433.scala for an example where this matters
113+
val otherTp = otherMember.asSeenFrom(base.typeRef).info
114+
val accTp = acc.asSeenFrom(base.typeRef).info
115+
if (!(otherTp <:< accTp)) {
116+
val superCall = i"super.$memberName"
117+
val accSuperCall = i"super[${acc.owner.name}].$memberName"
118+
val resolvedSuperCall = i"super[${cur.name}].$memberName"
119+
val overriddenOwnerName = {
120+
val overriddenIterator = other.symbol.allOverriddenSymbols
121+
if (overriddenIterator.hasNext)
122+
overriddenIterator.next.owner.name
123+
else
124+
"SomeParent"
125+
}
126+
val overriddenSuperCall = i"super[$overriddenOwnerName].$memberName"
127+
ctx.error(
128+
hl"""$base cannot be instantiated due to a conflict between its parents when
129+
|resolving a super-call:
130+
|
131+
|1. One of its parent (${acc.owner}) contains a call $superCall in its body.
132+
|2. Because ${cur.name} comes before ${acc.owner.name} in the linearization
133+
| order of ${base.name}, and because ${cur.name} overrides $memberName,
134+
| this is resolved as a call to $resolvedSuperCall in ${base.name}.
135+
|3. However:
136+
| ${otherTp.widenExpr} (the type of $resolvedSuperCall in ${base.name})
137+
| is not a subtype of
138+
| ${accTp.widenExpr} (the type of $accSuperCall in ${base.name})
139+
|
140+
|Here are two possible ways to resolve this:
141+
|
142+
|1. Change the linearization order of ${base.name} such that
143+
| ${acc.owner.name} comes before ${cur.name}.
144+
|2. Alternatively, replace $superCall in the body of ${acc.owner} by a
145+
| super-call to a specific parent, e.g. $overriddenSuperCall
146+
|""".stripMargin, base.sourcePos)
147+
}
148+
}
149+
107150
bcs = bcs.tail
108151
}
109152
assert(sym.exists)

tests/neg/i5433.check

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<298..298> in i5433.scala
2+
class Fail cannot be instantiated due to a conflict between its parents when
3+
resolving a super-call:
4+
5+
1. One of its parent (trait C) contains a call super.foo in its body.
6+
2. Because B comes before C in the linearization
7+
order of Fail, and because B overrides foo,
8+
this is resolved as a call to super[B].foo in Fail.
9+
3. However:
10+
X (the type of super[B].foo in Fail)
11+
is not a subtype of
12+
Y (the type of super[C].foo in Fail)
13+
14+
Here are two possible ways to resolve this:
15+
16+
1. Change the linearization order of Fail such that
17+
C comes before B.
18+
2. Alternatively, replace super.foo in the body of trait C by a
19+
super-call to a specific parent, e.g. super[A].foo

tests/neg/i5433.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
class X
2+
class Y extends X
3+
4+
trait A[+T] {
5+
def foo: T = null.asInstanceOf[T]
6+
}
7+
8+
trait B extends A[X] {
9+
override def foo: X = new X
10+
}
11+
12+
trait C extends A[Y] {
13+
override def foo: Y = new Y
14+
def superFoo: Y = super.foo // C will have an abstract `def C$$super$foo: Y` because of this call
15+
}
16+
17+
class Fail extends B with C // error
18+
// Generated `def C$$super$foo: Y = super[B].foo`
19+
20+
object Test {
21+
def main(args: Array[String]): Unit = {
22+
val y: Y = (new Fail).superFoo // Used to fail with a ClassCastException because of `Fail#C$$super$foo` being incorrect above
23+
assert(y == null)
24+
}
25+
}

0 commit comments

Comments
 (0)