aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLiam Miller-Cushon <cushon@google.com>2023-01-19 07:55:00 -0800
committerJavac Team <javac-team+copybara@google.com>2023-01-19 07:56:00 -0800
commitf4370dd3fd8474ea1a78ded7da0d9cd6d3f22406 (patch)
treecc0782f836acd88473ed530a2170f1b0497d78a9
parenta7b11915f827ad0dbf1f7c58e93486014ec455de (diff)
downloadturbine-f4370dd3fd8474ea1a78ded7da0d9cd6d3f22406.tar.gz
Fix handling of implicit record constructors
A record has to have a primary constructor with parameters that correspond to the component types. https://github.com/bazelbuild/bazel/issues/17250 PiperOrigin-RevId: 503164403
-rw-r--r--java/com/google/turbine/binder/TypeBinder.java294
-rw-r--r--javatests/com/google/turbine/lower/LowerIntegrationTest.java2
-rw-r--r--javatests/com/google/turbine/lower/testdata/record_ctor.test52
3 files changed, 227 insertions, 121 deletions
diff --git a/java/com/google/turbine/binder/TypeBinder.java b/java/com/google/turbine/binder/TypeBinder.java
index c39fffa..39c7089 100644
--- a/java/com/google/turbine/binder/TypeBinder.java
+++ b/java/com/google/turbine/binder/TypeBinder.java
@@ -249,13 +249,16 @@ public class TypeBinder {
ImmutableList<RecordComponentInfo> components = bindComponents(scope, base.decl().components());
- ImmutableList<MethodInfo> boundSyntheticMethods =
- syntheticMethods(syntheticMethods, components);
List<MethodInfo> boundMethods = bindMethods(scope, base.decl().members(), components);
- ImmutableList.Builder<MethodInfo> methods =
- ImmutableList.<MethodInfo>builder().addAll(boundSyntheticMethods).addAll(boundMethods);
+ ImmutableList<MethodInfo> methods;
if (base.kind().equals(TurbineTyKind.RECORD)) {
- methods.addAll(syntheticRecordMethods(syntheticMethods, components, boundMethods));
+ methods = recordMethods(syntheticMethods, components, boundMethods);
+ } else {
+ methods =
+ ImmutableList.<MethodInfo>builder()
+ .addAll(syntheticMethods(syntheticMethods))
+ .addAll(boundMethods)
+ .build();
}
ImmutableList<FieldInfo> fields = bindFields(scope, base.decl().members());
@@ -267,7 +270,7 @@ public class TypeBinder {
typeParameterTypes,
base.access(),
components,
- methods.build(),
+ methods,
fields,
base.owner(),
base.kind(),
@@ -282,6 +285,169 @@ public class TypeBinder {
base.decl());
}
+ private ImmutableList<MethodInfo> recordMethods(
+ SyntheticMethods syntheticMethods,
+ ImmutableList<RecordComponentInfo> components,
+ List<MethodInfo> boundMethods) {
+ List<MethodInfo> boundConstructors = new ArrayList<>();
+ List<MethodInfo> boundNonConstructors = new ArrayList<>();
+ boolean hasToString = false;
+ boolean hasEquals = false;
+ boolean hasHashCode = false;
+ boolean hasPrimaryConstructor = false;
+ for (MethodInfo m : boundMethods) {
+ if (m.name().equals("<init>")) {
+ if (isPrimaryConstructor(m, components)) {
+ hasPrimaryConstructor = true;
+ }
+ boundConstructors.add(m);
+ } else {
+ switch (m.name()) {
+ case "toString":
+ hasToString = m.parameters().isEmpty();
+ break;
+ case "equals":
+ hasEquals =
+ m.parameters().size() == 1
+ && hasSameErasure(getOnlyElement(m.parameters()).type(), Type.ClassTy.OBJECT);
+ break;
+ case "hashCode":
+ hasHashCode = m.parameters().isEmpty();
+ break;
+ default: // fall out
+ }
+ boundNonConstructors.add(m);
+ }
+ }
+ ImmutableList.Builder<MethodInfo> methods = ImmutableList.builder();
+ methods.addAll(boundConstructors);
+ if (!hasPrimaryConstructor) {
+ methods.add(defaultRecordConstructor(syntheticMethods, components));
+ }
+ methods.addAll(boundNonConstructors);
+ if (!hasToString) {
+ MethodSymbol toStringMethod = syntheticMethods.create(owner, "toString");
+ methods.add(
+ new MethodInfo(
+ toStringMethod,
+ ImmutableMap.of(),
+ Type.ClassTy.STRING,
+ ImmutableList.of(),
+ ImmutableList.of(),
+ TurbineFlag.ACC_PUBLIC | TurbineFlag.ACC_FINAL,
+ null,
+ null,
+ ImmutableList.of(),
+ null));
+ }
+ if (!hasHashCode) {
+ MethodSymbol hashCodeMethod = syntheticMethods.create(owner, "hashCode");
+ methods.add(
+ new MethodInfo(
+ hashCodeMethod,
+ ImmutableMap.of(),
+ Type.PrimTy.create(TurbineConstantTypeKind.INT, ImmutableList.of()),
+ ImmutableList.of(),
+ ImmutableList.of(),
+ TurbineFlag.ACC_PUBLIC | TurbineFlag.ACC_FINAL,
+ null,
+ null,
+ ImmutableList.of(),
+ null));
+ }
+ if (!hasEquals) {
+ MethodSymbol equalsMethod = syntheticMethods.create(owner, "equals");
+ methods.add(
+ new MethodInfo(
+ equalsMethod,
+ ImmutableMap.of(),
+ Type.PrimTy.create(TurbineConstantTypeKind.BOOLEAN, ImmutableList.of()),
+ ImmutableList.of(
+ new ParamInfo(
+ new ParamSymbol(equalsMethod, "other"),
+ Type.ClassTy.OBJECT,
+ ImmutableList.of(),
+ TurbineFlag.ACC_MANDATED)),
+ ImmutableList.of(),
+ TurbineFlag.ACC_PUBLIC | TurbineFlag.ACC_FINAL,
+ null,
+ null,
+ ImmutableList.of(),
+ null));
+ }
+ for (RecordComponentInfo c : components) {
+ MethodSymbol componentMethod = syntheticMethods.create(owner, c.name());
+ methods.add(
+ new MethodInfo(
+ componentMethod,
+ ImmutableMap.of(),
+ c.type(),
+ ImmutableList.of(),
+ ImmutableList.of(),
+ TurbineFlag.ACC_PUBLIC,
+ null,
+ null,
+ c.annotations(),
+ null));
+ }
+ return methods.build();
+ }
+
+ private MethodInfo defaultRecordConstructor(
+ SyntheticMethods syntheticMethods, ImmutableList<RecordComponentInfo> components) {
+ MethodSymbol symbol = syntheticMethods.create(owner, "<init>");
+ ImmutableList.Builder<ParamInfo> params = ImmutableList.builder();
+ for (RecordComponentInfo component : components) {
+ params.add(
+ new ParamInfo(
+ new ParamSymbol(symbol, component.name()),
+ component.type(),
+ component.annotations(),
+ component.access()));
+ }
+ return syntheticConstructor(
+ symbol, params.build(), TurbineVisibility.fromAccess(base.access()));
+ }
+
+ private boolean isPrimaryConstructor(
+ MethodInfo m, ImmutableList<RecordComponentInfo> components) {
+ if (m.parameters().size() != components.size()) {
+ return false;
+ }
+ for (int i = 0; i < m.parameters().size(); i++) {
+ if (!hasSameErasure(m.parameters().get(i).type(), components.get(i).type())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean hasSameErasure(Type a, Type b) {
+ switch (a.tyKind()) {
+ case PRIM_TY:
+ return b.tyKind() == Type.TyKind.PRIM_TY
+ && ((Type.PrimTy) a).primkind() == ((Type.PrimTy) b).primkind();
+ case CLASS_TY:
+ return b.tyKind() == Type.TyKind.CLASS_TY
+ && ((Type.ClassTy) a).sym().equals(((Type.ClassTy) b).sym());
+ case ARRAY_TY:
+ return b.tyKind() == Type.TyKind.ARRAY_TY
+ && hasSameErasure(((Type.ArrayTy) a).elementType(), ((Type.ArrayTy) b).elementType());
+ case TY_VAR:
+ return b.tyKind() == Type.TyKind.TY_VAR
+ && ((Type.TyVar) a).sym().equals(((Type.TyVar) b).sym());
+ case ERROR_TY:
+ return false;
+ case WILD_TY:
+ case INTERSECTION_TY:
+ case METHOD_TY:
+ case NONE_TY:
+ case VOID_TY:
+ // fall out: impossible method parameter types
+ }
+ throw new AssertionError(a.tyKind());
+ }
+
/**
* A generated for synthetic {@link MethodSymbol}s.
*
@@ -317,13 +483,10 @@ public class TypeBinder {
}
/** Collect synthetic and implicit methods, including default constructors and enum methods. */
- ImmutableList<MethodInfo> syntheticMethods(
- SyntheticMethods syntheticMethods, ImmutableList<RecordComponentInfo> components) {
+ ImmutableList<MethodInfo> syntheticMethods(SyntheticMethods syntheticMethods) {
switch (base.kind()) {
case CLASS:
return maybeDefaultConstructor(syntheticMethods);
- case RECORD:
- return maybeDefaultRecordConstructor(syntheticMethods, components);
case ENUM:
return syntheticEnumMethods(syntheticMethods);
default:
@@ -331,25 +494,6 @@ public class TypeBinder {
}
}
- private ImmutableList<MethodInfo> maybeDefaultRecordConstructor(
- SyntheticMethods syntheticMethods, ImmutableList<RecordComponentInfo> components) {
- if (hasConstructor()) {
- return ImmutableList.of();
- }
- MethodSymbol symbol = syntheticMethods.create(owner, "<init>");
- ImmutableList.Builder<ParamInfo> params = ImmutableList.builder();
- for (RecordComponentInfo component : components) {
- params.add(
- new ParamInfo(
- new ParamSymbol(symbol, component.name()),
- component.type(),
- component.annotations(),
- component.access()));
- }
- return ImmutableList.of(
- syntheticConstructor(symbol, params.build(), TurbineVisibility.fromAccess(base.access())));
- }
-
private ImmutableList<MethodInfo> maybeDefaultConstructor(SyntheticMethods syntheticMethods) {
if (hasConstructor()) {
return ImmutableList.of();
@@ -468,98 +612,6 @@ public class TypeBinder {
return methods.build();
}
- private ImmutableList<MethodInfo> syntheticRecordMethods(
- SyntheticMethods syntheticMethods,
- ImmutableList<RecordComponentInfo> components,
- List<MethodInfo> boundMethods) {
- boolean hasToString = false;
- boolean hasEquals = false;
- boolean hasHashCode = false;
- for (MethodInfo m : boundMethods) {
- switch (m.name()) {
- case "toString":
- hasToString = m.parameters().isEmpty();
- break;
- case "equals":
- hasEquals =
- m.parameters().size() == 1
- && getOnlyElement(m.parameters()).type().equals(Type.ClassTy.OBJECT);
- break;
- case "hashCode":
- hasHashCode = m.parameters().isEmpty();
- break;
- default: // fall out
- }
- }
- ImmutableList.Builder<MethodInfo> methods = ImmutableList.builder();
- if (!hasToString) {
- MethodSymbol toStringMethod = syntheticMethods.create(owner, "toString");
- methods.add(
- new MethodInfo(
- toStringMethod,
- ImmutableMap.of(),
- Type.ClassTy.STRING,
- ImmutableList.of(),
- ImmutableList.of(),
- TurbineFlag.ACC_PUBLIC | TurbineFlag.ACC_FINAL,
- null,
- null,
- ImmutableList.of(),
- null));
- }
- if (!hasHashCode) {
- MethodSymbol hashCodeMethod = syntheticMethods.create(owner, "hashCode");
- methods.add(
- new MethodInfo(
- hashCodeMethod,
- ImmutableMap.of(),
- Type.PrimTy.create(TurbineConstantTypeKind.INT, ImmutableList.of()),
- ImmutableList.of(),
- ImmutableList.of(),
- TurbineFlag.ACC_PUBLIC | TurbineFlag.ACC_FINAL,
- null,
- null,
- ImmutableList.of(),
- null));
- }
- if (!hasEquals) {
- MethodSymbol equalsMethod = syntheticMethods.create(owner, "equals");
- methods.add(
- new MethodInfo(
- equalsMethod,
- ImmutableMap.of(),
- Type.PrimTy.create(TurbineConstantTypeKind.BOOLEAN, ImmutableList.of()),
- ImmutableList.of(
- new ParamInfo(
- new ParamSymbol(equalsMethod, "other"),
- Type.ClassTy.OBJECT,
- ImmutableList.of(),
- TurbineFlag.ACC_MANDATED)),
- ImmutableList.of(),
- TurbineFlag.ACC_PUBLIC | TurbineFlag.ACC_FINAL,
- null,
- null,
- ImmutableList.of(),
- null));
- }
- for (RecordComponentInfo c : components) {
- MethodSymbol componentMethod = syntheticMethods.create(owner, c.name());
- methods.add(
- new MethodInfo(
- componentMethod,
- ImmutableMap.of(),
- c.type(),
- ImmutableList.of(),
- ImmutableList.of(),
- TurbineFlag.ACC_PUBLIC,
- null,
- null,
- c.annotations(),
- null));
- }
- return methods.build();
- }
-
private boolean hasConstructor() {
for (Tree m : base.decl().members()) {
if (m.kind() != Kind.METH_DECL) {
diff --git a/javatests/com/google/turbine/lower/LowerIntegrationTest.java b/javatests/com/google/turbine/lower/LowerIntegrationTest.java
index a2e8abe..9026725 100644
--- a/javatests/com/google/turbine/lower/LowerIntegrationTest.java
+++ b/javatests/com/google/turbine/lower/LowerIntegrationTest.java
@@ -49,6 +49,7 @@ public class LowerIntegrationTest {
"record.test", 16, //
"record2.test", 16,
"record_tostring.test", 16,
+ "record_ctor.test", 16,
"sealed.test", 17,
"sealed_nested.test", 17,
"textblock.test", 15);
@@ -271,6 +272,7 @@ public class LowerIntegrationTest {
"receiver_param.test",
"record.test",
"record2.test",
+ "record_ctor.test",
"record_tostring.test",
"rek.test",
"samepkg.test",
diff --git a/javatests/com/google/turbine/lower/testdata/record_ctor.test b/javatests/com/google/turbine/lower/testdata/record_ctor.test
new file mode 100644
index 0000000..a3adc15
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/record_ctor.test
@@ -0,0 +1,52 @@
+=== Records.java ===
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+public class Records {
+ public record A(String value) {
+
+ void one() {}
+
+ public A(String a, String b) {
+ this(a + ", " + b);
+ }
+
+ void two() {}
+ }
+
+ @Target(ElementType.TYPE_USE)
+ @interface N {}
+
+ public record B(String value) {
+
+ void one() {}
+
+ public B(@N String value) {
+ this.value = value;
+ }
+
+ void two() {}
+
+ public B(String a, String b) {
+ this(a + ", " + b);
+ }
+
+ void three() {}
+ }
+
+ class Inner {}
+
+ public record C(Records.Inner value) {
+
+ public C(Records. @N Inner value) {
+ this.value = value;
+ }
+ }
+
+ public record D<T>(T value) {
+
+ public D(T value) {
+ this.value = value;
+ }
+ }
+}