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.
OK
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@9-ea/Layer.java:716)
at
java.lang.reflect.Layer.checkBootModulesForDuplicatePkgs(java.base@9-ea/Layer.java:674)
at
java.lang.reflect.Layer.defineModules(java.base@9-ea/Layer.java:610)
at
java.lang.reflect.Layer.defineModules(java.base@9-ea/Layer.java:383)
at
jdk.internal.module.ModuleBootstrap.boot(java.base@9-ea/ModuleBootstrap.java:307)
at java.lang.System.initPhase2(java.base@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?
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 module-info.java", which is still *very* weak, but *much* better
than nothing!
best,
Stephan