[Expanding on and summarizing discussion about abstract superclasses from 
today's meeting.]

-----
Motivation

There are some strong incentives for us to support inline classes that have 
superclasses other than Object. Briefly, these include:

- Identity -> inline migration candidates (notably java.lang.Integer) often 
extend abstract classes
- A common refactoring may be to extend an existing class with a (possibly 
private) inline implementation
- Abstract classes are more expressive than interfaces
- If we compile Foo.ref to an abstract class, we can better represent the full 
API of an inline class using an abstract class

To be clear, much of this has to do with migration, and I subscribe to a fairly 
expansive view of how much we should permit and encourage migration. I think 
most every project in the world has at least a few opportunities to use inline 
classes. Our design should limit the friction necessary (e.g., disruptive 
redesigns of type hierarchies) to integrate inline classes with the existing 
body of code.

We've considered, as an alternative, supporting transparent migration of 
existing classes to interfaces. But this raises many difficult issues 
surrounding source, binary, and behavioral compatibility. It would be nice not 
to have to tackle those issues, nor introduce a lot of caveats into the class 
-> interface migration story.

-----
Constraints

Inline class instantiation is is fundamentally different from identity class 
instantiation. While the language seeks to smooth over these differences, under 
the hood all inline objects come from 'defaultvalue' and 'withfield' 
invocations. There is no opportunity in these bytecodes for a superclass to 
execute initialization code.

(Could we redesign the construction model to properly delegate to a superclass? 
Sure, but that's a huge new feature that probably isn't justified by the use 
cases.)

As a result, constructors, instance initializers, and instance fields in a 
superclass are unusable to inline class instances. In fact, their existence 
would be a vulnerability, since authors typically make assumptions about 
initialization having occurred.

Fortunately, 'Object' doesn't require any initialization and so can safely be 
extended. Our goal is to expand the set of safe-to-extend classes.

-----
Language model

An inline class may extend another class, as long as the superclass has the 
following properties:
- It has no instance fields
- It has no constructors
- It has no instance initializers
- It is abstract* or Object
- It extends another class with these properties

Subtype polymorphism works the same for superclasses as it does for 
superinterfaces.

(*Remi points out that we could drop the 'abstract' restriction, meaning there 
may be identity instances of the superclass. Given the restriction on fields, 
though, I'm struggling to envision a use case; the consensus is that 'new 
Object()' is probably something we want to *stop* supporting.)

Call a class that satisfies these constraints an "initialization-free class" 
(bikeshedding on this term is welcome!). Like an interface, its value set may 
include references to both inline class instances and identity class instances.

We *do *not* want the initialization-free property to be expressed as a class 
modifier—this feature is too obscure to deserve that much prominence, 
encouraging every class author to consider one more degree of freedom; and we 
don't want every class to have to manually opt in.

But we *do* need the initialization-free property to be part of the public 
information about the class. For example, the javadoc should say something like 
"this is an initialization-free class". Otherwise, it's impossible to tell the 
difference between, e.g., a class with no fields and a class with private 
fields.

In the past, a Java class declaration that lacks a constructor always got a 
default constructor. In this model, however, an initialization-free class has 
no constructor at all. A 'super()' call directed at such a class is a no-op.

-----
Compilation & JVM support

There are two alternative compilation strategies:

1) An initialization-free class is compiled like always, including an '<init>' 
method of the form 'aload_0; invokespecial; return;'. Some metadata (flag or 
attribute) indicates that the class is initialization-free.

The initialization-free flag is partially validated at class load time: it's an 
error to claim to be initialization-free and be non-abstract (and non-Object), 
declare instance fields, or extend a non-initialization-free superclass.

We don't validate <init> method contents. If someone chooses to generate an 
"initialization-free" class file that contains <init> code, they accept the 
risk that the code won't run.

On loading, an inline class must extend an initialization-free class.

2) An initialization-free class is compiled without an '<init>' method.

For binary compatibility, existing references to 'Foo.<init>' must successfully 
resolve, with invocation being a no-op. (This is something new—resolution to 
fake declarations—and potentially concerning.)

At class load time, an inline class must extend a chain of superclasses that 
are abstract (or Object), lack '<init>' methods, and lack instance field 
declarations.

Existing classes that meet these requirements may act as inline class 
superclasses (probably surprisingly, since currently a class without an <init> 
method can't be initialized at all).

(My thoughts on (1) vs. (2): both are plausible, and I like the lack of 
metadata overhead in (2), but otherwise (1) seems much cleaner.)

In either case, the big new feature here is that an inline class may have a 
superclass other than Object. This may violate some existing assumptions in the 
implementation, although it sounds like we can hope nothing new really needs to 
be done to support it.

Reply via email to