Draft JLS spec for records

Alex Buckley alex.buckley at oracle.com
Tue Sep 3 21:00:50 UTC 2019

Let me comment on Maurizio's comments, and add some more.

In 1.1, the spec for enum types has been used as a template, which is 
fine, but the summary of enum types has always been poor: the 
singular/plural construction is weird, and the mixing of kinds (class v. 
object) is horrible. The JLS is not the place to sling the term "enum" 
and sometimes have it mean the type and something the instance of the 
type; similarly, for "record". Notice that 8.9 never says "enum" on its 
own. Still, since people talk about "enums" and "records" a lot, the JLS 
should claim those terms -- as the types. For the avoidance of doubt, 
it's fine for this new draft spec to clarify/align the specification of 
older features. So:

   Enums are a special kind of class that support the definition of 
small sets of values which can then be used in a type safe manner. 
Unlike enumerations in other languages, enums may have their own methods.

   Records are a special kind of class that support the compact 
expression of simple objects that serve as aggregates of values.

The intro to ch.8 is the place to say more about enums and records. It 
is unfortunate that enums aren't covered there, e.g., the only way you 
learn that you can switch over an enum type is via an example buried in 
8.9. For records, a description like "shallowly immutable" would be OK, 
since there's room to explain. The earlier the mention of components as 
a shorthand for fields, the easier and less dry will be 8.10 and 
especially 8.10.1.

On 9/3/2019 6:40 AM, Maurizio Cimadamore wrote:
> * in 8.10.1:
> "Each record component in the /RecordHeader/ declares one |private 
> final| field in the record class whose name is same as the /Identifier/ 
> in the record component."
> This seems a bit early. Also, you repeat the same in 8.10.3, which is 
> arguably a much better place?

Let's dial back the focus on the record header; compare with how 8.4 
introduces `MethodHeader` but never utters "header" in narrative text.

8.10.1  Record Components

[This paragraph has two normative sentences in it.] The _record 
components_ of a record type, if any, are specified in the header of the 
record declaration. [The X of the type being specified in the Y of the 
declaration comports with how 8.10 says that the declaration specifies 
the type.] [The grammar allows a component-less record type; I'm 
following that. See 8.4.1 for another "if any" usage.] Each record 
component corresponds to an implicitly declared field of the record type 

RecordHeader:    [Following style of 14.2 and 8.4/8.4.1]
     `(` _[_RecordComponents_]_ `)`

     RecordComponent _{_ `,` RecordComponent _}_

// Grammar should always be followed by basic well-formedness rules.

It is a compile-time error for a record header to specify two record 
components with the same name.

It is a compile-time error for a record header to specify a record 
component with the name clone, finalize, getClass, hashCode, notify, 
notifyAll, readObjectNoData, readResolve, serialPersistentFields, 
toString, wait, or writeReplace.  [It's right to say this here, where 
we're talking about components themselves -- we're hinting to compilers 
that the error should be about component with a bad name, not 
field-you-can't-see-in-source having a bad name.]

8.10.2 says "It is a compile-time error for the body of a record 
declaration to contain non-static field declarations. All non-static 
fields should be declared as record components in the record header."

Use "should" very very sparingly in normative text. This is the place 
for a note about how, "in the judgment of the designers of the Java 
programming language", the only per-instance state of a record type 
should be the components -- and yes, that state is final, so plainly a 
record type isn't syntactic sugar, it expresses something about the 
aggregate. Also a good place to compare enum types -- instance 
controlled, but state can be arbitrary -- versus record types -- not 
instance controlled, but state is locked down. (Comparison not because a 
developer is wondering whether to use an enum type or a record type; 
comparison because, well, this is the JLS -- it introduced two "special" 
kinds of class type, so needs to teach their outlines before anyone asks.)

8.10.4: Consider how 8.8.9 states that "If a class contains no 
constructor declarations, then a default constructor is implicitly 
declared." and how 8.9.2 modifies that with "In an enum declaration with 
no constructor declarations, a default constructor is implicitly 
declared. The default constructor is private, has no formal parameters, 
and has no throws clause." ... 8.10.4 should follow suit. At the same 
time, there is value in the term "canonical ctor" -- in a normal class, 
if you explicitly declare an arbitrary ctor, then you don't get a 
default ctor implicitly declared, whereas in a record type, if you 
explicitly declare an arbitrary ctor, then you still get a canonical 
ctor implicitly declared. I recognize this is a more imperative 
description than the declarative sentences which currently start 8.10.4, 
but I really couldn't get the whole picture until I wrote out the above.

I also want to bring in the class instance creation expression that 
invokes a ctor (yes, that's the phrasing from 8.8) with a bunch of 
arguments. Given record R(int x,int y), you can create it via `new R("Hi 
Bob")` if you wrote a suitable ctor, but by policy we're also going to 
give you the option of `new R(1,2)` because the language has a 
first-class idea of the important state of the record. That needs to be 
memorialized in the JLS.

Separately, there is a compact ctor declaration form whose body is 
magic. That comes at the end, after everything has been said about 
canonical ctor. Currently the compact form is grabbing too much focus.

Here's a plan:

To support proper initialization of its record components, a record type 
does not implicitly declare a default constructor (8.8.9). Instead, a 
record type has a _canonical constructor_ that is declared either 
explicitly or implicitly, and whose formal parameters correspond to the 
record components.  [This para is meant to be suggestive, not precise.]

For a record type R with components C_1, ..., C_n, the canonical 
constructor has the form:
[bullet list of precise statements about access, params, no-throws]

If the canonical constructor is explicitly declared, then ... [Umm, what 
are the rules? Can I call super(..) anywhere? Can it throw? What? I'm 
not talking about the compact form, forget that, it's a distraction.]

It is a compile-time error if a record declaration contains more than 
one explicit declaration of the canonical constructor.

If the canonical constructor is not explicitly declared, then it is 
implicitly declared. Its body ...

If a record declaration contains constructor declarations other than the 
canonical constructor, then they follow the same rules as for a normal 
class declaration.  ["may be of the same form" is vague; a fixed form is 
a feature of default ctors only; all other ctors of a class just have a 
bunch of rules.]

8.10.5  Compact Record Constructors

A _compact constructor declaration_ is a form of constructor declaration 
that explicitly declares the canonical constructor.

CompactConstructorDeclaration:  [Present this here, not 8.10.2, which 
should forward-ref this section.]
     {Annotation} {ConstructorModifier} [TypeParameters] SimpleTypeName 
[Throws] ConstructorBody


> * initialised - UK vs US english - not sure which we should use, but I 
> see other uses of 'initialized' around

US English.

> * 9.7.4:
> we say this:
> "
> It is a compile-time error if an annotation of type /T/ is syntactically 
> a modifier for:
> [...]
> a record component but T is not applicable to record component 
> declarations, type contexts, type parameter declarations, field 
> declarations, or method declarations."
> But it's not super clear what happens to the annotations that are 
> actually correct and how they are propagated. Should e.g. the fact that 
> a field decl anno on a record element is propagated on its synthetic 
> private field be covered by the JLS?

The private fields aren't synthetic (generated by a compiler as a 
private implementation detail), they're mandated ("implicitly 
declared"). 8.10.3 says that the implicitly declared fields have the 
annos from the components, great, done.


More information about the amber-spec-observers mailing list