The general consensus here is that this stacking is slightly better than
the previous one, so let's take this as the plan of record. Now, to
explore the next turn of the crank...
The concerns that were raised could be characterized by "do we *really*
need to even have separate class literals for Foo.ref and Foo.val?" And
the answer is, sort of yes, sort of no, so there is a possible next turn
of the crank that might or might not be a further improvement.
Two existing constraints we have are:
- Reflection (and MethodHandle) already do everything with jl.Class.
That means, for example, we had to invent the primitive mirrors a long
time ago, so that you could describe a field or method parameter of type
int. "Fixing" this is not really on the table right now.
- The language has given this some surface; you can say `int.class`
(which maps to`getstatic Integer.TYPE`.)
Given all this, reflection needs to be able to differentiate between
LFoo and QFoo, which suggests a second mirror. And the user needs to be
able to express this mirror to call getMethod() or Lookup::findXxx (and
to interpret the results of reflective lookups). But maybe we don't
need to give it so much air *in the language*, especially given how
confusing it is because the language and VM views (which reflection is
closer to) don't align entirely.
Rather than talking about the ref vs val mirror (which is confusing,
because getClass returns the same thing whether something is stored in a
.ref or a .val), perhaps we can reframe in terms of the "class mirror
and the flattened representation mirror", or the "primary mirror and the
restricted mirror", or some other such (though, even the term "mirror"
is foreign to users, since all the see is jl.Class.)
In this model, Point.class would be the only mirror expressible directly
in the language, and it would always be the primary mirror. Reflection
users would need to navigate to the secondary mirror through something
like `Point.class.secondaryMirror()` or some other method on jl.Class.
By not putting this in the language, we avoid some of the confusing
aspects of this story.
On 6/23/2021 11:13 AM, Brian Goetz wrote:
In working through the details of reflective support in JEP 401, I
think we've fallen into a slight "false consistency" regarding class
literals. (This is a complicated network with many interrelated moving
parts, so I'm not looking for "quick answers" here, as much as
balancing global consistency with an understandable user story.)
In the language, a primitive class declaration gives rise to a class
(P) and three types: P, P.ref and P.val. P is an alias for one of
them; most of the time, it's an alias for P.val, but for migrated
value-based class, it's an alias for P.ref. The language has a concept
for tuning this mapping, currently under the name `ref-default`. (The
VM would like to remain ignorant of details like this.)
In the VM, there is one class (P), whose instances can have two
representations, flattened and indirect, described by two descriptors
(QP and LP).
The VM and reflection need mirrors for both of these descriptor
types. This is a problem we face today to some degree for the
primitive types; there's a "neutered" mirror for int.class that
doesn't correspond to an actual declared class with limited
operational capability. These "secondary" mirrors are only used for
reflection / method handles, to reflect method and field descriptors.
We double down on this story for mirrors for primitive classes. Each
primitive class has a primary (unrestricted) mirror corresponding to
the L descriptor, and a secondary (restricted, flattened, possibly
null-free) mirror corresponding to the Q descriptor. The secondary
mirror has only one job: reflecting Q descriptors in method and field
descriptors (and supporting method handles in their emulation of same.)
When you ask an object for getClass(), it always hands back the
primary mirror.
As a bonus, this has a nice compatibility story with Integer today;
reflective code is used to testing for Integer.class when an int is
expected (reflection is always boxed), so this will continue to work,
because instances of `int` will report `Integer.class` from `getClass()`.
All of this feels like the low-energy-state for extending mirrors to
primitives. Here's where I think we bobbled a little bit, and can
correct.
Currently, we say "Well, if Point is an alias for Point.val, then
Point.class must be Point.val.class". This is consistent, but kind of
useless. Because then, for example:
void puzzler() {
assertEquals(Point.class, new Point(0,0).getClass());
}
will fail (unless Point is ref-default, at which point it succeeds.)
I think the mistake we made is too literally following the model of
"Point is an alias for Point.val". So, I'm proposing a slight
adjustment, that treats the unqualified class name contextually:
- In the context of a variable type, Point is an alias for Point.val,
or Point.ref, if ref-default (no change from current);
- Where a class is needed (e.g., new Point(), instanceof Point),
Point refers to the class Point, not any of its derived types, and
Point.{ref,val} are not valid in these contexts (no change from current);
- Where a reflective literal is needed, the unqualified Point.class
is always the primary mirror, which always matches the behavior of
Object::getClass (change);
- If an explicit literal is needed for reflection (e.g., calling
getMethod()), you can be explicit and say Point.{ref,val}.class to say
what you mean (no change).
This aligns the meaning of "Point.class" with the actual behavior of
other reflective API points.