Refinements for sealed types

Brian Goetz brian.goetz at
Thu Aug 15 17:38:23 UTC 2019

As Gavin has worked through the spec for sealed types, it has shone a 
spotlight on one of the messy parts -- implicitly sealed types.  I've 
got an alternate way to stack this that I think is simpler and still 
meets the goals.

First, the goals (which in most cases align, but in some cases conflict 
with each other):

Unsealed concrete leaves should not be the default.  If we follow the 
default strategy that a class declaration gets only the flags that are 
declared explicitly (which is generally a good default), it is quite 
likely that many subtypes of sealed types will be extensible, when that 
was not the intent or understanding of the author.  For example, in a 
hierarchy like the following:

     sealed interface X permits A, B { }
     class A implements X { }
     class B implements X { }

it is highly likely to be a source of mistakes that A and B are neither 
sealed nor final, even though they are co-declared with the sealed 
interface.  The author may well assume that they are in control of all 
the implementations of X methods, when in fact anyone can subclass A and 
override those methods.

Avoiding excessive ceremony.  If the user had to declare every concrete 
subtype as final, this may well be seen as excess ceremony.  (This is 
the same reason we allow the permits clause to be inferred.)

In the current design, we took the following path:

  - Subtypes of sealed types are implicitly sealed, unless marked 
  - We infer a permits clause when it is omitted, which is possibly 
empty (in which case the type is effectively final.)

These are reasonable defaults, both from avoiding the safety question of 
accidental extensibility, and reducing ceremony, but they interact in 
some uncomfortable ways.  For example, under these rules, its possible 
to have an explicit "permits" clause without saying "sealed", which is a 
little strange.  And it is possible to infer both sealing and the 
permits list, which might exceed our nontransparency comfort level.

So, let me propose a simplification:

  - A concrete subtype A of a sealed type X, which has no permits 
clause, no known subtypes, and is not marked non-sealed, is implicitly 
sealed (with an empty permits clause).
  - Any other subtype of a sealed type must either have a "sealed" 
modifier, or a "non-sealed" modifier.
  - Any type with a permits list must have a sealed modifier.

Rationale: This still manages to avoid the "accidental extension" 
problem, and addresses both the extension and the ceremony problem where 
they are both most severe -- at the leaves.  Abstract types (which are 
generally fewer in number than leaves) err on the side of explicitness.

So the above hierarchy could be written as above, and A/B would be 
implicitly final, as desired.  If A or B wanted to permit subtypes, they 
would need to be explicitly marked non-sealed (to reopen them), or 
explicitly sealed (with or without an explicit permits list.)

I think this stays within the spirit of the goals, but with a little 
less magic / complexity.

More information about the amber-spec-experts mailing list