Remi, to restate your concern slightly - it sounds like there may be binary (and source?) compatibility concerns with the implementation of with'ers. You've pitched a possible implementation using invokedynamic (every compiler writer's favourite swiss army knife) but prefer something that is more explicit in the bytecode. I'm still working my way through the full reconstruction document but I assume compatibility and implementation have been given some thought even if they don't show up in that document.
Let's aim for the high order bits first - figuring out if the feature is desirable and if the general direction works before deep diving into class file representation and binary compatibility. So I'd suggest putting a pin in this topic and circling back to it after further discussion of the core concept. Just my two cents =) --Dan On Tue, Jun 14, 2022 at 8:23 AM Remi Forax <fo...@univ-mlv.fr> wrote: > > Hi all, > Let say we have a Point with 2 components > record Point(int x, int y) { } > > Then we change the record to add a 3rd components in a more or less backward > compatible way > record Point(int x, int y, int z) { > Point(int x, int y) { > this(x, y, 0); // creation of the new value 0 > } > } > > Now, let say there is a 'with' somewhere in another code > > var newPoint = point with { x = 3; }; > > If this code is compiled when the record Point had only two components, so > this is equivalent to > > Point(int x, int y) = point; // i.e. int x = point.x(); int y = point.y(); > x = 3; > var newPoint = new Point(x, y); > > The problem is that if we run that code with the new version of Point (the > one with 3 components), > newPoint.z is not equals to point.z but to 0, so once there is a 'with' > somewhere, there is no backward compatibility anymore. > > We can try to restore the backward compatibility by compiling to a slightly > different code using invokedynamic and a desugared method corresponding to > the body of the 'with' > > var newPoint = invokedynamic (point) [desugared$method]; // equivalent to > a call using with > > static Point desugared$method(int x, int y, MethodHandle mh) { // content > of the body > x = 3; > return mh.invokeExact(x, y); > } > > an at runtime, we generate a tree of method handles that does more or less > stub(Point point) { > return desugared$method(point.x(), point.y(), (a, b) -> new Point(a, b, > point.z()) > } > > because this code is generated at runtime, it will be always compatible with > the latest version of Point. > > If we want to support this encoding, it means that the local variables of the > enclosing method need to be effectively final so the body of with can be > lifted to a private static method (exactly like a lambda). > > > If we generalize this a bit, we can also use the same trick for the record > pattern, in that case the pattern Point(int a, int b) is equivalent at > runtime to Point(int a, int b, _) once the runtime found that the canonical > deconstructor emits the values of 3 components. > I'm not sure it's a path i want to follow because i would prefer the record > pattern to match the shape excatly, but i find it more attractive than the > idea to have overloaded deconstructors. > > regards, > Rémi >