Skip to content

Conversation

@tanishiking
Copy link
Member

@tanishiking tanishiking commented Dec 2, 2025

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:

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.

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.

Fixes scala#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 scala@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.
@tanishiking tanishiking changed the title [WIP] Fix isJvmAccessible returns true if the protected class is accessible Fix isJvmAccessible to handle protected Java classes 24507 Dec 3, 2025
@tanishiking tanishiking changed the title Fix isJvmAccessible to handle protected Java classes 24507 Fix isJvmAccessible to handle nested protected Java classes 24507 Dec 3, 2025
@tanishiking tanishiking changed the title Fix isJvmAccessible to handle nested protected Java classes 24507 Fix isJvmAccessible to handle nested protected Java classes Dec 3, 2025
@tanishiking tanishiking marked this pull request as ready for review December 3, 2025 08:24
@tanishiking
Copy link
Member Author

tanishiking commented Dec 3, 2025

@lrytz Do you mind taking a look? 🙏

@tanishiking tanishiking requested a review from lrytz December 3, 2025 08:44
@lrytz
Copy link
Member

lrytz commented Dec 3, 2025

LGTM, thank you! But could you add the test from scala/scala#6023?

@lrytz
Copy link
Member

lrytz commented Dec 3, 2025

Oh, also I think it would be better to make i24507 a run test.

@tanishiking
Copy link
Member Author

Thanks @lrytz! Made i24507 run test 2a35ded
And, the test in scala/scala#6023 already ported to https://github.com/scala/scala3/tree/2fc524a43dca6f72e43a98cd11cb60570b1f163e/tests/run/java-package-protected as tests/run/java-package-protected by #21362 👍

Copy link
Member

@lrytz lrytz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@tanishiking tanishiking enabled auto-merge December 3, 2025 13:46
@tanishiking tanishiking merged commit 93e58da into scala:main Dec 3, 2025
46 checks passed
@tanishiking tanishiking deleted the i24507 branch December 4, 2025 05:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Regression: Unable to emit reference to constructor ... class is not accessible

2 participants