Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Regression in playframework/playframework for access to private static class public members #21599

Open
WojciechMazur opened this issue Sep 16, 2024 · 4 comments
Labels
area:erasure compat:java itype:bug regression This worked in a previous version but doesn't anymore

Comments

@WojciechMazur
Copy link
Contributor

Based on Open CB failure in playframework/playframework - builds logs

Compiler version

3.6.0-RC1-bin-20240915-ad8c21a-NIGHTLY
Bisect points to e7d479f / #21362

Minimized code

import java.util.List;

public class RoutingDsl {

  final List<Route> routes = null;

  static class Method {}
  private static class Route {
    final Method actionMethod = null;
  }
}
@main def Test =
  val x: RoutingDsl = ???
  x.routes.forEach: route =>
    println(route.actionMethod) // error

Output

Compiling project (Scala 3.6.0-RC1-bin-20240915-ad8c21a-NIGHTLY, JVM (17))
[error] ./test.scala:4:13
[error] Unable to emit reference to value actionMethod in class Route, class Route is not accessible in the top-level definitions in package <empty>
[error]     println(route.actionMethod)
[error]             ^^^^^

Expectation

Should compile. This code does compile in Scala 3.5.0 and lower. It also compiles in Scala 2.12/2.13

@WojciechMazur WojciechMazur added itype:bug compat:java area:erasure regression This worked in a previous version but doesn't anymore labels Sep 16, 2024
@bracevac
Copy link
Contributor

bracevac commented Sep 16, 2024

IMHO, this is the correct behavior for private static access in Java. E.g., we'll get a similar error message with an equivalent Java client program:

public class Main {
    public static void main(String[] args) {
        RoutingDsl x = null;
        for(RoutingDsl.Route route : x.routes) {
                System.out.println(route.actionMethod);
        }
    }
}

which results in

Main.java:4: error: Route has private access in RoutingDsl
        for(RoutingDsl.Route route : x.routes) {
                      ^
1 error

So it looks like we got it wrong in Scala 3 until #21362 which is to say, that playframework would have to fix that on their end.

Edit: The following is closer to the Scala version, but my point remains the same:

public class Main {
    public static void main(String[] args) {
        RoutingDsl x = null;
        x.routes.forEach(route -> System.out.println(route.actionMethod));
    }
}

@lrytz
Copy link
Member

lrytz commented Sep 17, 2024

@bracevac I think one could argue both ways.

If the Route class is explicitly referenced in the Scala source, all Scala versions issue a "cannot be accessed" error.

However, in the example source code there's no reference to Route. The class is public in bytecode and the generated GETFIELD RoutingDsl$Route.actionMethod bytecode is valid. So the error message "Unable to emit reference to value actionMethod" is not really accurate.

I haven't looked in detail how Scala 2 makes the difference between the two cases, it could even be accidental / a bug.

To make an argument for your side, replacing RoutingDsl.Route route by var route in the equivalent Java program still causes an error in javac.

@dwijnand
Copy link
Member

The class is public in bytecode and the generated GETFIELD RoutingDsl$Route.actionMethod bytecode is valid.

I wonder why javac emits the class as public. So it can be instantiated by RoutingDsl?

@lrytz
Copy link
Member

lrytz commented Sep 17, 2024

class is public in bytecode

I was wrong here, the nested class has default access (package protected). Scala 2 is still correct, GETFIELD RoutingDsl$Route.actionMethod is OK within the same package. Moving the RoutingDsl into a different package, Scala 2 gives errors on compilation ("cannot emit reference").

Dale and me tried a simpler example:

// package p
public class A {
  public static B b() { return new B(); }
  private static class B {
    public String bar() { return "l"; }
  }
}
object T {
  def main(args: Array[String]): Unit = {
    println(/* p. */ A.b.bar)
  }
}

Again, no reference to the private B in the Scala file, but this time Scala 2 doesn't compile it ("Unable to emit reference"). So Scala 2 is inconsistent.

Scala 3.5 compiles the example even when moving A.java to a different package, which is clearly a bug, things then fail at runtime (IllegalAccessError).

Overall, I htink enforcing access as current main does is fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:erasure compat:java itype:bug regression This worked in a previous version but doesn't anymore
Projects
None yet
Development

No branches or pull requests

4 participants