Handle compact record constructors

https://github.com/bazelbuild/bazel/issues/14249

PiperOrigin-RevId: 408952084
diff --git a/java/com/google/turbine/binder/TypeBinder.java b/java/com/google/turbine/binder/TypeBinder.java
index ccc0ad3..e2011e2 100644
--- a/java/com/google/turbine/binder/TypeBinder.java
+++ b/java/com/google/turbine/binder/TypeBinder.java
@@ -251,7 +251,7 @@
     ImmutableList.Builder<MethodInfo> methods =
         ImmutableList.<MethodInfo>builder()
             .addAll(syntheticMethods(syntheticMethods, components))
-            .addAll(bindMethods(scope, base.decl().members()));
+            .addAll(bindMethods(scope, base.decl().members(), components));
     if (base.kind().equals(TurbineTyKind.RECORD)) {
       methods.addAll(syntheticRecordMethods(syntheticMethods, components));
     }
@@ -563,18 +563,22 @@
     return result.build();
   }
 
-  private List<MethodInfo> bindMethods(CompoundScope scope, ImmutableList<Tree> members) {
+  private List<MethodInfo> bindMethods(
+      CompoundScope scope,
+      ImmutableList<Tree> members,
+      ImmutableList<RecordComponentInfo> components) {
     List<MethodInfo> methods = new ArrayList<>();
     int idx = 0;
     for (Tree member : members) {
       if (member.kind() == Tree.Kind.METH_DECL) {
-        methods.add(bindMethod(idx++, scope, (Tree.MethDecl) member));
+        methods.add(bindMethod(idx++, scope, (MethDecl) member, components));
       }
     }
     return methods;
   }
 
-  private MethodInfo bindMethod(int idx, CompoundScope scope, Tree.MethDecl t) {
+  private MethodInfo bindMethod(
+      int idx, CompoundScope scope, MethDecl t, ImmutableList<RecordComponentInfo> components) {
 
     MethodSymbol sym = new MethodSymbol(idx, owner, t.name().value());
 
@@ -604,8 +608,26 @@
     if (name.equals("<init>")) {
       if (hasEnclosingInstance(base)) {
         parameters.add(enclosingInstanceParameter(sym));
-      } else if (base.kind() == TurbineTyKind.ENUM && name.equals("<init>")) {
-        parameters.addAll(enumCtorParams(sym));
+      } else {
+        switch (base.kind()) {
+          case ENUM:
+            parameters.addAll(enumCtorParams(sym));
+            break;
+          case RECORD:
+            if (t.mods().contains(TurbineModifier.COMPACT_CTOR)) {
+              for (RecordComponentInfo component : components) {
+                parameters.add(
+                    new ParamInfo(
+                        new ParamSymbol(sym, component.name()),
+                        component.type(),
+                        component.annotations(),
+                        component.access()));
+              }
+            }
+            break;
+          default:
+            break;
+        }
       }
     }
     ParamInfo receiver = null;
diff --git a/java/com/google/turbine/model/TurbineFlag.java b/java/com/google/turbine/model/TurbineFlag.java
index 6e8d64b..3e68a5e 100644
--- a/java/com/google/turbine/model/TurbineFlag.java
+++ b/java/com/google/turbine/model/TurbineFlag.java
@@ -58,5 +58,8 @@
   public static final int ACC_SEALED = 1 << 19;
   public static final int ACC_NON_SEALED = 1 << 20;
 
+  /** Compact record constructor. */
+  public static final int ACC_COMPACT_CTOR = 1 << 21;
+
   private TurbineFlag() {}
 }
diff --git a/java/com/google/turbine/parse/Parser.java b/java/com/google/turbine/parse/Parser.java
index ed8958e..c370ad8 100644
--- a/java/com/google/turbine/parse/Parser.java
+++ b/java/com/google/turbine/parse/Parser.java
@@ -846,6 +846,25 @@
           name = ident;
           return ImmutableList.of(methodRest(pos, access, annos, typaram, null, name));
         }
+      case LBRACE:
+        {
+          dropBlocks();
+          name = new Ident(position, CTOR_NAME);
+          String javadoc = lexer.javadoc();
+          access.add(TurbineModifier.COMPACT_CTOR);
+          return ImmutableList.<Tree>of(
+              new MethDecl(
+                  pos,
+                  access,
+                  annos,
+                  typaram,
+                  /* ret= */ Optional.empty(),
+                  name,
+                  /* params= */ ImmutableList.of(),
+                  /* exntys= */ ImmutableList.of(),
+                  /* defaultValue= */ Optional.empty(),
+                  javadoc));
+        }
       case IDENT:
         {
           result =
diff --git a/java/com/google/turbine/tree/Pretty.java b/java/com/google/turbine/tree/Pretty.java
index 788ab58..4ebc04f 100644
--- a/java/com/google/turbine/tree/Pretty.java
+++ b/java/com/google/turbine/tree/Pretty.java
@@ -544,6 +544,7 @@
         case ACC_ANNOTATION:
         case ACC_SYNTHETIC:
         case ACC_BRIDGE:
+        case COMPACT_CTOR:
           break;
       }
     }
diff --git a/java/com/google/turbine/tree/TurbineModifier.java b/java/com/google/turbine/tree/TurbineModifier.java
index e5153b9..2bfe53e 100644
--- a/java/com/google/turbine/tree/TurbineModifier.java
+++ b/java/com/google/turbine/tree/TurbineModifier.java
@@ -47,7 +47,8 @@
   DEFAULT(TurbineFlag.ACC_DEFAULT),
   TRANSITIVE(TurbineFlag.ACC_TRANSITIVE),
   SEALED(TurbineFlag.ACC_SEALED),
-  NON_SEALED(TurbineFlag.ACC_NON_SEALED);
+  NON_SEALED(TurbineFlag.ACC_NON_SEALED),
+  COMPACT_CTOR(TurbineFlag.ACC_COMPACT_CTOR);
 
   private final int flag;
 
diff --git a/javatests/com/google/turbine/lower/LowerIntegrationTest.java b/javatests/com/google/turbine/lower/LowerIntegrationTest.java
index db73ec0..b45559f 100644
--- a/javatests/com/google/turbine/lower/LowerIntegrationTest.java
+++ b/javatests/com/google/turbine/lower/LowerIntegrationTest.java
@@ -44,9 +44,7 @@
 public class LowerIntegrationTest {
 
   private static final ImmutableMap<String, Integer> SOURCE_VERSION =
-      ImmutableMap.of(
-          "record.test", 16,
-          "sealed.test", 17);
+      ImmutableMap.of("record.test", 16, "record2.test", 16, "sealed.test", 17);
 
   @Parameters(name = "{index}: {0}")
   public static Iterable<Object[]> parameters() {
@@ -265,6 +263,7 @@
       "rawfbound.test",
       "receiver_param.test",
       "record.test",
+      "record2.test",
       "rek.test",
       "samepkg.test",
       "sealed.test",
diff --git a/javatests/com/google/turbine/lower/testdata/record2.test b/javatests/com/google/turbine/lower/testdata/record2.test
new file mode 100644
index 0000000..af4093e
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/record2.test
@@ -0,0 +1,27 @@
+=== Records.java ===
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import java.util.Objects;
+
+class Records {
+  public record R1(String one) {
+    public R1 {
+      Objects.requireNonNull(one);
+    }
+  }
+
+  public record R2(String one) {
+    @Deprecated
+    public R2 {
+      Objects.requireNonNull(one);
+    }
+  }
+
+  public record R3<T>(T x) {
+    @Deprecated
+    public R3 {
+      Objects.requireNonNull(x);
+    }
+  }
+}