diff options
author | Liam Miller-Cushon <cushon@google.com> | 2023-01-19 07:55:00 -0800 |
---|---|---|
committer | Javac Team <javac-team+copybara@google.com> | 2023-01-19 07:56:00 -0800 |
commit | f4370dd3fd8474ea1a78ded7da0d9cd6d3f22406 (patch) | |
tree | cc0782f836acd88473ed530a2170f1b0497d78a9 | |
parent | a7b11915f827ad0dbf1f7c58e93486014ec455de (diff) | |
download | turbine-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
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; + } + } +} |