Compiling Java 9 (take 2)

Stephan Herrmann stephan.herrmann at
Sun Jan 8 19:13:05 UTC 2017

Hi Alex,

On 01/07/2017 01:37 AM, Alex Buckley wrote:
> On 1/4/2017 12:44 PM, Stephan Herrmann wrote:
> Yes, and I owe you some apologies for this. The "visibility of a declaration" is an obscure corner of the scope rules, and I intend
> to rename it. Visibility will unambiguously be "of a compilation unit", since that's key to how the Java Language supports the JPMS.

OK, I'll forget about visibility of declarations for now.

>> 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)?
> Per 7.3: "The ordinary compilation units that are visible to M drive the packages that are visible to M (§7.4.3), which in turn
> drives the top level packages in scope for code in compilation units associated with M (§6.3)."

Mentioning of top level packages here and in related locations makes me wonder,
whether package nesting (sub packages) is becoming semantically relevant?
I'm even under the impression, that unnecessarily (?) defining a relation
between packages and their sub-packages leads to the unfortunate hand-waving
in 7.4.3 to discriminate "'really' observable" from "'technically' observable".

Am I missing any of the intended semantics if I consider all packages as unrelated
to each other, no matter whether or not the qualified name of one package is the prefix
of the qualified name of another?

> That is, the effect of 'requires' statements is to move from a flat space of observable compilation units to a module-keyed space of
> visible compilation units. If a compiler is compiling module M that requires N, and module O that requires P, then the compiler has
> decided that compilation units in M and N and O and P are observable, but when processing a compilation unit in (say) M, only the
> compilation units in N (and not O or P) are visible.


>> As I already mentioned overloading, here's my example of the day:
>> //--- Base/
>> module Base {
>>      exports base;
>> }
>> //--- Base/other/
>> package other;
>> /** I'm not exported */
>> public class Other {
>>      public void test() {
>>          System.out.println("Other.test");
>>      }
>> }
>> //--- Base/base/
>> 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 Test {
>>      requires Base;
>> }
>> //--- Test/other/
>> package other;
>> public class Other {
>> }
>> //--- Test/test/
>> 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 at 9-ea/
>>          at
>> java.lang.reflect.Layer.checkBootModulesForDuplicatePkgs(java.base at 9-ea/
>>          at
>> java.lang.reflect.Layer.defineModules(java.base at 9-ea/
>>          at
>> java.lang.reflect.Layer.defineModules(java.base at 9-ea/
>>          at
>> jdk.internal.module.ModuleBootstrap.boot(java.base at 9-ea/
>>          at java.lang.System.initPhase2(java.base at 9-ea/
>> 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?
> How did you invoke javac? Which mode of javac (see "Compile time" in JEP 261) are you in?

I just re-tested: compilation in single-module mode and multi-module mode seem to have
the same effect. I'm *not* using legacy mode.

Let me use the example to try validate my understanding of the concepts involved:

Module Test reads module Base, so when compiling module Test all mentioned
compilation units are observable and visible.

It's just that Base/other.Other is not accessible from any code in module Test, right?

 > [...]
>> 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.
> It sounds like you're compiling in multi-module mode, where javac does indeed know this.

I cannot observe any difference between single-module and multi-module modes.
(Which makes sense to me)

>> 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.
> Per 7.3, javac is associating a first other.Other type with module Base, and a second other.Other type with module Test. The first
> and second types have no relationship to each other, so there are no conversions between them, so Base's test(other.Other) method is
> not applicable to the invocation b.test(other).

I read JLS as associating the *compilation units* with their respective module.
But then two different compilation units declare types of the same qualified name,
which is an illegal name clash. What am I missing?

> It's true that 7.3 also says that all the compilation units of Base are visible to Other, since Other reads Base. javac is
> effectively hiding Base's other.Other type from code in Other, since Other already has an other.Other type. It's possible we need to
> specify this explicitly.

Well, if you don't specify this, it's not part of the language, right?

I'm curious where in the spec this will be integrated.
Will JLS9 enhance the concept of qualified names for types to include the module?
Or, which concept is it that regulates precedence between same-named types?
- Shadowing focuses on simple names, but we need s.t. that is independent of the kind of name used.
- Hiding focuses on members that would otherwise be inherited, but we have no inheritance in the picture here.
- Obscuring speaks of different kinds of elements, but here we speak of two types.
If Other/other.Other is visible to module Test it seems we need yet another term
for disambiguation between same-named types from different modules.

OTOH, visibility of this type seems to be crucial for overload resolution
if there's a sub class of Base/other.Other that is accessible from Test.
But: do you *want* Test to be able to invoke Base.test(Other) despite not
having access to the parameter type??

I guess further questions have to wait until we have a specification
of this new kind of "hiding", as you call it.

Lastly, with the interplay of things defined in JLS vs. observability
which is subject to the host system, I wonder if JLS will at least include
"recommendations" regarding the structure of directories and compilation
units. The same holds for "association of a compilation unit to a module".
Otherwise, I'm afraid that different compilers will not be able to compile
projects developed with another compiler simply due to incompatible
directory layouts. Cf. the notion "the filename of the compilation unit is
typically", which is still *very* weak, but *much* better
than nothing!


More information about the jigsaw-dev mailing list