Skip to content

Commit 93e58da

Browse files
authored
Fix isJvmAccessible to handle nested protected Java classes (#24625)
Fixes #24507 The previous implementation of `isJvmAccessible` failed to recognize protected Java nested classes as accessible. And it causes compilation errors when extending them from subclasses. Previous implementation: ```scala def isJvmAccessible(cls: Symbol): Boolean = !cls.is(Flags.JavaDefined) || { val boundary = cls.accessBoundary(cls.owner)(using preErasureCtx) (boundary eq defn.RootClass) || (ctx.owner.enclosingPackageClass eq boundary) } ``` For protected nested classes like B below, the access boundary is the enclosing class (`A` in this case), not a `package a` or root. However, `B` should be accessible through a public class `A`. ```java package a; public class A { protected class B {} } ``` This commit replace the manual boundary checks with `cls.isAccessibleFrom`, which properly handles all Java access modifiers. This aligns with the Scala 2 implementation which uses the equivalent `context.isAccessible(cls, cls.owner.thisType)`. https://github.com/scala/scala/blob/efb71845113639bd1da231c917e18af33519fc07/src/compiler/scala/tools/nsc/transform/Erasure.scala#L1451-L1455 For example, - `cls` will be `protected class B` - `cls.owner.thisType` will be `public class A` - And, `cls.isAccessibleFrom(cls.owner.thisType)` is true. On the other hand, `isJvmAccessible` returns false for Java-defined package protected class case like `java-package-protected` case. https://github.com/tanishiking/scala3/tree/b2850be4c79996d8ebd9afaf36ed12f46febb0d1/tests/run/java-package-protected In this case, - `cls` = `class B` (package protected under `a`) - `cls.owner.thisType` = `package a` - And, `cls.isAccessibleFrom(cls.owner.thisType)` is false. because `B` is package protected and, we cannot access B through `a.B` from another package. In this case, cast to `A` happens. --- Note that, this regression starts from e7d479f but the true commit is d3ce551 but the reason why we stopped using `isAccessibleFrom` seems to be an optimization and the `tests/pos/trait-access` still compiles with this change.
2 parents 8067622 + 2a35ded commit 93e58da

File tree

5 files changed

+26
-7
lines changed

5 files changed

+26
-7
lines changed

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

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -748,13 +748,8 @@ object Erasure {
748748
// Scala classes are always emitted as public, unless the
749749
// `private` modifier is used, but a non-private class can never
750750
// extend a private class, so such a class will never be a cast target.
751-
!cls.is(Flags.JavaDefined) || {
752-
// We can't rely on `isContainedWith` here because packages are
753-
// not nested from the JVM point of view.
754-
val boundary = cls.accessBoundary(cls.owner)(using preErasureCtx)
755-
(boundary eq defn.RootClass) ||
756-
(ctx.owner.enclosingPackageClass eq boundary)
757-
}
751+
!cls.is(Flags.JavaDefined) ||
752+
cls.isAccessibleFrom(cls.owner.thisType)(using preErasureCtx)
758753

759754
@tailrec
760755
def recur(qual: Tree): Tree =

tests/pos/i24507-jframe.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class Test extends javax.swing.JFrame { class Acc extends AccessibleJFrame }
2+

tests/run/i24507.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
foo

tests/run/i24507/A.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package a;
2+
3+
public class A {
4+
protected class B {
5+
public String foo() {
6+
return "foo";
7+
}
8+
}
9+
}

tests/run/i24507/test.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// scalajs: --skip
2+
import a._
3+
4+
class T extends A {
5+
class Acc extends B
6+
val acc = new Acc
7+
def foo(): String = acc.foo()
8+
}
9+
10+
@main def Test =
11+
val t = new T
12+
println(t.foo())

0 commit comments

Comments
 (0)