[sealed] Module & package constraints

Remi Forax forax at univ-mlv.fr
Thu Apr 23 20:01:49 UTC 2020

----- Mail original -----
> De: "daniel smith" <daniel.smith at oracle.com>
> À: "amber-spec-experts" <amber-spec-experts at openjdk.java.net>
> Envoyé: Jeudi 23 Avril 2020 20:27:30
> Objet: [sealed] Module & package constraints

> I'm scrutinizing this rule from the sealed types language spec (8.1.6):
> "It is a compile-time error if any class named in a permits clause of a sealed
> class declaration C is not a member of the same module as C. If the sealed
> class C is a member of the unnamed module, then it is a compile-time error if
> any class named in the permits clause of the declaration of C is not in the
> same package as C."
> I'm wondering what run-time checks should correspond to this rule (prompted by
> the "runtime checking of PermittedSubtypes" thread), and whether we want to say
> something slightly different.
> Here are some knobs. Where do we want to turn them?
> 1) Is it legal to have a permitted subclass/subinterface that does not actually
> extend the permitting class or interface?
> In past discussions about compilation, I feel like we've leaned towards "no",
> but I don't see a corresponding rule. We're definitely not interested in
> scenarios involving separate compilation; so if the child and parent disagree,
> we're compiling an inconsistent set of classes. Seems reasonable to ask the
> programmer to fix it. If we don't, we end up with downstream design issues like
> whether to trust the 'permits' clause when defining exhaustiveness.
> At run time, it's convenient for the JVM if the answer is "yes". It's expensive
> to try to validate a PermittedSubtypes attribute all at once (could require
> O(nm) class loads, n=sealed hierarchy height, m=branching factor; although both
> are typically small). Instead, the best time to validate is when another
> class/interface attempts to extend the sealed class/interface.

The answer is yes, until you load the subtype and discover that it's not a subtype,
it's like the rules for nestmates.

> 2) What are the constraints on module membership?
> At compilation time, the only way to extend across a module boundary is if the
> parent permits the child, but the child doesn't reciprocate. (If the child
> attempts to reciprocate, it won't be able to access the parent class, or
> there's an illegal module circularity.) So depending on the choice for (1), a
> restriction on modules may be redundant.
> At run time, when we validate an 'extends'/'implements' clause, the mutual
> references *almost* imply membership in the same run-time module, with one
> exception: unnamed modules can have circular references. For example: class Foo
> extends sealed class Bar, Foo belongs to the unnamed module of loader L1, Bar
> belongs to the unnamed module of loader L2. The loaders can "see" each other,
> and all class references resolve safely. I'm not sure how likely this scenario
> is, but it could be a major problem for a program if the JVM starts blowing up
> after someone makes Bar sealed. (On the other hand, it's really helpful for the
> JVM if it can require the classes to have to same loader.)

yes, that the missing rule, sealed classes has to have the same class loader.
So if there is no module, there are still in the same unnamed module at runtime.

> 3) What are the constraints on package membership?
> The use case here is a class/interface extending a sealed class/interface, both
> in the same unnamed module.
> At compile time, if the child and parent must successfully refer to each other,
> then we've already guaranteed that they're compiled at the same time. Is there
> something more to be gained from forcing them into the same package?
> (java.lang.reflect.Type is an example of a potential sealed interface that
> couldn't be declared under this rule if it didn't belong to a named module,
> because java.lang.Class is in a different package. I imagine lots of real code
> bases will have relationships like this. Then again, it's not totally
> unreasonable to tell these code bases they need to declare a module if they
> want cross-package sealed types.)
> At run time, it's fairly straightforward to check for the same package name when
> we validate a subclass. But it's also doesn't benefit the JVM in any way, so
> maybe this is more of a language-specific restriction that should be ignored by
> the JVM. (E.g., maybe Kotlin doesn't mind compiling sealed hierarchies across
> different packages in the unnamed module, even if Java won't do it.)

Very good question.

The compiler verifying that the classes are in the same package if there is no module is a sanity check, if there are not in the same package, they may be in different jars.
It's not fool proof because you can have split packages at runtime but it's good enough because at least you have to have all classes at compile time.
So it may not benefit the JVM but benefit the poor soul trying to debug the code at runtime.

>From the POV on any languages running on the JVM like Scala or Kotlin that already have the concept of sealed types, there are free to map it to the JVM concept or not, so if they want to support a case which is not supported by the JVM spec, the simplest solution is just to not generated the PermittedSubtypes attributes.

So I don't see the need to have the Java spec and the JVM spec to diverge on that specific point.


More information about the amber-spec-experts mailing list