Skip to content

Commit 3cb01d6

Browse files
committed
Drop incorrect super accessor in trait subclass
When a trait extends a class and accesses a protected member (while in a different package), we would emitted a super accessor and call that instead. That breaks the semantics because if the protected member is overridden, that implementation won't be called, as the call is to the super method instead. In addition to that, the call to the super accessor was causing a false positive error as we don't allow super calls to bind to vals.
1 parent f92d6f1 commit 3cb01d6

File tree

6 files changed

+50
-28
lines changed

6 files changed

+50
-28
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ abstract class AccessProxies {
7171
def needsAccessor(sym: Symbol)(using Context): Boolean
7272

7373
def ifNoHost(reference: RefTree)(using Context): Tree = {
74-
assert(false, "no host found for $reference with ${reference.symbol.showLocated} from ${ctx.owner}")
74+
assert(false, i"no host found for $reference with ${reference.symbol.showLocated} from ${ctx.owner}")
7575
reference
7676
}
7777

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

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,11 @@ object ProtectedAccessors {
3333
ctx.owner.isContainedIn(boundary) || ctx.owner.isContainedIn(boundary.linkedClass)
3434
}
3535

36-
/** Do we need a protected accessor if the current context's owner
37-
* is not in a subclass or subtrait of `sym`?
38-
*/
39-
def needsAccessorIfNotInSubclass(sym: Symbol)(using Context): Boolean =
40-
sym.isTerm && sym.is(Protected) &&
41-
!sym.owner.is(Trait) && // trait methods need to be handled specially, are currently always public
42-
!insideBoundaryOf(sym)
43-
4436
/** Do we need a protected accessor for accessing sym from the current context's owner? */
4537
def needsAccessor(sym: Symbol)(using Context): Boolean =
46-
needsAccessorIfNotInSubclass(sym) &&
38+
sym.isTerm && sym.is(Protected) &&
39+
!sym.owner.is(Trait) && // trait methods need to be handled specially, are currently always public
40+
!insideBoundaryOf(sym) &&
4741
!ctx.owner.enclosingClass.derivesFrom(sym.owner)
4842
}
4943

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

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -174,26 +174,20 @@ class SuperAccessors(thisPhase: DenotTransformer) {
174174
val sel @ Select(qual, name) = tree: @unchecked
175175
val sym = sel.symbol
176176

177-
/** If an accesses to protected member of a class comes from a trait,
178-
* or would need a protected accessor placed in a trait, we cannot
179-
* perform the access to the protected member directly since jvm access
180-
* restrictions require the call site to be in an actual subclass and
181-
* traits don't count as subclasses in this respect. In this case
182-
* we generate a super accessor instead. See SI-2296.
183-
*/
184177
def needsSuperAccessor =
185-
ProtectedAccessors.needsAccessorIfNotInSubclass(sym) &&
178+
ProtectedAccessors.needsAccessor(sym) &&
186179
AccessProxies.hostForAccessorOf(sym).is(Trait)
187180
qual match {
188181
case _: This if needsSuperAccessor =>
189-
/*
190-
* A trait which extends a class and accesses a protected member
191-
* of that class cannot implement the necessary accessor method
192-
* because jvm access restrictions require the call site to be in
193-
* an actual subclass and traits don't count as subclasses in this
194-
* respect. We generate a super accessor itself, which will be fixed
195-
* by the implementing class. See SI-2296.
196-
*/
182+
/* Given a protected member m defined in class C,
183+
* and a trait T that calls m.
184+
*
185+
* If T extends C, then we can access it by casting
186+
* the qualifier of the select to C.
187+
*
188+
* Otherwise, we need to go through an accessor,
189+
* which the implementing class will provide an implementation for.
190+
*/
197191
superAccessorCall(sel)
198192
case Super(_, mix) =>
199193
transformSuperSelect(sel)

tests/neg/i11170a.scala renamed to tests/pos/i11170a.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ package cpackage {
2323
import apackage._
2424
import bpackage._
2525

26-
case class C(override protected val x: Int) extends A with B // error
26+
case class C(override protected val x: Int) extends A with B
2727
case class C2(override val x: Int) extends A2 with B2
28-
}
28+
}

tests/run/i17021.defs.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Derives from run/i17021, but with defs instead of vals
2+
package p1:
3+
class A:
4+
protected def foo: Int = 1
5+
6+
package p2:
7+
trait B extends p1.A:
8+
def bar: Int = foo
9+
10+
class C extends B:
11+
override def foo: Int = 2
12+
13+
object Test:
14+
def main(args: Array[String]): Unit =
15+
val n = new p2.C().bar
16+
assert(n == 2, n) // was: assertion failed: 1

tests/run/i17021.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package p1:
2+
class A:
3+
protected val foo: Int = 1
4+
5+
package p2:
6+
trait B extends p1.A:
7+
def bar: Int = foo
8+
9+
class C extends B: // was: error: parent trait B has a super call which binds to the value p1.A.foo. Super calls can only target methods.
10+
override val foo: Int = 2
11+
12+
// Also, assert that the access continues to delegate:
13+
// i.e. B#bar delegates to this.foo and so C#bar returns 2,
14+
// not B#bar delegates to super.foo and so C#bar returns 1.
15+
object Test:
16+
def main(args: Array[String]): Unit =
17+
val n = new p2.C().bar
18+
assert(n == 2, n) // was: assertion failed: 1

0 commit comments

Comments
 (0)