Add @static SIP by DarkDimius · Pull Request #491 · scala/docs.scala-lang (original) (raw)

I'd like to see the "Compilation scheme" section fleshed out. Currently, it talks about how this would fit into the compiler pipeline, but lacks details about how things would be compiled to bytecode.

Here's an example I'm interested in:

package p1; object C { @static val foo: AnyRef = C; val bar: AnyRef = C }

If I translate this according to the intuitive description herein, taking a few guesses at the details, I start with:

public class Test { public static void main(String[] args) { System.out.println(C$.MODULE$.foo()); // null } }

class C { public static final Object foo; static { foo = C$.MODULE$; // initializer moved to CompanionClass. } } final class C$ { public static C$ MODULE$; private final Object bar; public static Object foo() { return C.foo; } static { Object x = C.foo; // or UNSAFE.ensureClassInitialized(classOf[C]) new C$(); } private C$() { super(); MODULE$ = this; bar = this; } }

Running this prints null. I guess we should move the call to C.<init> after the super constructor call and the assignment of the module var:

final class C$ { public static C$ MODULE$; private final Object bar; public static Object foo() { return C.foo; } static { new C$(); } private C$() { super(); MODULE$ = this; Object x = C.foo; // or UNSAFE.ensureClassInitialized(classOf[C]) bar = this; } }

Which avoids the NPE.

But in that case, triggering C.<clinit> before C$.<clinit> would reverse the order that the initalizer for foo and the super constructor of the module class is run.

Both of these variations have another drawback: References to C in the RHS of static initializers introduce a cyclic dependency between C.<clinit> and C$.<clinit>. This is deadlock prone if a a pair of threads simultaneously statically initialize the class and module class.

So maybe we instead should put leave all the initalization code in the module class?

class C { public static Object foo; // can't be final } final class C$ { public static C$ MODULE$; private final Object bar; public static Object foo() { return C.foo; } static { new C$(); } private C$() { super(); MODULE$ = this; C.foo = this; bar = this; } }

This looks more promising for thread-safe initialization, and preserving semantics. But we are no unable to declare foo as final, which is a bit of a drag, because one reason you want to make a field static is to hold a method handle "constant", and hotspot only is able to fully inline invocations through that handle if the field is marked final. (Or, if it has the JDK internal annotation, java.lang.invoke.Stable, which isn't in our box of tricks.)