The motivation for this comes from erasure. Even if we specialize, we need to be able to use specialized generics in an erased context, because there will be erased clients. If we have
class Foo<T> { T m(); T[] arr(); } we can use also sorts of bridging/casting tricks to make Foo<int> work with the erasure of T to Object, but we’ve got much less latitude when we want to treat an int[] as an Object[]. Since arrays are mutable / identity objects, we can’t copy them even if we wanted to; we need an identity-preserving way to say “this int[] array, as if it were an Object[]”. Rejigging array covariance to work over the “extends” relation rather than the “subtype” relation seems the least damaging way to get there. On Jan 25, 2022, at 9:40 AM, Dan Heidinga <heidi...@redhat.com<mailto:heidi...@redhat.com>> wrote: Can you mix and match both modes in the same method? Probably, since the interpreter doesn’t care about multi-bytecode patterns. Dunno if this causes a testing problem, and if so how to fix it. I think it’s probably OK, especially if we require the two-way checkcast (Q-Foo not a subtype of L-Foo in the verifier) so that each mode stays “in its own lane”. More explicitly, this is a set of use cases for using Q-types in C_Class entries in the constant pool to switch to Q-mode for bytecodes that refer to classes, including withfield and aconst_init. Let's talk a bit about having the L world and the Q world completely disjoint at least from the bytecode verifier POV. It means that we need some checkcasts to move in both direction, from a Q-type to a L-type and vice-versa. But at the same time, an array of L-type is a subtype of an array of Q-type ? The result to a very uncommon/unconventional type system, and i'm not a big fan of surprises in that area. I've been puzzling over this as well and echo your discomfort with it, mostly on the array side. I haven't been able to identify what the sharp edge here is though other than that it feels surprising. After playing with bytecode sequences, the part I'm not clear on is whether I can store an LFoo; into a [QFoo; directly, or do I need a checkcast QFoo; before the aastore? If I need the checkcast (and I think I do), then I'm starting to come around to the view that the array side isn't actually any different from what we'd do for any other subclass relationship. The checkcast for Q->L is still odd, but less concerning as it deals with new value semantics rather than changing the array covariance? For reference, the bytecodes sequences I've been looking at are the following: Convert with checkcast: -------------------------------- aload_1 checkcast QFoo; // or LFoo; and Convert with Q->L array store/load: --------------------------------------- anewarray //LFoo astore_2 aload_2 iconst_0 invokestatic QFoo.<new>QFoo; // or any other way to get a Q aastore aload_2 iconst_0 aaload // use as an LFoo Convert with L->Q array store/load: --------------------------------------- anewarray //QFoo astore_2 aload_2 iconst_0 invokestatic X.getFoo:()LFoo; // Is a checkcast needed here first to downcast? I think so aastore aload_2 iconst_0 aaload // use as an QFoo --Dan Furthermore, i believe that subtyping is a key to avoid multiple bytecode verification of the generics code. By example, with the TypeRestriction attribute [1], the restriction has to be subtype of the declared type/descriptor. Rémi [1] https://cr.openjdk.java.net/~jrose/values/parametric-vm.html#type-restricted-methods-and-fields-and-the-typerestriction-attribute