Mu suggestion here is to leave subtyping on the side, at least for now and use some other way to describe what's going on.

Let's model the value vs. reference constraint explicitly - e.g. with 'val' and 'ref' type kinds (and let's not open the can worm as to what the syntax should be, whether it should be a type anno, etc.)

So:

val Object ---> accepts all values
ref Object ---> accepts all references
any Object ---> accepts all references

Now that everything is explicit, for types we have two possible moves:

1) reinterpret `Object` as `ref Object`

This will keep semantics as is - that is, upon recompilation, if source code doesn't change, a program that expected references cannot start receiving value parameters. If code wants to work on both references and values, it will have to opt in (by using `any`).

2) reinterpret `Object` as `any Object`

That is, the semantics of `Object` is being redefined here - code which assumed to work with references might need to opt-in to additional constraints (e.g. add `ref`) in order to make sure it still work as intended.

I think we are leaning towards (2) - that is, we want meaning of Object to be upgraded, and we want users that are not happy with that to opt out in some form.

Ok, now let's think about expressions; given an expression E, I have to figure out (i) its type and its (ii) kind, since the type system I'm describing here takes both factors into account.

Here I'm expecting rules of the kind:

a) if E is an identifier pointing to a variable decl, then type and kind are derived from the declaration b) if E is a method call, where declared method is M, type and kind are derived from M's return type declaration
...
z) if E is a new expression, of the kind `new T()`, the type is T and the kind can be either `ref` or `val` depending on whether T is a reference class or not. If T can be both, then kind `any` is inferred.

So, we can use (z) e.g. to say that `new String()` has kind `ref`. But, if we want Object to be the top type for both values and references, I believe one consequence is that `new Object` is interpreted as `any` which means you cannot pass it to `ref Object`.

I don't see another way out of this conundrum - other than adding a special rule (z2) which says that `new Object()` is treated specially and always has kind `ref`. But doing so will run afoul in almost every possible way - as soon as you manipulate the result of the `new` in any way (cast, assignment to variable of type `Object`, ...) you go back to `any` and you are back to a place that is incompatible with `ref Object`.


Your idea of treating Object as abstract is, I believe, a sound one (which doesn't need any extra rule) - but we might have to figure out some story for anonymous inner classes of the kind `new Object() { ... }`.

Maurizio



On 15/04/2019 14:26, Brian Goetz wrote:


But the other concerns remain, e.g. as to the fact that the boundary between reinterpreted types (Object as RefObject) and non-reinterpreted types (Object as top type) seems very fuzzy.

Right, which is why we’re still searching for an answer :)

We really, really want to be able to represent ref/val-ness in the type system.  Why?  Ignoring pedagogical concerns (which are significant):

 - If certain operations (e.g., locking) are partial, we want to be able to provide a way to ask if the operation could succeed.  Such as:

    if (x instanceof RefObejct) { … lock on x … }

Saying “lock, and hope it doesn’t throw” is not a very good answer.  We already have a tool for querying the dynamic type of an object — instanceof.

 - Saying that a method should only accept reference objects should be something expressible in the method signature, as in

    m(RefObject o) { … }

Types are how we do that.

 - Similarly, we might want to express the above constraint generically; again, types are the way we do that:

    class Foo<T extends RefObject> { }


And, Q-world already taught us that we wanted to retain Object as the top type.  This mean, necessarily, that Object gets a little weirder; it takes on some partly-class, partly-interface behavior.

Here’s an idea: What if we migrated `Object` to be an abstract class?  The casualty would be the code that says `new Object()`.  While there’s certainly a lot of it out there, perhaps this is something amenable to migration:

 - At the source level, for N versions, `new Object()` gets a warning that says “I’ll pretend you said `Object.newLockInstance()` or something.  - At the bytemode level, for M versions, we do something similar, likely for M > N.

We can start this now, before Valhalla even previews.


Reply via email to