Skip to content

Commit 42fb202

Browse files
authored
Drop incorrect super accessor in trait subclass (#17062)
2 parents 34f8362 + f8fb548 commit 42fb202

File tree

10 files changed

+102
-21
lines changed

10 files changed

+102
-21
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/SuperAccessors.scala

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -174,27 +174,30 @@ 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 =
185178
ProtectedAccessors.needsAccessorIfNotInSubclass(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-
*/
197-
superAccessorCall(sel)
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+
* That's because the protected method is actually public,
189+
* so we can call it. For truly protected methods, like from
190+
* Java, we error instead of emitting the wrong code (i17021.ext-java).
191+
*
192+
* Otherwise, we need to go through an accessor,
193+
* which the implementing class will provide an implementation for.
194+
*/
195+
if ctx.owner.enclosingClass.derivesFrom(sym.owner) then
196+
if sym.is(JavaDefined) then
197+
report.error(em"${ctx.owner} accesses protected $sym inside a concrete trait method: use super.${sel.name} instead", sel.srcPos)
198+
sel
199+
else
200+
superAccessorCall(sel)
198201
case Super(_, mix) =>
199202
transformSuperSelect(sel)
200203
case _ =>

compiler/test/dotty/tools/vulpix/ParallelTesting.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -751,8 +751,11 @@ trait ParallelTesting extends RunnerOrchestration { self =>
751751
case _ =>
752752
}
753753
case Failure(output) =>
754-
echo(s"Test '${testSource.title}' failed with output:")
755-
echo(output)
754+
if output == "" then
755+
echo(s"Test '${testSource.title}' failed with no output")
756+
else
757+
echo(s"Test '${testSource.title}' failed with output:")
758+
echo(output)
756759
failTestSource(testSource)
757760
case Timeout =>
758761
echo("failed because test " + testSource.title + " timed out")

tests/neg/i17021.ext-java/A.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Derives from run/i17021.defs, but with a Java protected member
2+
package p1;
3+
4+
public class A {
5+
protected int foo() { return 1; }
6+
}

tests/neg/i17021.ext-java/Test.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Derives from run/i17021.defs
2+
// but with a Java protected member
3+
// which leads to a compile error
4+
package p2:
5+
trait B extends p1.A:
6+
def bar: Int = foo // error: method bar accesses protected method foo inside a concrete trait method: use super.foo instead
7+
8+
class C extends B:
9+
override def foo: Int = 2
10+
11+
object Test:
12+
def main(args: Array[String]): Unit =
13+
val n = new p2.C().bar
14+
assert(n == 2, n)

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.ext-java/A.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Derives from run/i17021.defs, but with a Java protected member
2+
package p1;
3+
4+
public class A {
5+
protected int foo() { return 1; }
6+
}

tests/run/i17021.ext-java/Test.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// scalajs: --skip
2+
// Derives from run/i17021.defs
3+
// but with a Java protected member
4+
// and fixed calling code, that uses super
5+
package p2:
6+
trait B extends p1.A:
7+
def bar: Int = super.foo
8+
9+
class C extends B:
10+
override def foo: Int = 2
11+
12+
object Test:
13+
def main(args: Array[String]): Unit =
14+
val n = new p2.C().bar
15+
assert(n == 1, n)

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)