Reference-default style

Brian Goetz brian.goetz at
Fri Feb 7 23:37:42 UTC 2020

> 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 

     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