Skip to content

Commit 737ae96

Browse files
committed
Avoid generation of protected accessors in binary API
1 parent ea1b21a commit 737ae96

File tree

3 files changed

+79
-1
lines changed

3 files changed

+79
-1
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ object ProtectedAccessors {
3737
* is not in a subclass or subtrait of `sym`?
3838
*/
3939
def needsAccessorIfNotInSubclass(sym: Symbol)(using Context): Boolean =
40-
sym.isTerm && sym.is(Protected) &&
40+
sym.isTerm && sym.is(Protected) && !sym.hasPublicInBinary &&
4141
!sym.owner.is(Trait) && // trait methods need to be handled specially, are currently always public
4242
!insideBoundaryOf(sym)
4343

compiler/test/dotty/tools/backend/jvm/PublicInBinaryTests.scala

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,4 +345,44 @@ class PublicInBinaryTests extends DottyBytecodeTest {
345345
Invoke(INVOKEVIRTUAL, "foo/D$", "bazImpl", "()V", false)))
346346
}
347347
}
348+
349+
@Test
350+
def noProtectedAccessorsForBinaryInPublic(): Unit = {
351+
val code =
352+
"""import scala.annotation.publicInBinary
353+
|package p {
354+
| class A {
355+
| protected def a(): Int = 1
356+
| @publicInBinary protected def b(): Int = 1
357+
| }
358+
|}
359+
|package q {
360+
| class B extends p.A {
361+
| trait BInner {
362+
| def test1() = a() // protected accessor for `a`
363+
| def test2() = b() // no protected accessor for `b`
364+
| }
365+
| }
366+
|}
367+
""".stripMargin
368+
checkBCode(code) { dir =>
369+
val bClass = loadClassNode(dir.subdirectoryNamed("q").lookupName("B.class", directory = false).input, skipDebugInfo = false)
370+
assert(bClass.methods.asScala.exists(_.name == "protected$a"))
371+
assert(bClass.methods.asScala.forall(_.name != "protected$b"))
372+
373+
val bInnerClass = loadClassNode(dir.subdirectoryNamed("q").lookupName("B$BInner.class", directory = false).input, skipDebugInfo = false)
374+
375+
val test1Method = getMethod(bInnerClass, "test1")
376+
val test1Instructions = instructionsFromMethod(test1Method).filter(_.isInstanceOf[Invoke])
377+
assertSameCode(test1Instructions, List(
378+
Invoke(INVOKEINTERFACE, "q/B$BInner", "q$B$BInner$$$outer", "()Lq/B;", true),
379+
Invoke(INVOKEVIRTUAL, "q/B", "protected$a", "()I", false)))
380+
381+
val test2Method = getMethod(bInnerClass, "test2")
382+
val test2Instructions = instructionsFromMethod(test2Method).filter(_.isInstanceOf[Invoke])
383+
assertSameCode(test2Instructions, List(
384+
Invoke(INVOKEINTERFACE, "q/B$BInner", "q$B$BInner$$$outer", "()Lq/B;", true),
385+
Invoke(INVOKEVIRTUAL, "q/B", "b", "()I", false) ))
386+
}
387+
}
348388
}

tests/run/noProtectedSuper.scala

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import scala.annotation.publicInBinary
2+
3+
package p {
4+
class A {
5+
@publicInBinary protected def foo(): Int = 1
6+
@publicInBinary protected def fuzz(): Int = 2
7+
}
8+
}
9+
package q {
10+
class B extends p.A { // protected accessor for foo
11+
trait BInner {
12+
def bar() = foo()
13+
}
14+
}
15+
trait Inner extends p.A {
16+
def bar() = foo() // shared super accessor for foo
17+
// new super accessor for fuzz
18+
class InnerInner {
19+
def bar() = foo()
20+
def baz() = fuzz()
21+
}
22+
}
23+
}
24+
object Test extends App {
25+
val b = new q.B
26+
val bi = new b.BInner {}
27+
assert(bi.bar() == 1)
28+
29+
class C extends p.A with q.Inner {
30+
// implements super accessors for foo and fuzz
31+
}
32+
val c = new C
33+
assert(c.bar() == 1)
34+
35+
val d = new c.InnerInner
36+
assert(d.bar() == 1)
37+
assert(d.baz() == 2)
38+
}

0 commit comments

Comments
 (0)