Skip to content

Commit fb68cf4

Browse files
authored
Merge pull request #5604 from dotty-staging/fix-super-mixin-2
Fix #5433: check that the implemented super-accessor is valid
2 parents 0558618 + 8bad055 commit fb68cf4

File tree

7 files changed

+132
-14
lines changed

7 files changed

+132
-14
lines changed

compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,8 @@ public enum ErrorMessageID {
144144
OverloadInRefinementID,
145145
NoMatchingOverloadID,
146146
StableIdentPatternID,
147-
StaticFieldsShouldPrecedeNonStaticID
147+
StaticFieldsShouldPrecedeNonStaticID,
148+
IllegalSuperAccessorID
148149
;
149150

150151
public int errorNumber() {

compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala

+56
Original file line numberDiff line numberDiff line change
@@ -2265,4 +2265,60 @@ object messages {
22652265
hl"""Stable identifier required, but ${tree.show} found"""
22662266
override def explanation: String = ""
22672267
}
2268+
2269+
case class IllegalSuperAccessor(base: Symbol, memberName: Name,
2270+
acc: Symbol, accTp: Type,
2271+
other: Symbol, otherTp: Type)(implicit val ctx: Context) extends Message(IllegalSuperAccessorID) {
2272+
val kind: String = "Reference"
2273+
val msg: String = {
2274+
// The mixin containing a super-call that requires a super-accessor
2275+
val accMixin = acc.owner
2276+
// The class or trait that the super-accessor should resolve too in `base`
2277+
val otherMixin = other.owner
2278+
// The super-call in `accMixin`
2279+
val superCall = i"super.$memberName"
2280+
// The super-call that the super-accesors in `base` forwards to
2281+
val resolvedSuperCall = i"super[${otherMixin.name}].$memberName"
2282+
// The super-call that we would have called if `super` in traits behaved like it
2283+
// does in classes, i.e. followed the linearization of the trait itself.
2284+
val staticSuperCall = {
2285+
val staticSuper = accMixin.asClass.info.parents.reverse
2286+
.find(_.nonPrivateMember(memberName).matchingDenotation(accMixin.thisType, acc.info).exists)
2287+
val staticSuperName = staticSuper match {
2288+
case Some(parent) =>
2289+
parent.classSymbol.name.show
2290+
case None => // Might be reachable under separate compilation
2291+
"SomeParent"
2292+
}
2293+
i"super[$staticSuperName].$memberName"
2294+
}
2295+
hl"""$base cannot be defined due to a conflict between its parents when
2296+
|implementing a super-accessor for $memberName in $accMixin:
2297+
|
2298+
|1. One of its parent (${accMixin.name}) contains a call $superCall in its body,
2299+
| and when a super-call in a trait is written without an explicit parent
2300+
| listed in brackets, it is implemented by a generated super-accessor in
2301+
| the class that extends this trait based on the linearization order of
2302+
| the class.
2303+
|2. Because ${otherMixin.name} comes before ${accMixin.name} in the linearization
2304+
| order of ${base.name}, and because ${otherMixin.name} overrides $memberName,
2305+
| the super-accessor in ${base.name} is implemented as a call to
2306+
| $resolvedSuperCall.
2307+
|3. However,
2308+
| ${otherTp.widenExpr} (the type of $resolvedSuperCall in ${base.name})
2309+
| is not a subtype of
2310+
| ${accTp.widenExpr} (the type of $memberName in $accMixin).
2311+
| Hence, the super-accessor that needs to be generated in ${base.name}
2312+
| is illegal.
2313+
|
2314+
|Here are two possible ways to resolve this:
2315+
|
2316+
|1. Change the linearization order of ${base.name} such that
2317+
| ${accMixin.name} comes before ${otherMixin.name}.
2318+
|2. Alternatively, replace $superCall in the body of $accMixin by a
2319+
| super-call to a specific parent, e.g. $staticSuperCall
2320+
|""".stripMargin
2321+
}
2322+
val explanation: String = ""
2323+
}
22682324
}

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

+13-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import DenotTransformers._
1212
import NameOps._
1313
import NameKinds._
1414
import ResolveSuper._
15+
import reporting.diagnostic.messages.IllegalSuperAccessor
1516

1617
/** This phase adds super accessors and method overrides where
1718
* linearization differs from Java's rule for default methods in interfaces.
@@ -101,9 +102,18 @@ object ResolveSuper {
101102
ctx.debuglog(i"starting rebindsuper from $base of ${acc.showLocated}: ${acc.info} in $bcs, name = $memberName")
102103
while (bcs.nonEmpty && sym == NoSymbol) {
103104
val other = bcs.head.info.nonPrivateDecl(memberName)
104-
if (ctx.settings.Ydebug.value)
105-
ctx.log(i"rebindsuper ${bcs.head} $other deferred = ${other.symbol.is(Deferred)}")
106-
sym = other.matchingDenotation(base.thisType, base.thisType.memberInfo(acc)).symbol
105+
.matchingDenotation(base.thisType, base.thisType.memberInfo(acc))
106+
ctx.debuglog(i"rebindsuper ${bcs.head} $other deferred = ${other.symbol.is(Deferred)}")
107+
if (other.exists) {
108+
sym = other.symbol
109+
// Having a matching denotation is not enough: it should also be a subtype
110+
// of the superaccessor's type, see i5433.scala for an example where this matters
111+
val otherTp = other.asSeenFrom(base.typeRef).info
112+
val accTp = acc.asSeenFrom(base.typeRef).info
113+
if (!(otherTp <:< accTp))
114+
ctx.error(IllegalSuperAccessor(base, memberName, acc, accTp, other.symbol, otherTp), base.sourcePos)
115+
}
116+
107117
bcs = bcs.tail
108118
}
109119
assert(sym.exists)

compiler/test/dotty/tools/TestSources.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ object TestSources {
2121
val acc2 = files.foldLeft(acc)((acc1, file) => if (file.isFile && file.getPath.endsWith(".scala")) file.getPath :: acc1 else acc1)
2222
files.foldLeft(acc2)((acc3, file) => if (file.isDirectory) collectAllFilesInDir(file, acc3) else acc3)
2323
}
24-
collectAllFilesInDir(new File(stdLibPath), Nil)
24+
collectAllFilesInDir(new File(stdLibPath), Nil).sorted
2525
}
2626

2727
// pos tests lists

tests/neg/i5433.check

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<298..298> in i5433.scala
2+
class Fail cannot be defined due to a conflict between its parents when
3+
implementing a super-accessor for foo in trait C:
4+
5+
1. One of its parent (C) contains a call super.foo in its body,
6+
and when a super-call in a trait is written without an explicit parent
7+
listed in brackets, it is implemented by a generated super-accessor in
8+
the class that extends this trait based on the linearization order of
9+
the class.
10+
2. Because B comes before C in the linearization
11+
order of Fail, and because B overrides foo,
12+
the super-accessor in Fail is implemented as a call to
13+
super[B].foo.
14+
3. However,
15+
X (the type of super[B].foo in Fail)
16+
is not a subtype of
17+
Y (the type of foo in trait C).
18+
Hence, the super-accessor that needs to be generated in Fail
19+
is illegal.
20+
21+
Here are two possible ways to resolve this:
22+
23+
1. Change the linearization order of Fail such that
24+
C comes before B.
25+
2. Alternatively, replace super.foo in the body of trait C by a
26+
super-call to a specific parent, e.g. super[A].foo

tests/neg/i5433.scala

+25
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+
}

tests/scala2-library/src/library/scala/collection/IndexedSeqOptimized.scala

+9-9
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,11 @@ trait IndexedSeqOptimized[+A, +Repr] extends Any with IndexedSeqLike[A, Repr] {
7070

7171
override /*TraversableLike*/
7272
def reduceLeft[B >: A](op: (B, A) => B): B =
73-
if (length > 0) foldl(1, length, this(0), op) else super.reduceLeft(op)
73+
if (length > 0) foldl(1, length, this(0), op) else super[IndexedSeqLike].reduceLeft(op)
7474

7575
override /*IterableLike*/
7676
def reduceRight[B >: A](op: (A, B) => B): B =
77-
if (length > 0) foldr(0, length - 1, this(length - 1), op) else super.reduceRight(op)
77+
if (length > 0) foldr(0, length - 1, this(length - 1), op) else super[IndexedSeqLike].reduceRight(op)
7878

7979
override /*IterableLike*/
8080
def zip[A1 >: A, B, That](that: GenIterable[B])(implicit bf: CanBuildFrom[Repr, (A1, B), That]): That = that match {
@@ -89,7 +89,7 @@ trait IndexedSeqOptimized[+A, +Repr] extends Any with IndexedSeqLike[A, Repr] {
8989
}
9090
b.result()
9191
case _ =>
92-
super.zip[A1, B, That](that)(bf)
92+
super[IndexedSeqLike].zip[A1, B, That](that)(bf)
9393
}
9494

9595
override /*IterableLike*/
@@ -122,16 +122,16 @@ trait IndexedSeqOptimized[+A, +Repr] extends Any with IndexedSeqLike[A, Repr] {
122122
}
123123

124124
override /*IterableLike*/
125-
def head: A = if (isEmpty) super.head else this(0)
125+
def head: A = if (isEmpty) super[IndexedSeqLike].head else this(0)
126126

127127
override /*TraversableLike*/
128-
def tail: Repr = if (isEmpty) super.tail else slice(1, length)
128+
def tail: Repr = if (isEmpty) super[IndexedSeqLike].tail else slice(1, length)
129129

130130
override /*TraversableLike*/
131-
def last: A = if (length > 0) this(length - 1) else super.last
131+
def last: A = if (length > 0) this(length - 1) else super[IndexedSeqLike].last
132132

133133
override /*IterableLike*/
134-
def init: Repr = if (length > 0) slice(0, length - 1) else super.init
134+
def init: Repr = if (length > 0) slice(0, length - 1) else super[IndexedSeqLike].init
135135

136136
override /*TraversableLike*/
137137
def take(n: Int): Repr = slice(0, n)
@@ -167,7 +167,7 @@ trait IndexedSeqOptimized[+A, +Repr] extends Any with IndexedSeqLike[A, Repr] {
167167
i == len
168168
}
169169
case _ =>
170-
super.sameElements(that)
170+
super[IndexedSeqLike].sameElements(that)
171171
}
172172

173173
override /*IterableLike*/
@@ -274,7 +274,7 @@ trait IndexedSeqOptimized[+A, +Repr] extends Any with IndexedSeqLike[A, Repr] {
274274
true
275275
}
276276
case _ =>
277-
super.endsWith(that)
277+
super[IndexedSeqLike].endsWith(that)
278278
}
279279
}
280280

0 commit comments

Comments
 (0)