1 /*
   2  * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /**
  25  * ValueObjectCompilationTests
  26  *
  27  * @test
  28  * @bug 8287136 8292630 8279368 8287136 8287770 8279840 8279672 8292753 8287763 8279901 8287767 8293183 8293120
  29  *      8329345 8341061 8340984 8334484
  30  * @summary Negative compilation tests, and positive compilation (smoke) tests for Value Objects
  31  * @enablePreview
  32  * @library /lib/combo /tools/lib
  33  * @modules
  34  *     jdk.compiler/com.sun.tools.javac.util
  35  *     jdk.compiler/com.sun.tools.javac.api
  36  *     jdk.compiler/com.sun.tools.javac.main
  37  *     jdk.compiler/com.sun.tools.javac.code
  38  * @build toolbox.ToolBox toolbox.JavacTask
  39  * @run junit ValueObjectCompilationTests
  40  */
  41 
  42 import java.io.File;
  43 
  44 import java.lang.classfile.Attributes;
  45 import java.lang.classfile.ClassFile;
  46 import java.lang.classfile.ClassModel;
  47 import java.lang.classfile.Instruction;
  48 import java.lang.classfile.MethodModel;
  49 import java.lang.classfile.Opcode;
  50 import java.lang.classfile.instruction.FieldInstruction;
  51 import java.lang.constant.ConstantDescs;
  52 import java.lang.reflect.AccessFlag;
  53 import java.lang.reflect.Method;
  54 import java.util.ArrayList;
  55 import java.util.List;
  56 import java.util.Locale;
  57 import java.util.Set;
  58 
  59 import com.sun.tools.javac.util.Assert;
  60 
  61 import com.sun.tools.javac.code.Flags;
  62 
  63 import org.junit.jupiter.api.Test;
  64 import tools.javac.combo.CompilationTestCase;
  65 import toolbox.ToolBox;
  66 
  67 class ValueObjectCompilationTests extends CompilationTestCase {
  68 
  69     private static String[] PREVIEW_OPTIONS = {
  70             "--enable-preview",
  71             "-source", Integer.toString(Runtime.version().feature())
  72     };
  73 
  74     public ValueObjectCompilationTests() {
  75         setDefaultFilename("ValueObjectsTest.java");
  76         setCompileOptions(PREVIEW_OPTIONS);
  77     }
  78 
  79     @Test
  80     void testValueModifierConstraints() {
  81         assertFail("compiler.err.illegal.combination.of.modifiers",
  82                 """
  83                 value @interface IA {}
  84                 """);
  85         assertFail("compiler.err.illegal.combination.of.modifiers",
  86                 """
  87                 value interface I {}
  88                 """);
  89         assertFail("compiler.err.mod.not.allowed.here",
  90                 """
  91                 class Test {
  92                     value int x;
  93                 }
  94                 """);
  95         assertFail("compiler.err.mod.not.allowed.here",
  96                 """
  97                 class Test {
  98                     value int foo();
  99                 }
 100                 """);
 101         assertFail("compiler.err.mod.not.allowed.here",
 102                 """
 103                 value enum Enum {}
 104                 """);
 105     }
 106 
 107     record TestData(String message, String snippet, String[] compilerOptions, boolean testLocalToo) {
 108         TestData(String snippet) {
 109             this("", snippet, null, true);
 110         }
 111 
 112         TestData(String snippet, boolean testLocalToo) {
 113             this("", snippet, null, testLocalToo);
 114         }
 115 
 116         TestData(String message, String snippet) {
 117             this(message, snippet, null, true);
 118         }
 119 
 120         TestData(String snippet, String[] compilerOptions) {
 121             this("", snippet, compilerOptions, true);
 122         }
 123 
 124         TestData(String message, String snippet, String[] compilerOptions) {
 125             this(message, snippet, compilerOptions, true);
 126         }
 127 
 128         TestData(String message, String snippet, boolean testLocalToo) {
 129             this(message, snippet, null, testLocalToo);
 130         }
 131     }
 132 
 133     private void testHelper(List<TestData> testDataList) {
 134         String ttt =
 135                 """
 136                     class TTT {
 137                         void m() {
 138                             #LOCAL
 139                         }
 140                     }
 141                 """;
 142         for (TestData td : testDataList) {
 143             String localSnippet = ttt.replace("#LOCAL", td.snippet);
 144             String[] previousOptions = getCompileOptions();
 145             try {
 146                 if (td.compilerOptions != null) {
 147                     setCompileOptions(td.compilerOptions);
 148                 }
 149                 if (td.message == "") {
 150                     assertOK(td.snippet);
 151                     if (td.testLocalToo) {
 152                         assertOK(localSnippet);
 153                     }
 154                 } else if (td.message.startsWith("compiler.err")) {
 155                     assertFail(td.message, td.snippet);
 156                     if (td.testLocalToo) {
 157                         assertFail(td.message, localSnippet);
 158                     }
 159                 } else {
 160                     assertOKWithWarning(td.message, td.snippet);
 161                     if (td.testLocalToo) {
 162                         assertOKWithWarning(td.message, localSnippet);
 163                     }
 164                 }
 165             } finally {
 166                 setCompileOptions(previousOptions);
 167             }
 168         }
 169     }
 170 
 171     private static final List<TestData> superClassConstraints = List.of(
 172             new TestData(
 173                     "compiler.err.value.type.has.identity.super.type",
 174                     """
 175                     abstract class I {
 176                         synchronized void foo() {}
 177                     }
 178                     value class V extends I {}
 179                     """
 180             ),
 181             new TestData(
 182                     "compiler.err.value.type.has.identity.super.type",
 183                     """
 184                     class ConcreteSuperType {
 185                         static abstract value class V extends ConcreteSuperType {}  // Error: concrete super.
 186                     }
 187                     """
 188             ),
 189             new TestData(
 190                     """
 191                     value record Point(int x, int y) {}
 192                     """
 193             ),
 194             new TestData(
 195                     """
 196                     value class One extends Number {
 197                         public int intValue() { return 0; }
 198                         public long longValue() { return 0; }
 199                         public float floatValue() { return 0; }
 200                         public double doubleValue() { return 0; }
 201                     }
 202                     """
 203             ),
 204             new TestData(
 205                     """
 206                     value class V extends Object {}
 207                     """
 208             ),
 209             new TestData(
 210                     "compiler.err.value.type.has.identity.super.type",
 211                     """
 212                     abstract class A {}
 213                     value class V extends A {}
 214                     """
 215             )
 216     );
 217 
 218     @Test
 219     void testSuperClassConstraints() {
 220         testHelper(superClassConstraints);
 221     }
 222 
 223     @Test
 224     void testRepeatedModifiers() {
 225         assertFail("compiler.err.repeated.modifier", "value value class ValueTest {}");
 226     }
 227 
 228     @Test
 229     void testParserTest() {
 230         assertOK(
 231                 """
 232                 value class Substring implements CharSequence {
 233                     private String str;
 234                     private int start;
 235                     private int end;
 236 
 237                     public Substring(String str, int start, int end) {
 238                         checkBounds(start, end, str.length());
 239                         this.str = str;
 240                         this.start = start;
 241                         this.end = end;
 242                     }
 243 
 244                     public int length() {
 245                         return end - start;
 246                     }
 247 
 248                     public char charAt(int i) {
 249                         checkBounds(0, i, length());
 250                         return str.charAt(start + i);
 251                     }
 252 
 253                     public Substring subSequence(int s, int e) {
 254                         checkBounds(s, e, length());
 255                         return new Substring(str, start + s, start + e);
 256                     }
 257 
 258                     public String toString() {
 259                         return str.substring(start, end);
 260                     }
 261 
 262                     private static void checkBounds(int start, int end, int length) {
 263                         if (start < 0 || end < start || length < end)
 264                             throw new IndexOutOfBoundsException();
 265                     }
 266                 }
 267                 """
 268         );
 269     }
 270 
 271     private static final List<TestData> semanticsViolations = List.of(
 272             new TestData(
 273                     "compiler.err.cant.inherit.from.final",
 274                     """
 275                     value class Base {}
 276                     class Subclass extends Base {}
 277                     """
 278             ),
 279             new TestData(
 280                     "compiler.err.cant.assign.val.to.var",
 281                     """
 282                     value class Point {
 283                         int x = 10;
 284                         int y;
 285                         Point (int x, int y) {
 286                             this.x = x; // Error, final field 'x' is already assigned to.
 287                             this.y = y; // OK.
 288                         }
 289                     }
 290                     """
 291             ),
 292             new TestData(
 293                     "compiler.err.cant.assign.val.to.var",
 294                     """
 295                     value class Point {
 296                         int x;
 297                         int y;
 298                         Point (int x, int y) {
 299                             this.x = x;
 300                             this.y = y;
 301                         }
 302                         void foo(Point p) {
 303                             this.y = p.y; // Error, y is final and can't be written outside of ctor.
 304                         }
 305                     }
 306                     """
 307             ),
 308             new TestData(
 309                     "compiler.err.cant.assign.val.to.var",
 310                     """
 311                     abstract value class Point {
 312                         int x;
 313                         int y;
 314                         Point (int x, int y) {
 315                             this.x = x;
 316                             this.y = y;
 317                         }
 318                         void foo(Point p) {
 319                             this.y = p.y; // Error, y is final and can't be written outside of ctor.
 320                         }
 321                     }
 322                     """
 323             ),
 324             new TestData(
 325                     "compiler.err.strict.field.not.have.been.initialized.before.super",
 326                     """
 327                     value class Point {
 328                         int x;
 329                         int y;
 330                         Point (int x, int y) {
 331                             this.x = x;
 332                             // y hasn't been initialized
 333                         }
 334                     }
 335                     """
 336             ),
 337             new TestData(
 338                     "compiler.err.mod.not.allowed.here",
 339                     """
 340                     abstract value class V {
 341                         synchronized void foo() {
 342                          // Error, abstract value class may not declare a synchronized instance method.
 343                         }
 344                     }
 345                     """
 346             ),
 347             new TestData(
 348                     """
 349                     abstract value class V {
 350                         static synchronized void foo() {} // OK static
 351                     }
 352                     """
 353             ),
 354             new TestData(
 355                     "compiler.err.mod.not.allowed.here",
 356                     """
 357                     value class V {
 358                         synchronized void foo() {}
 359                     }
 360                     """
 361             ),
 362             new TestData(
 363                     """
 364                     value class V {
 365                         synchronized static void soo() {} // OK static
 366                     }
 367                     """
 368             ),
 369             new TestData(
 370                     "compiler.err.type.found.req",
 371                     """
 372                     value class V {
 373                         { synchronized(this) {} }
 374                     }
 375                     """
 376             ),
 377             new TestData(
 378                     "compiler.err.mod.not.allowed.here",
 379                     """
 380                     value record R() {
 381                         synchronized void foo() { } // Error;
 382                         synchronized static void soo() {} // OK.
 383                     }
 384                     """
 385             ),
 386             new TestData(
 387                     "compiler.err.cant.ref.before.ctor.called",
 388                     """
 389                     value class V {
 390                         int x;
 391                         V() {
 392                             foo(this); // Error.
 393                             x = 10;
 394                         }
 395                         void foo(V v) {}
 396                     }
 397                     """
 398             ),
 399             new TestData(
 400                     "compiler.err.cant.ref.before.ctor.called",
 401                     """
 402                     value class V {
 403                         int x;
 404                         V() {
 405                             x = 10;
 406                             foo(this); // error
 407                         }
 408                         void foo(V v) {}
 409                     }
 410                     """
 411             ),
 412             new TestData(
 413                     "compiler.err.type.found.req",
 414                     """
 415                     interface I {}
 416                     interface VI extends I {}
 417                     class C {}
 418                     value class VC<T extends VC> {
 419                         void m(T t) {
 420                             synchronized(t) {} // error
 421                         }
 422                     }
 423                     """
 424             ),
 425             new TestData(
 426                     "compiler.err.type.found.req",
 427                     """
 428                     interface I {}
 429                     interface VI extends I {}
 430                     class C {}
 431                     value class VC<T extends VC> {
 432                         void foo(Object o) {
 433                             synchronized ((VC & I)o) {} // error
 434                         }
 435                     }
 436                     """
 437             ),
 438             new TestData(
 439                     // OK if the value class is abstract
 440                     """
 441                     interface I {}
 442                     abstract value class VI implements I {}
 443                     class C {}
 444                     value class VC<T extends VC> {
 445                         void bar(Object o) {
 446                             synchronized ((VI & I)o) {} // error
 447                         }
 448                     }
 449                     """
 450             ),
 451             new TestData(
 452                     "compiler.err.type.found.req", // --enable-preview -source"
 453                     """
 454                     class V {
 455                         final Integer val = Integer.valueOf(42);
 456                         void test() {
 457                             synchronized (val) { // error
 458                             }
 459                         }
 460                     }
 461                     """
 462             ),
 463             new TestData(
 464                     "compiler.err.type.found.req", // --enable-preview -source"
 465                     """
 466                     import java.time.*;
 467                     class V {
 468                         final Duration val = Duration.ZERO;
 469                         void test() {
 470                             synchronized (val) { // warn
 471                             }
 472                         }
 473                     }
 474                     """,
 475                     false // cant do local as there is an import statement
 476             ),
 477             new TestData(
 478                     "compiler.warn.attempt.to.synchronize.on.instance.of.value.based.class", // empty options
 479                     """
 480                     class V {
 481                         final Integer val = Integer.valueOf(42);
 482                         void test() {
 483                             synchronized (val) { // warn
 484                             }
 485                         }
 486                     }
 487                     """,
 488                     new String[] {}
 489             ),
 490             new TestData(
 491                     "compiler.warn.attempt.to.synchronize.on.instance.of.value.based.class", // --source
 492                     """
 493                     class V {
 494                         final Integer val = Integer.valueOf(42);
 495                         void test() {
 496                             synchronized (val) { // warn
 497                             }
 498                         }
 499                     }
 500                     """,
 501                     new String[] {"--source", Integer.toString(Runtime.version().feature())}
 502             ),
 503             new TestData(
 504                     "compiler.warn.attempt.to.synchronize.on.instance.of.value.based.class", // --source
 505                     """
 506                     class V {
 507                         final Integer val = Integer.valueOf(42);
 508                         void test() {
 509                             synchronized (val) { // warn
 510                             }
 511                         }
 512                     }
 513                     """,
 514                     new String[] {"--source", Integer.toString(Runtime.version().feature())}
 515             ),
 516             new TestData(
 517                     "compiler.err.illegal.combination.of.modifiers", // --enable-preview -source"
 518                     """
 519                     value class V {
 520                         volatile int f = 1;
 521                     }
 522                     """
 523             )
 524     );
 525 
 526     @Test
 527     void testSemanticsViolations() {
 528         testHelper(semanticsViolations);
 529     }
 530 
 531     private static final List<TestData> sealedClassesData = List.of(
 532             new TestData(
 533                     """
 534                     abstract sealed value class SC {}
 535                     value class VC extends SC {}
 536                     """,
 537                     false // local sealed classes are not allowed
 538             ),
 539             new TestData(
 540                     """
 541                     abstract sealed interface SI {}
 542                     value class VC implements SI {}
 543                     """,
 544                     false // local sealed classes are not allowed
 545             ),
 546             new TestData(
 547                     """
 548                     abstract sealed class SC {}
 549                     final class IC extends SC {}
 550                     non-sealed class IC2 extends SC {}
 551                     final class IC3 extends IC2 {}
 552                     """,
 553                     false
 554             ),
 555             new TestData(
 556                     """
 557                     abstract sealed interface SI {}
 558                     final class IC implements SI {}
 559                     non-sealed class IC2 implements SI {}
 560                     final class IC3 extends IC2 {}
 561                     """,
 562                     false // local sealed classes are not allowed
 563             ),
 564             new TestData(
 565                     "compiler.err.non.abstract.value.class.cant.be.sealed.or.non.sealed",
 566                     """
 567                     abstract sealed value class SC {}
 568                     non-sealed value class VC extends SC {}
 569                     """,
 570                     false
 571             ),
 572             new TestData(
 573                     "compiler.err.non.abstract.value.class.cant.be.sealed.or.non.sealed",
 574                     """
 575                     sealed value class SI {}
 576                     """,
 577                     false
 578             ),
 579             new TestData(
 580                     """
 581                     sealed abstract value class SI {}
 582                     value class V extends SI {}
 583                     """,
 584                     false
 585             ),
 586             new TestData(
 587                     """
 588                     sealed abstract value class SI permits V {}
 589                     value class V extends SI {}
 590                     """,
 591                     false
 592             ),
 593             new TestData(
 594                     """
 595                     sealed interface I {}
 596                     non-sealed abstract value class V implements I {}
 597                     """,
 598                     false
 599             ),
 600             new TestData(
 601                     """
 602                     sealed interface I permits V {}
 603                     non-sealed abstract value class V implements I {}
 604                     """,
 605                     false
 606             )
 607     );
 608 
 609     @Test
 610     void testInteractionWithSealedClasses() {
 611         testHelper(sealedClassesData);
 612     }
 613 
 614     @Test
 615     void testCheckClassFileFlags() throws Exception {
 616         for (String source : List.of(
 617                 """
 618                 interface I {}
 619                 class Test {
 620                     I i = new I() {};
 621                 }
 622                 """,
 623                 """
 624                 class C {}
 625                 class Test {
 626                     C c = new C() {};
 627                 }
 628                 """,
 629                 """
 630                 class Test {
 631                     Object o = new Object() {};
 632                 }
 633                 """,
 634                 """
 635                 class Test {
 636                     abstract class Inner {}
 637                 }
 638                 """
 639         )) {
 640             File dir = assertOK(true, source);
 641             for (final File fileEntry : dir.listFiles()) {
 642                 if (fileEntry.getName().contains("$")) {
 643                     var classFile = ClassFile.of().parse(fileEntry.toPath());
 644                     Assert.check(classFile.flags().has(AccessFlag.IDENTITY));
 645                 }
 646             }
 647         }
 648 
 649         for (String source : List.of(
 650                 """
 651                 class C {}
 652                 """,
 653                 """
 654                 abstract class A {
 655                     int i;
 656                 }
 657                 """,
 658                 """
 659                 abstract class A {
 660                     synchronized void m() {}
 661                 }
 662                 """,
 663                 """
 664                 class C {
 665                     synchronized void m() {}
 666                 }
 667                 """,
 668                 """
 669                 abstract class A {
 670                     int i;
 671                     { i = 0; }
 672                 }
 673                 """,
 674                 """
 675                 abstract class A {
 676                     A(int i) {}
 677                 }
 678                 """,
 679                 """
 680                     enum E {}
 681                 """,
 682                 """
 683                     record R() {}
 684                 """
 685         )) {
 686             File dir = assertOK(true, source);
 687             for (final File fileEntry : dir.listFiles()) {
 688                 var classFile = ClassFile.of().parse(fileEntry.toPath());
 689                 Assert.check(classFile.flags().has(AccessFlag.IDENTITY));
 690             }
 691         }
 692 
 693         {
 694             String source =
 695                     """
 696                     abstract value class A {}
 697                     value class Sub extends A {} //implicitly final
 698                     """;
 699             File dir = assertOK(true, source);
 700             for (final File fileEntry : dir.listFiles()) {
 701                 var classFile = ClassFile.of().parse(fileEntry.toPath());
 702                 switch (classFile.thisClass().asInternalName()) {
 703                     case "Sub":
 704                         Assert.check((classFile.flags().flagsMask() & (Flags.FINAL)) != 0);
 705                         break;
 706                     case "A":
 707                         Assert.check((classFile.flags().flagsMask() & (Flags.ABSTRACT)) != 0);
 708                         break;
 709                     default:
 710                         throw new AssertionError("you shoulnd't be here");
 711                 }
 712             }
 713         }
 714 
 715         for (String source : List.of(
 716                 """
 717                 value class V {
 718                     int i = 0;
 719                     static int j;
 720                 }
 721                 """,
 722                 """
 723                 abstract value class A {
 724                     static int j;
 725                 }
 726 
 727                 value class V extends A {
 728                     int i = 0;
 729                 }
 730                 """
 731         )) {
 732             File dir = assertOK(true, source);
 733             for (final File fileEntry : dir.listFiles()) {
 734                 var classFile = ClassFile.of().parse(fileEntry.toPath());
 735                 for (var field : classFile.fields()) {
 736                     if (!field.flags().has(AccessFlag.STATIC)) {
 737                         Set<AccessFlag> fieldFlags = field.flags().flags();
 738                         Assert.check(fieldFlags.size() == 2 && fieldFlags.contains(AccessFlag.FINAL) && fieldFlags.contains(AccessFlag.STRICT_INIT));
 739                     }
 740                 }
 741             }
 742         }
 743     }
 744 
 745     @Test
 746     void testConstruction() throws Exception {
 747         record Data(String src, boolean isRecord) {
 748             Data(String src) {
 749                 this(src, false);
 750             }
 751         }
 752         for (Data data : List.of(
 753                 new Data(
 754                     """
 755                     value class Test {
 756                         int i = 100;
 757                     }
 758                     """),
 759                 new Data(
 760                     """
 761                     value class Test {
 762                         int i;
 763                         Test() {
 764                             i = 100;
 765                         }
 766                     }
 767                     """),
 768                 new Data(
 769                     """
 770                     value class Test {
 771                         int i;
 772                         Test() {
 773                             i = 100;
 774                             super();
 775                         }
 776                     }
 777                     """),
 778                 new Data(
 779                     """
 780                     value class Test {
 781                         int i;
 782                         Test() {
 783                             this.i = 100;
 784                             super();
 785                         }
 786                     }
 787                     """),
 788                 new Data(
 789                     """
 790                     value record Test(int i) {}
 791                     """, true)
 792         )) {
 793             if (!data.isRecord()) {
 794                 checkMnemonicsFor(data.src, "aload_0,bipush,putfield,aload_0,invokespecial,return");
 795             } else {
 796                 checkMnemonicsFor(data.src, "aload_0,iload_1,putfield,aload_0,invokespecial,return");
 797             }
 798         }
 799 
 800         String source =
 801                 """
 802                 value class Test {
 803                     int i = 100;
 804                     int j = 0;
 805                     {
 806                         System.out.println(j);
 807                     }
 808                 }
 809                 """;
 810         checkMnemonicsFor(
 811                 """
 812                 value class Test {
 813                     int i = 100;
 814                     int j = 0;
 815                     {
 816                         System.out.println(j);
 817                     }
 818                 }
 819                 """,
 820                 "aload_0,bipush,putfield,aload_0,iconst_0,putfield,aload_0,invokespecial,getstatic,iconst_0,invokevirtual,return"
 821         );
 822 
 823         assertFail("compiler.err.cant.ref.before.ctor.called",
 824                 """
 825                 value class Test {
 826                     Test() {
 827                         m();
 828                     }
 829                     void m() {}
 830                 }
 831                 """
 832         );
 833         assertFail("compiler.err.strict.field.not.have.been.initialized.before.super",
 834                 """
 835                 value class Test {
 836                     int i;
 837                     Test() {
 838                         super();
 839                         this.i = i;
 840                     }
 841                 }
 842                 """
 843         );
 844         assertOK(
 845                 """
 846                 class UnrelatedThisLeak {
 847                     value class V {
 848                         int f;
 849                         V() {
 850                             UnrelatedThisLeak x = UnrelatedThisLeak.this;
 851                             f = 10;
 852                             x = UnrelatedThisLeak.this;
 853                         }
 854                     }
 855                 }
 856                 """
 857         );
 858         assertFail("compiler.err.cant.ref.before.ctor.called",
 859                 """
 860                 value class Test {
 861                     Test t = null;
 862                     Runnable r = () -> { System.err.println(t); }; // cant reference `t` from a lambda expression in the prologue
 863                 }
 864                 """
 865         );
 866         assertFail("compiler.err.strict.field.not.have.been.initialized.before.super",
 867                 """
 868                 value class Test {
 869                     int f;
 870                     {
 871                         f = 1;
 872                     }
 873                 }
 874                 """
 875         );
 876         assertFail("compiler.err.var.might.not.have.been.initialized",
 877                 """
 878                 value class V {
 879                     int x;
 880                     int y = x + 1; // error
 881                     V() {
 882                         x = 12;
 883                         // super();
 884                     }
 885                 }
 886                 """
 887         );
 888         assertOK("""
 889                 value class V {
 890                     int y;
 891                     int x = (y = 1);
 892 
 893                     V() {
 894                         int z = y; // ok
 895                         super();
 896                     }
 897                 }
 898                 """);
 899         assertFail("compiler.err.cant.ref.before.ctor.called",
 900                 """
 901                 value class V2 {
 902                     int x;
 903                     V2() { this(x = 3); } // error
 904                     V2(int i) { x = 4; }
 905                 }
 906                 """
 907         );
 908         assertOK(
 909                 """
 910                 abstract value class AV1 {
 911                     AV1(int i) {}
 912                 }
 913                 value class V3 extends AV1 {
 914                     int x;
 915                     V3() {
 916                         super(x = 3); // ok
 917                     }
 918                 }
 919                 """
 920         );
 921         assertFail("compiler.err.var.might.not.have.been.initialized",
 922                 """
 923                 value class V4 {
 924                     int x;
 925                     int y = x + 1;
 926                     V4() {
 927                         x = 12;
 928                     }
 929                     V4(int i) {
 930                         x = i;
 931                     }
 932                 }
 933                 """
 934         );
 935         assertOK(
 936                 """
 937                 value class V {
 938                     final int x = "abc".length();
 939                     { System.out.println(x); }
 940                 }
 941                 """
 942         );
 943         assertFail("compiler.err.illegal.forward.ref",
 944                 """
 945                 value class V {
 946                     { System.out.println(x); }
 947                     final int x = "abc".length();
 948                 }
 949                 """
 950         );
 951         assertOK(
 952                 """
 953                 value class V {
 954                     int x = "abc".length();
 955                     int y = x;
 956                 }
 957                 """
 958         );
 959         assertOK(
 960                 """
 961                 value class V {
 962                     int x = "abc".length();
 963                     { int y = x; }
 964                 }
 965                 """
 966         );
 967         assertOK(
 968                 """
 969                 value class V {
 970                     String s1;
 971                     { System.out.println(s1); }
 972                     String s2 = (s1 = "abc");
 973                 }
 974                 """
 975         );
 976 
 977         source =
 978             """
 979             value class V {
 980                 int i = 1;
 981                 int y;
 982                 V() {
 983                     y = 2;
 984                 }
 985             }
 986             """;
 987         {
 988             File dir = assertOK(true, source);
 989             File fileEntry = dir.listFiles()[0];
 990             var expectedCodeSequence = "putfield i,putfield y";
 991             var classFile = ClassFile.of().parse(fileEntry.toPath());
 992             for (var method : classFile.methods()) {
 993                 if (method.methodName().equalsString("<init>")) {
 994                     var code = method.findAttribute(Attributes.code()).orElseThrow();
 995                     List<String> mnemonics = new ArrayList<>();
 996                     for (var coe : code) {
 997                         if (coe instanceof FieldInstruction inst && inst.opcode() == Opcode.PUTFIELD) {
 998                             mnemonics.add(inst.opcode().name().toLowerCase(Locale.ROOT) + " " + inst.name());
 999                         }
1000                     }
1001                     var foundCodeSequence = String.join(",", mnemonics);
1002                     Assert.check(expectedCodeSequence.equals(foundCodeSequence), "found " + foundCodeSequence);
1003                 }
1004             }
1005         }
1006 
1007         // check that javac doesn't generate duplicate initializer code
1008         checkMnemonicsFor(
1009                 """
1010                 value class Test {
1011                     static class Foo {
1012                         int x;
1013                         int getX() { return x; }
1014                     }
1015                     Foo data = new Foo();
1016                     Test() { // we will check that: `data = new Foo();` is generated only once
1017                         data.getX();
1018                         super();
1019                     }
1020                 }
1021                 """,
1022                 "new,dup,invokespecial,astore_1,aload_1,invokevirtual,pop,aload_0,aload_1,putfield,aload_0,invokespecial,return"
1023         );
1024 
1025         assertFail("compiler.err.invalid.canonical.constructor.in.record",
1026                 """
1027                 record R(int x) {
1028                     public R {
1029                         super();
1030                     }
1031                 }
1032                 """
1033         );
1034 
1035         assertFail("compiler.err.invalid.canonical.constructor.in.record",
1036                 """
1037                 record R(int x) {
1038                     public R {
1039                         this();
1040                     }
1041                     public R() {
1042                         this(1);
1043                     }
1044                 }
1045                 """
1046         );
1047 
1048         assertOK(
1049                 """
1050                 record R(int x) {
1051                     public R(int x) {
1052                         this.x = x;
1053                         super();
1054                     }
1055                 }
1056                 """
1057         );
1058     }
1059 
1060     @Test
1061     void testSyntheticCapturesInEarlyInitializers() throws Exception {
1062         File dir = assertOK(true, """
1063           class Test {
1064               class Inner { }
1065               value class V {
1066                   Object o = new Inner(); // this$0 ref
1067                   V() { super(); }
1068               }
1069               public static void main(String[] args) {
1070                   Test t = new Test();
1071                   t.new V();
1072               }
1073           }
1074           """);
1075         invokeMain("Test", dir);
1076 
1077         dir = assertOK(true, """
1078           class Test {
1079               static class Box { Box(int i) { } }
1080               static void test() {
1081                   int x = 42;
1082                   value class V {
1083                       Object o = new Box(x); // capture ref
1084                       V() { super(); }
1085                   }
1086                   new V();
1087               }
1088               public static void main(String[] args) {
1089                   test();
1090               }
1091           }
1092           """);
1093         invokeMain("Test", dir);
1094 
1095         dir = assertOK(true, """
1096           import java.util.function.Supplier;
1097 
1098           class Test {
1099               static int seen;
1100               static class Box { Box(int i) { } }
1101               static void test() {
1102                   int x = 42;
1103                   value class V {
1104                       Supplier<Box> s = () -> new Box(x); // lambda capture ref
1105                       V() { super(); }
1106                   }
1107                   new V().s.get();
1108               }
1109               public static void main(String[] args) {
1110                   test();
1111               }
1112           }
1113           """);
1114         invokeMain("Test", dir);
1115 
1116         dir = assertOK(true, """
1117           import java.util.function.Supplier;
1118 
1119           class Test {
1120               static int seen;
1121               class Inner { }
1122               value class V {
1123                   Supplier<Inner> s = () -> new Inner(); // lambda this$0 ref
1124                   V() { super(); }
1125               }
1126               public static void main(String[] args) {
1127                   Test t = new Test();
1128                   t.new V().s.get();
1129               }
1130           }
1131           """);
1132         invokeMain("Test", dir);
1133     }
1134 
1135     void invokeMain(String className, File dir) throws Exception {
1136         Method method = loadClass(className, dir)
1137                 .getDeclaredMethod("main", String[].class);
1138         method.setAccessible(true);
1139         method.invoke(null, (Object) new String[0]);
1140     }
1141 
1142     void checkMnemonicsFor(String source, String expectedMnemonics) throws Exception {
1143         File dir = assertOK(true, source);
1144         for (final File fileEntry : dir.listFiles()) {
1145             var classFile = ClassFile.of().parse(fileEntry.toPath());
1146             if (classFile.thisClass().name().equalsString("Test")) {
1147                 for (var method : classFile.methods()) {
1148                     if (method.methodName().equalsString("<init>")) {
1149                         var code = method.findAttribute(Attributes.code()).orElseThrow();
1150                         List<String> mnemonics = new ArrayList<>();
1151                         for (var coe : code) {
1152                             if (coe instanceof Instruction inst) {
1153                                 mnemonics.add(inst.opcode().name().toLowerCase(Locale.ROOT));
1154                             }
1155                         }
1156                         var foundCodeSequence = String.join(",", mnemonics);
1157                         Assert.check(expectedMnemonics.equals(foundCodeSequence), "found " + foundCodeSequence);
1158                     }
1159                 }
1160             }
1161         }
1162     }
1163 
1164     @Test
1165     void testThisCallingConstructor() throws Exception {
1166         // make sure that this() calling constructors doesn't initialize final fields
1167         String source =
1168                 """
1169                 value class Test {
1170                     int i;
1171                     Test() {
1172                         this(0);
1173                     }
1174 
1175                     Test(int i) {
1176                         this.i = i;
1177                     }
1178                 }
1179                 """;
1180         File dir = assertOK(true, source);
1181         File fileEntry = dir.listFiles()[0];
1182         String expectedCodeSequenceThisCallingConst = "aload_0,iconst_0,invokespecial,return";
1183         String expectedCodeSequenceNonThisCallingConst = "aload_0,iload_1,putfield,aload_0,invokespecial,return";
1184         var classFile = ClassFile.of().parse(fileEntry.toPath());
1185         for (var method : classFile.methods()) {
1186             if (method.methodName().equalsString("<init>")) {
1187                 var code = method.findAttribute(Attributes.code()).orElseThrow();
1188                 List<String> mnemonics = new ArrayList<>();
1189                 for (var coe : code) {
1190                     if (coe instanceof Instruction inst) {
1191                         mnemonics.add(inst.opcode().name().toLowerCase(Locale.ROOT));
1192                     }
1193                 }
1194                 var foundCodeSequence = String.join(",", mnemonics);
1195                 var expected = method.methodTypeSymbol().parameterCount() == 0 ?
1196                         expectedCodeSequenceThisCallingConst : expectedCodeSequenceNonThisCallingConst;
1197                 Assert.check(expected.equals(foundCodeSequence), "found " + foundCodeSequence);
1198             }
1199         }
1200     }
1201 
1202     @Test
1203     void testSelectors() throws Exception {
1204         assertOK(
1205                 """
1206                 value class V {
1207                     void selector() {
1208                         Class<?> c = int.class;
1209                     }
1210                 }
1211                 """
1212         );
1213         assertFail("compiler.err.expected",
1214                 """
1215                 value class V {
1216                     void selector() {
1217                         int i = int.some_selector;
1218                     }
1219                 }
1220                 """
1221         );
1222     }
1223 
1224     @Test
1225     void testNullAssigment() throws Exception {
1226         assertOK(
1227                 """
1228                 value final class V {
1229                     final int x = 10;
1230 
1231                     value final class X {
1232                         final V v;
1233                         final V v2;
1234 
1235                         X() {
1236                             this.v = null;
1237                             this.v2 = null;
1238                         }
1239 
1240                         X(V v) {
1241                             this.v = v;
1242                             this.v2 = v;
1243                         }
1244 
1245                         V foo(X x) {
1246                             x = new X(null);  // OK
1247                             return x.v;
1248                         }
1249                     }
1250                     V bar(X x) {
1251                         x = new X(null); // OK
1252                         return x.v;
1253                     }
1254 
1255                     class Y {
1256                         V v;
1257                         V [] va = { null }; // OK: array initialization
1258                         V [] va2 = new V[] { null }; // OK: array initialization
1259                         void ooo(X x) {
1260                             x = new X(null); // OK
1261                             v = null; // legal assignment.
1262                             va[0] = null; // legal.
1263                             va = new V[] { null }; // legal
1264                         }
1265                     }
1266                 }
1267                 """
1268         );
1269     }
1270 
1271     @Test
1272     void testSerializationWarnings() throws Exception {
1273         String[] previousOptions = getCompileOptions();
1274         try {
1275             setCompileOptions(new String[] {"-Xlint:serial", "--enable-preview", "--source",
1276                     Integer.toString(Runtime.version().feature())});
1277             assertOK(
1278                     """
1279                     import java.io.*;
1280                     abstract value class AVC implements Serializable {}
1281                     """);
1282             assertOKWithWarning("compiler.warn.serializable.value.class.without.write.replace.1",
1283                     """
1284                     import java.io.*;
1285                     value class VC implements Serializable {
1286                         private static final long serialVersionUID = 0;
1287                     }
1288                     """);
1289             assertOKWithWarning("compiler.warn.ineffectual.serial.method.value.class",
1290                     """
1291                     import java.io.*;
1292                     value class VC implements Serializable {
1293                         private static final long serialVersionUID = 0;
1294                         private void writeObject(ObjectOutputStream stream) throws IOException {}
1295                     }
1296                     """);
1297             assertOKWithWarning("compiler.warn.ineffectual.serial.method.value.class",
1298                     """
1299                     import java.io.*;
1300                     value class VC implements Serializable {
1301                         private static final long serialVersionUID = 0;
1302                         private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {}
1303                     }
1304                     """);
1305             assertOKWithWarning("compiler.warn.ineffectual.serial.method.value.class",
1306                     """
1307                     import java.io.*;
1308                     value class VC implements Serializable {
1309                         private static final long serialVersionUID = 0;
1310                         private void readObjectNoData() throws ObjectStreamException {}
1311                     }
1312                     """);
1313             assertOK(
1314                     """
1315                     import java.io.*;
1316                     class C implements Serializable {
1317                         private static final long serialVersionUID = 0;
1318                     }
1319                     """);
1320             assertOK(
1321                     """
1322                     import java.io.*;
1323                     abstract value class Super implements Serializable {
1324                         private static final long serialVersionUID = 0;
1325                         protected Object writeReplace() throws ObjectStreamException {
1326                             return null;
1327                         }
1328                     }
1329                     value class ValueSerializable extends Super {
1330                         private static final long serialVersionUID = 1;
1331                     }
1332                     """);
1333             assertOK(
1334                     """
1335                     import java.io.*;
1336                     abstract value class Super implements Serializable {
1337                         private static final long serialVersionUID = 0;
1338                         Object writeReplace() throws ObjectStreamException {
1339                             return null;
1340                         }
1341                     }
1342                     value class ValueSerializable extends Super {
1343                         private static final long serialVersionUID = 1;
1344                     }
1345                     """);
1346             assertOK(
1347                     """
1348                     import java.io.*;
1349                     abstract value class Super implements Serializable {
1350                         private static final long serialVersionUID = 0;
1351                         public Object writeReplace() throws ObjectStreamException {
1352                             return null;
1353                         }
1354                     }
1355                     value class ValueSerializable extends Super {
1356                         private static final long serialVersionUID = 1;
1357                     }
1358                     """);
1359             assertOKWithWarning("compiler.warn.serializable.value.class.without.write.replace.1",
1360                     """
1361                     import java.io.*;
1362                     abstract value class Super implements Serializable {
1363                         private static final long serialVersionUID = 0;
1364                         private Object writeReplace() throws ObjectStreamException {
1365                             return null;
1366                         }
1367                     }
1368                     value class ValueSerializable extends Super {
1369                         private static final long serialVersionUID = 1;
1370                     }
1371                     """);
1372             assertOKWithWarning("compiler.warn.serializable.value.class.without.write.replace.2",
1373                     """
1374                     import java.io.*;
1375                     abstract value class Super implements Serializable {
1376                         private static final long serialVersionUID = 0;
1377                         private Object writeReplace() throws ObjectStreamException {
1378                             return null;
1379                         }
1380                     }
1381                     class Serializable1 extends Super {
1382                         private static final long serialVersionUID = 1;
1383                     }
1384                     class Serializable2 extends Serializable1 {
1385                         private static final long serialVersionUID = 1;
1386                     }
1387                     """);
1388             assertOK(
1389                     """
1390                     import java.io.*;
1391                     abstract value class Super implements Serializable {
1392                         private static final long serialVersionUID = 0;
1393                         Object writeReplace() throws ObjectStreamException {
1394                             return null;
1395                         }
1396                     }
1397                     class ValueSerializable extends Super {
1398                         private static final long serialVersionUID = 1;
1399                     }
1400                     """);
1401             assertOK(
1402                     """
1403                     import java.io.*;
1404                     abstract value class Super implements Serializable {
1405                         private static final long serialVersionUID = 0;
1406                         public Object writeReplace() throws ObjectStreamException {
1407                             return null;
1408                         }
1409                     }
1410                     class ValueSerializable extends Super {
1411                         private static final long serialVersionUID = 1;
1412                     }
1413                     """);
1414             assertOK(
1415                     """
1416                     import java.io.*;
1417                     abstract value class Super implements Serializable {
1418                         private static final long serialVersionUID = 0;
1419                         protected Object writeReplace() throws ObjectStreamException {
1420                             return null;
1421                         }
1422                     }
1423                     class ValueSerializable extends Super {
1424                         private static final long serialVersionUID = 1;
1425                     }
1426                     """);
1427             assertOK(
1428                     """
1429                     import java.io.*;
1430                     value record ValueRecord() implements Serializable {
1431                         private static final long serialVersionUID = 1;
1432                     }
1433                     """);
1434             assertOK(
1435                     // Number is a special case, no warning for identity classes extending it
1436                     """
1437                     class NumberSubClass extends Number {
1438                         private static final long serialVersionUID = 0L;
1439                         @Override
1440                         public double doubleValue() { return 0; }
1441                         @Override
1442                         public int intValue() { return 0; }
1443                         @Override
1444                         public long longValue() { return 0; }
1445                         @Override
1446                         public float floatValue() { return 0; }
1447                     }
1448                     """
1449             );
1450         } finally {
1451             setCompileOptions(previousOptions);
1452         }
1453     }
1454 
1455     @Test
1456     void testAssertUnsetFieldsSMEntry() throws Exception {
1457         String[] previousOptions = getCompileOptions();
1458         try {
1459             String[] testOptions = {
1460                     "--enable-preview",
1461                     "-source", Integer.toString(Runtime.version().feature()),
1462                     "-XDnoLocalProxyVars",
1463                     "-XDdebug.stackmap",
1464             };
1465             setCompileOptions(testOptions);
1466 
1467             record Data(String src, int[] expectedFrameTypes, String[][] expectedUnsetFields) {}
1468             for (Data data : List.of(
1469                     new Data(
1470                             """
1471                             value class Test {
1472                                 final int x;
1473                                 final int y;
1474                                 Test(boolean a, boolean b) {
1475                                     if (a) { // early_larval {x, y}
1476                                         x = 1;
1477                                         if (b) { // early_larval {y}
1478                                             y = 1;
1479                                         } else { // early_larval {y}
1480                                             y = 2;
1481                                         }
1482                                     } else { // early_larval {x, y}
1483                                         x = y = 3;
1484                                     }
1485                                     super();
1486                                 }
1487                             }
1488                             """,
1489                             // three unset_fields entries, entry type 246, are expected in the stackmap table
1490                             new int[] {246, 246, 246},
1491                             // expected fields for each of them:
1492                             new String[][] { new String[] { "y:I" }, new String[] { "x:I", "y:I" }, new String[] {} }
1493                     ),
1494                     new Data(
1495                             """
1496                             value class Test {
1497                                 final int x;
1498                                 final int y;
1499                                 Test(int n) {
1500                                     switch(n) {
1501                                         case 2:
1502                                             x = y = 2;
1503                                             break;
1504                                         default:
1505                                             x = y = 100;
1506                                             break;
1507                                     }
1508                                     super();
1509                                 }
1510                             }
1511                             """,
1512                             // here we expect only one
1513                             new int[] {20, 12, 246},
1514                             // stating that no field is unset
1515                             new String[][] { new String[] {} }
1516                     ),
1517                     new Data(
1518                             """
1519                             value class Test {
1520                                 final int x;
1521                                 final int y;
1522                                 Test(int n) {
1523                                     if (n % 3 == 0) {
1524                                         x = n / 3;
1525                                     } else { // no unset change
1526                                         x = n + 2;
1527                                     } // early_larval {y}
1528                                     y = n >>> 3;
1529                                     super();
1530                                     if ((char) n != n) {
1531                                         n -= 5;
1532                                     } // no uninitializedThis - automatically cleared unsets
1533                                     Math.abs(n);
1534                                 }
1535                             }
1536                             """,
1537                             // here we expect only one, none for the post-larval frame
1538                             new int[] {16, 246, 255},
1539                             // stating that y is unset when if-else finishes
1540                             new String[][] { new String[] {"y:I"} }
1541                     )
1542             )) {
1543                 File dir = assertOK(true, data.src());
1544                 for (final File fileEntry : dir.listFiles()) {
1545                     var classFile = ClassFile.of().parse(fileEntry.toPath());
1546                     for (var method : classFile.methods()) {
1547                         if (method.methodName().equalsString(ConstantDescs.INIT_NAME)) {
1548                             var code = method.findAttribute(Attributes.code()).orElseThrow();
1549                             var stackMapTable = code.findAttribute(Attributes.stackMapTable()).orElseThrow();
1550                             Assert.check(data.expectedFrameTypes().length == stackMapTable.entries().size(), "unexpected stackmap length");
1551                             int entryIndex = 0;
1552                             int expectedUnsetFieldsIndex = 0;
1553                             for (var entry : stackMapTable.entries()) {
1554                                 Assert.check(data.expectedFrameTypes()[entryIndex++] == entry.frameType(), "expected " + data.expectedFrameTypes()[entryIndex - 1] + " found " + entry.frameType());
1555                                 if (entry.frameType() == 246) {
1556                                     Assert.check(data.expectedUnsetFields()[expectedUnsetFieldsIndex].length == entry.unsetFields().size());
1557                                     int index = 0;
1558                                     for (var nat : entry.unsetFields()) {
1559                                         String unsetStr = nat.name() + ":" + nat.type();
1560                                         Assert.check(data.expectedUnsetFields()[expectedUnsetFieldsIndex][index++].equals(unsetStr));
1561                                     }
1562                                     expectedUnsetFieldsIndex++;
1563                                 }
1564                             }
1565                         }
1566                     }
1567                 }
1568             }
1569         } finally {
1570             setCompileOptions(previousOptions);
1571         }
1572     }
1573 
1574     @Test
1575     void testLocalProxyVars() throws Exception {
1576         checkMnemonicsFor(
1577                     """
1578                     value class Test {
1579                         int i;
1580                         int j;
1581                         Test() {// javac should generate a proxy local var for `i`
1582                             i = 1;
1583                             j = i; // as here `i` is being read during the early construction phase, use the local var instead
1584                             super();
1585                         }
1586                     }
1587                     """,
1588                     "iconst_1,istore_1,aload_0,iload_1,putfield,aload_0,iload_1,putfield,aload_0,invokespecial,return");
1589         checkMnemonicsFor(
1590                     """
1591                     value class Test {
1592                         static String s0;
1593                         String s;
1594                         String ss;
1595                         Test(boolean b) {
1596                             s0 = null;
1597                             s = s0; // no local proxy variable for `s0` as it is static
1598                             ss = s; // but there should be a local proxy for `s`
1599                             super();
1600                         }
1601                     }
1602                     """,
1603                     "aconst_null,putstatic,getstatic,astore_2,aload_0,aload_2,putfield,aload_0,aload_2," +
1604                     "putfield,aload_0,invokespecial,return");
1605     }
1606 
1607     @Test
1608     void testIdentityRecordUsesPreview() throws Exception {
1609         String source_withComponents =
1610                 """
1611                 record IdentityRecord(int x, int y) {
1612                     IdentityRecord(int x, int y) {
1613                         this.x = x;
1614                         if (x < 0) {
1615                             y = -y;
1616                         }
1617                         this.y = y;
1618                     }
1619                 }
1620                 """;
1621         String source_noComponents =
1622                 """
1623                 record IdentityRecord() {}
1624                 """;
1625 
1626         // --enable-preview - use preview VM features
1627         String[] previousOptions = getCompileOptions();
1628         try {
1629             setCompileOptions("--enable-preview",
1630                     "-source", Integer.toString(Runtime.version().feature()),
1631                     "-Xlint:preview");
1632 
1633             File dir = assertOK(true, source_withComponents);
1634             File classFile = new File(dir, "IdentityRecord.class");
1635             Assert.check(classFile.exists(), "missing class file");
1636             var classModel = ClassFile.of().parse(classFile.toPath());
1637             Assert.check(classModel.minorVersion() == ClassFile.PREVIEW_MINOR_VERSION,
1638                     "identity records should produce preview class files when compiled with preview enabled");
1639             Assert.check(classModel.fields().stream().allMatch(f -> f.flags().has(AccessFlag.STRICT_INIT)),
1640                     "identity record component instance field should be strictly initialized with preview enabled");
1641             var constructor = classModel.methods()
1642                     .stream()
1643                     .filter(mm -> mm.methodName().equalsString(ConstantDescs.INIT_NAME))
1644                     .findFirst()
1645                     .orElseThrow();
1646             System.err.println(constructor.toDebugString());
1647             var stackMaps = constructor.code().orElseThrow().findAttribute(Attributes.stackMapTable()).orElseThrow();
1648             Assert.check(stackMaps.entries().getFirst().frameType() == 246,
1649                     "identity record constructor StackMapTable should declare unset fields with preview enabled");
1650 
1651             dir = assertOK(true, source_noComponents);
1652             classFile = new File(dir, "IdentityRecord.class");
1653             Assert.check(classFile.exists(), "missing class file");
1654             Assert.check(ClassFile.of().parse(classFile.toPath()).minorVersion() == 0,
1655                     "identity records with no components should not produce preview class files even with preview enabled");
1656         } finally {
1657             setCompileOptions(previousOptions);
1658         }
1659 
1660         // No preview - no preview VM features
1661         previousOptions = getCompileOptions();
1662         try {
1663             setCompileOptions("-source", "28");
1664 
1665             File dir = assertOK(true, source_withComponents);
1666             File classFile = new File(dir, "IdentityRecord.class");
1667             Assert.check(classFile.exists(), "missing class file");
1668             var classModel = ClassFile.of().parse(classFile.toPath());
1669             Assert.check(classModel.minorVersion() == 0,
1670                     "identity records should not preview class files for older releases");
1671             Assert.check(classModel.fields().stream().noneMatch(f -> f.flags().has(AccessFlag.STRICT_INIT)),
1672                     "identity record component instance field should not be strictly initialized for older releases");
1673             var constructor = classModel.methods()
1674                     .stream()
1675                     .filter(mm -> mm.methodName().equalsString(ConstantDescs.INIT_NAME))
1676                     .findFirst()
1677                     .orElseThrow();
1678             var stackMaps = constructor.code().orElseThrow().findAttribute(Attributes.stackMapTable()).orElseThrow();
1679             Assert.check(stackMaps.entries().getFirst().frameType() != 246,
1680                     "identity record constructor StackMapTable should not declare unset fields for older releases");
1681 
1682             dir = assertOK(true, source_noComponents);
1683             classFile = new File(dir, "IdentityRecord.class");
1684             Assert.check(classFile.exists(), "missing class file");
1685             Assert.check(ClassFile.of().parse(classFile.toPath()).minorVersion() == 0,
1686                     "identity records with no components should not produce preview class files for older releases");
1687         } finally {
1688             setCompileOptions(previousOptions);
1689         }
1690     }
1691 }