So I think the JVM story is fairly settled; I've spun it off into another 
thread, with a proposed spec.

The language story is still uncertain. Here are four alternative designs, all 
of which are, I think, reasonable approaches to consider. Some discussion about 
trade-offs is at the end.

-----

Alternative 1: No language support

The language requires all inline classes to extend Object (or perhaps prohibits 
the 'extends' clause). Abstract <init> methods are available only as a compiler 
tool.

Some special exceptions are necessary:

- Somehow, class files for Object and Number must be generated with abstract 
<init> methods

- Integer, Double, etc., are inline classes but are allowed to extend Number

-----

Alternative 2: Abstract constructors in the language

abstract class C {
    public abstract C();
}

Like a method, a constructor can be declared 'abstract' and omit a body. 
(Bikeshed: maybe the 'abstract' keyword is spelled differently, or left off 
entirely.)

These declarations can align closely with the JVM's rules for <init> methods:
- The superclass must also have an abstract constructor
- No instance initializers or instance field initializers are allowed
- It's okay to overload the constructor, but 'abstract' only works on a no-arg 
constructor
- (Perhaps) the class doesn't have to be abstract
- It's allowed (as a no-op) to invoke the constructor (new Object() or super())

We'll need one further restriction that isn't checked by the JVM and isn't as 
principled: no instance fields are allowed, even if they're 
default-initialized. Otherwise, inline classes will have to search for private 
fields to decide if extension is legal or not, breaking encapsulation.

The abstract-ness of the constructor is part of the API—appears in javadoc, 
changing it is incompatible. Having an empty or default constructor isn't the 
same as having an abstract constructor.

Inline classes can only extend classes with abstract constructors (and maybe no 
'synchronized' instance methods).

-----

Alternative 3: Inline-friendly property in the language

inlineable abstract class C {
}

The 'inlineable' property (bikeshed: how to spell this?) enforces some 
constraints and authorizes children to be inline classes.

Specific constraints:
- Can't have instance fields
- Can't declare a constructor or instance initializer
- Must extend an inlineable class
- Must be an abstract class or Object (maybe?)
- Must not have synchronized methods (probably?)

An annotation spelling is an option, too, although that crosses a line—there's 
a precedent for annotations that prompt compiler errors, but not for 
annotations that influence bytecode output.

An inlineable class's bytecode has an abstract <init> method.

The 'inlineable' property is part of the API—appears in javadoc, changing it is 
incompatible.

Inline classes can only extend inlineable classes.

-----

Alternative 4: Infer no constructor

Typically, for each class that lacks a constructor declaration, a default 
constructor is provided that does some simple initialization. But in the 
following circumstances, we claim there is no constructor at all:

- Class doesn't declare a constructor or instance initializer
- Class doesn't declare fields
- Superclass has no constructor
- Class is abstract or Object (maybe?)

Again, blank private fields may not *need* initialization, but inline 
subclasses need to know that they exist, and the best way to communicate that 
is through the constructor.

'new Object()' (and perhaps 'new Foo()' if we drop the abstract class 
requirement) doesn't reference any constructor at all. In that case, a fresh 
instance is allocated without any code execution.

For compatibility, this also applies to 'super()' (although we can discourage 
its use in these cases going forward).

A class without a constructor has, in bytecode, an abstract <init> method.

The lack of a constructor is part of the API—appears in javadoc, changing it is 
incompatible. An annotation like @FunctionalInterface could help by checking 
that nothing has changed on recompilation.

Inline classes can only extend classes that lack constructors (and maybe 
'synchronized' instance methods).

-----

Discussion

Noise level: We don't want to make a big deal about this feature. People 
shouldn't think too much about it. (4) wins in this regard—no new syntax, just 
some hand-waving in the language model about what it actually means when you 
leave out your constructor. (2) introduces a subtle variation on constructor 
declarations, which can generally be overlooked. (3) is a neon invitation to 
treat these classes like a fundamentally new kind of entity.

Compatibility: Adding or removing 'abstract' from an '<init>' method is a 
binary incompatible change, so it's good if that doesn't happen accidentally. 
But it's hard to increase awareness of that commitment while minimizing noise 
level, so there are trade-offs. (2) and (3) both force authors to change 
something. (4) doesn't have that guardrail, and it's quite easy to imagine an 
innocent refactoring that makes a class inline-hostile.

Brian suggested an annotation as an optional guard against this, but I doubt 
most inline class superclasses will even be aware that they should consider the 
annotation. And if we succeed in making everyone worry about it, well... adding 
that overhead as a programming best practice seems counter to (4)'s strengths.

What's especially troubling about (4) is we're taking longstanding intuitions 
about Java programming—an absent constructor is the same as an empty 
constructor—and saying, just kidding, those are two different things. And not 
just different implementations, but different APIs with different compatibility 
promises. In fact, by leaving off the constructor, you've promised to *never* 
introduce a private instance field to this class.

Availability: Ideally, extending a suitable abstract class should be 
frictionless for inline class authors. In (2) and (3), the authors are blocked 
until the abstract class author opts in. In (4), the opt in is by default, 
although there's still a requirement that the abstract class be compiled with 
an appropriate source version.

In practice, if we require an opt in, this will be an obscure, little-used 
feature; or maybe it will become widespread, but we'll have introduced some new 
standard boilerplate into the language. If we don't require an opt in, inline 
classes will be free to extend most abstract classes automatically.

For context, Brian passed to me some Google corpus numbers on abstract classes. 
Abstract classes are fairly rare in the universe of type declarations (4%). 
Among abstract classes, a large majority seem to be candidate supertypes for 
inline classes—85% have no fields. And most are public (75%), meaning they're 
less likely to be aware of all their subclasses.

Summary: I'm torn. (4) is really the feature I want, especially on the 
availability front, but I don't know if we can get away with pulling the rug 
out from under authors.

And stepping back, whether we opt in (as in (4)) or opt out (as in (2), (3)) by 
default, it's unfortunate that the decision we're asking authors to make is not 
something they're equipped for. Specifically: "Do I think it's more likely that 
someday an inline class will want to extend this class, or that someone will 
want to add a private field to this class?" How can they answer that? Best they 
can do is play the odds, which I don't imagine change much from class to class, 
and I'm guessing would say it's much more likely to be extended by an inline 
class.

Reply via email to