Reference-default style

John Rose john.r.rose at
Fri Feb 7 23:47:04 UTC 2020

So, unless someone else comes up with something surprising,
I think we know enough to prototype vigorously on your proposal,
Brian.  (Brian would say, “why don’t we run with this for now”.)

My queasiness about hollowing out C.val will either gain some
practical shape from experience in prototyping, or will turn
out to be something like coronavirus.

— John

On Feb 7, 2020, at 3:37 PM, Brian Goetz <brian.goetz at> wrote:
>> I have a tiny reservation about the co-accessibility of both projections,
>> although it’s a good principle overall.  There might be cases (migration
>> and maybe new code) where the nullable type has wider access than
>> the inline type, where the type’s contract somehow embraces nullability
>> to the extent that the .val projection is invisible.  But we can cross that
>> bridge when and if we come to it; I can’t think of compelling examples.
> I was worried about this too, wanting to support patterns like "public interface, private implementation."  But then I realized I was thinking like a compiler rather than a programmer!  If we want that, we can just write:
>     public interface Foo { ... }
>     private inline class FooImpl implements Foo { ... }
> just like we always did.
>> (Nitpick:  The JVM *fully* checks synchronization of such things dynamically;
>> it cannot fully check at load time.  Given that, it is not a good idea to partially
>> check for evidence of synchronization; that just creates the semblance of an
>> invariant where one does not exist.  The JVM tries hard to make static checks
>> that actually prove things, rather than just “catch user errors”.  So, please,
>> no JVM load-time checks for synchronized methods, except *maybe* within
>> the inline classes themselves.)
> Sure, that's your call.  The static compiler can do its own thing.
>>> #### 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.
>> This is a little like MVT, in that inline classes end up containing very little
>> other than fields.  This is the right move, IMO, for migrated classes.
>> Hollowing out *all* inline classes strikes me as over-rotation for the sake
>> of migration.  I see how it allows both cases to have the same translation
>> strategy, *except for the name*.  That’s a pleasing property on paper.
> It's more than a pleasing property (though it is that.)  Having translation strategies with big switches in them is where surprising migration compatibility constraints come from; would we want (say) changing a method from private to public to be a binary-incompatible change?  These are the kinds of things that happen when the translation strategy gets too squirrely and ad-hoc.
>> In the case of reflection, I think we can afford to show a consistent view
>> for both kinds of inlines, by making all fields and methods appear on
>> both projections.
> The language does this; while reflection is usually classfile-based, we'd like to treat these two classfiles as being mostly one artifact, so this seems reasonable to consider.
>> This to say “the class holding the method” instead of “the C.ref”, we preserve
>> the immediate goal of supporting migration of Optional etc., but we incur
>> some migration debt, because it’s harder to move from val-default to ref-default.
>> This, I think, is best fixed by adding auto-bridging of some sort later, rather
>> than over-rotating towards the migration case right now.
>> (Did I miss some other reason for putting everything on C.ref?)
> Putting stuff on C.ref means we can lean harder on inheritance/subtyping in the translation scheme; putting stuff on C.val means we're always casting.  That might be OK, since there are probably more derefs of the val than of the ref.

More information about the valhalla-spec-observers mailing list