Reference-default style

Brian Goetz brian.goetz at oracle.com
Fri Feb 7 22:05:35 UTC 2020


>
> So, summary:
>
>  - Yes, we should figure out how to support abstract class supertypes 
> of inline classes, if only at the VM level;
>  - There should be one way to declare an inline class, with a modifier 
> saying which projection gets the good name;
>  - Both the ref and val projections should have the same 
> accessibility, in part so that the compiler can freely use inline 
> widening/narrowing as convenient;
>  - We would prefer to avoid duplication of the methods on both 
> projections, where possible;
>  - The migration case requires that, for ref-default inline classes, 
> we translate so that the methods appear on the ref projection.


Let me flesh this out some more, since the previous mail was a bit of a 
winding lead-up.

#### Abstract class supertypes

It is desirable, both for the migration story and for the language in 
general, for inline classes to be able to extend abstract classes.  
There are restrictions: no fields, no constructors, no instance 
initializers, no synchronized methods.  These can be checked by the 
compiler at compile time, and need to be re-checked by the VM at load time.

The VM folks have indicated that their preferred way to say 
"inline-friendly abstract class" is to have only a no-arg constructor 
which is ACC_ABSTRACT.  For abstract classes that meet the 
inline-friendly requirement, the static compiler can replace the default 
constructor we generate now with an abstract one.  The VM would have to 
be able to deal with subclasses doing `invokespecial <init>` super-calls 
on these.

My current bikeshed preference for how to indicate these is to do just 
the test structurally, with good error messages, and back it up with 
annotation support similar to `@FunctionalInterface` that turns on 
stricter type checking and documentation support.  (The case we would 
worry about, which stronger declaration-site indication would help with, 
would be: a public accidentally-inline-friendly abstract class in one 
maintenance domain, extended by an inline class in another maintenance 
domain, and then subsequently the abstract class is modified to, say, 
add a field.  This could happen, but likely would not happen that often; 
we can warn users of the risks by additionally issuing a warning on the 
subclass when the superclass is not marked with the annotation.)

#### Val and ref projections

An inline class `C` declaration defines three types; `C`, `C.ref`, and 
`C.val`.  One of the latter two is always an alias for the first.  
`C.ref` is always a reference type, and we extend the `.ref` operator to 
extend to all classes (thus allowing us to speak of `T.ref` in generic 
code.)

Which of the two projections (.ref and .val) is aliased to C is 
determined by a modifier, either `ref-default` or `val-default` (the 
latter being the, er, default.)

The three types are defined to have the same members, type variables, 
and accessibility; the only differences between `C.ref` and `C.val` are:
  - The value set of `C.val` consists of values, whereas the value set 
of `C.ref` consists of references to values, and includes null;
  - `C.ref <: Object` and `C.ref <: I` for all implemented interfaces `I`.
  - `C.val` can be _converted_ to `Object` or `I` via an _inline 
widening conversion_ (but, this conversion is cheaper than you might think.)
  - Variables of type `C.val` are routinely flattened by the VM, whereas 
variables of type `C.ref` are not.

#### Translation -- classfiles

A val-default inline class `C` is translated to two classfiles, `C` (val 
projection) and `C$ref` (ref projection).  A ref-default inline class 
`D` is translated to two classfiles, `D` (ref projection) and `D$val` 
(val projection), as follows:

  - The two classfiles are members of the same nest.
  - The ref projection is a sealed abstract class that permits only the 
val projection.
  - Instance fields are lifted onto the val projection.
  - Supertypes, methods (including static methods, and including the 
static "constructor" factory), and static fields are lifted onto the ref 
projection.  Method bodies may internally require downcasting to `C.val` 
to access fields.

#### Translation -- uses

Variables of type `C.ref` are translated as L types (`LC` or `LC$ref`, 
depending); variables of type `C.val` are translated as Q types (`QC` or 
`QC$val`, depending.)

`C.val` is widened to `C.ref` by direct assignment, since in the VM, an 
inline class is related to its supertypes by subtyping. `C.ref` is 
narrowed to `C.val` by casting, which the VM can optimize to a null check.

Instance field accesses on `C.val` are translated to `getfield`; field 
accesses on `C.ref` are translated by casting to `C.val` and `getfield`.

Method invocation on `C.val` or `C.ref` can be translated directly, 
except for private methods, which would require casting `C.val` to 
`C.ref` first (not because they are inaccessible, but because they are 
not inherited.)  Same for static fields.

Conversion of `C.ref` to supertypes is ordinary subtyping; conversion of 
`C.val` goes through widening to `C.ref`. Similarly, `instanceof` on an 
operand of type `C.val` goes through casting to `C.ref`.


There are other stackings, of course, but this is a starting point, 
chosen for simplicity and compatibility.



More information about the valhalla-spec-observers mailing list