Reference-default style
Brian Goetz
brian.goetz at oracle.com
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
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