Compiling Java 9 (take 2)
Stephan Herrmann
stephan.herrmann at berlin.de
Wed Jan 4 20:44:13 UTC 2017
I have started to dig through the JLS changes (2016-12-16) to figure out requirements for a Java 9 compiler without consulting
javac, but I'm having some difficulties understanding the interplay of scope, visibility, importing, accessibility and observability
at this level.
For instance, this already starts when re-reading unchanged parts from 6.3 and 6.4.1:
6.3:
The scope of a declaration is the region of the program within which the entity declared by the declaration can be referred to
using a simple name, provided it is visible (§6.4.1).
6.4.1:
A declaration d is said to be visible at point p in a program if the scope of d includes p, and d is not shadowed by any other
declaration at p.
=> For a declaration to be in scope it must be visible, and for being visible it must be in scope. This is beyond me.
Is it correct (and intended) that "visibility of a declaration" (6.3) is a different concept than "visibility of a compilation unit"
(7.3)? (Are we so entangled with overloading that we need to overload the terms in our spec, too? :) ).
I'm also surprised that some requirements are indeed specified with regard to compilation units. Modules export packages, and types
are declared in packages. Isn't it possible to define requirements due to JPMS solely in those terms (modules, packages, types), and
avoid to mention compilation units in these rules? What do compilation units add conceptually to JPMS (besides adding complexity)?
As I already mentioned overloading, here's my example of the day:
//--- Base/module-info.java
module Base {
exports base;
}
//--- Base/other/Other.java
package other;
/** I'm not exported */
public class Other {
public void test() {
System.out.println("Other.test");
}
}
//--- Base/base/Base.java
package base;
import other.Other;
/** I'm exported */
public class Base {
public void test(Object other) {
System.out.println("Object");
}
public void test(Other other) {
System.out.println("Other");
other.test();
}
}
//---
//--- Test/module-info.java
module Test {
requires Base;
}
//--- Test/other/Other.java
package other;
public class Other {
}
//--- Test/test/Test.java
package test;
import base.Base;
import other.Other;
public class Test {
void test(Base b, Other other, Object o) {
b.test(o); // OK
b.test(other); // ??
}
public static void main(String[] args) {
new Test().test(new Base(), new Other(), new Object());
}
}
//---
Firstly, I'm surprised that javac (ea-149) compiles this fine but trying to run the program under JPMS crashes with
Error occurred during initialization of VM
java.lang.reflect.LayerInstantiationException: Package other in both module Base and module Test
at java.lang.reflect.Layer.fail(java.base at 9-ea/Layer.java:716)
at java.lang.reflect.Layer.checkBootModulesForDuplicatePkgs(java.base at 9-ea/Layer.java:674)
at java.lang.reflect.Layer.defineModules(java.base at 9-ea/Layer.java:610)
at java.lang.reflect.Layer.defineModules(java.base at 9-ea/Layer.java:383)
at jdk.internal.module.ModuleBootstrap.boot(java.base at 9-ea/ModuleBootstrap.java:307)
at java.lang.System.initPhase2(java.base at 9-ea/System.java:1927)
Is that because the compiler knows nothing about layers?
Is there any tool that statically checks whether a given modular program can possibly run in the default Layer implementation?
Secondly, I'm confused how overload resolution for b.test(..) should happen.
We have two candidates, where one parameter type is accessible (Object) and the other is not (other.Other).
In all of JLS chapter 15 (2016-12-16) I cannot find anything that would allow the compiler to consider accessibility of parameter
types during overload resolution.
Hence, I see two possible answers by a compiler:
(1) resolve b.test(other) to test(Other) because both types other.Other appear to be the same type, and hence test(Other) is
applicable and more specific than test(Object).
(2) reject the program because of a name clash on other.Other.
Javac (ea-149) does neither, but resolves b.test(other) to test(Object).
Apparently, javac "knows" that Base/other.Other and Test/other.Other are distinct types. Given that types are identified by
qualified names that do not contain the module name, this distinction doesn't seem to be possible per JLS.
Or javac marks the method test(Other) as inaccessible, but JLS determines accessibility of a method only using the method's modifiers.
So, what JLS rules lead javac to selecting test(Object)?
thanks,
Stephan
More information about the jigsaw-dev
mailing list