From 375f74033f2e707cd7eb4f9e5debf576e31c6634 Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Sun, 7 Apr 2024 13:36:10 -0500 Subject: [PATCH] Extract method creation to its own precompilation pass --- .../gwt/dev/jjs/JavaToJavaScriptCompiler.java | 3 + .../com/google/gwt/dev/jjs/ast/JProgram.java | 3 +- .../google/gwt/dev/jjs/ast/JRecordType.java | 28 +++ .../gwt/dev/jjs/ast/RuntimeConstants.java | 4 + .../gwt/dev/jjs/impl/GwtAstBuilder.java | 154 +----------- .../impl/ImplementClassLiteralsAsFields.java | 2 + .../jjs/impl/ImplementRecordComponents.java | 231 ++++++++++++++++++ 7 files changed, 277 insertions(+), 148 deletions(-) create mode 100644 dev/core/src/com/google/gwt/dev/jjs/ast/JRecordType.java create mode 100644 dev/core/src/com/google/gwt/dev/jjs/impl/ImplementRecordComponents.java diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java index a51cd7f9597..fd7b9d5bacf 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java +++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java @@ -84,6 +84,7 @@ import com.google.gwt.dev.jjs.impl.ImplementCastsAndTypeChecks; import com.google.gwt.dev.jjs.impl.ImplementClassLiteralsAsFields; import com.google.gwt.dev.jjs.impl.ImplementJsVarargs; +import com.google.gwt.dev.jjs.impl.ImplementRecordComponents; import com.google.gwt.dev.jjs.impl.JavaAstVerifier; import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap; import com.google.gwt.dev.jjs.impl.JjsUtils; @@ -1156,6 +1157,8 @@ private UnifiedAst precompile(PrecompilationContext precompilationContext) // Replace calls to native overrides of object methods. ReplaceCallsToNativeJavaLangObjectOverrides.exec(jprogram); + ImplementRecordComponents.exec(jprogram); + FixAssignmentsToUnboxOrCast.exec(jprogram); if (options.isEnableAssertions()) { AssertionNormalizer.exec(jprogram); diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java index 2811ac4bdd6..6764157ab48 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java @@ -1267,7 +1267,8 @@ private static Set buildInitialTypeNamesToIndex() { JAVASCRIPTOBJECT, CLASS_LITERAL_HOLDER, "com.google.gwt.core.client.RunAsyncCallback", "com.google.gwt.core.client.impl.AsyncFragmentLoader", "com.google.gwt.core.client.impl.Impl", - "com.google.gwt.core.client.prefetch.RunAsyncCode")); + "com.google.gwt.core.client.prefetch.RunAsyncCode", + "java.util.Objects")); typeNamesToIndex.addAll(CODEGEN_TYPES_SET); return typeNamesToIndex; } diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JRecordType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JRecordType.java new file mode 100644 index 00000000000..974fece4309 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JRecordType.java @@ -0,0 +1,28 @@ +/* + * Copyright 2024 GWT Project Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.dev.jjs.ast; + +import com.google.gwt.dev.jjs.SourceInfo; + +/** + * Java record type reference expression. At this time, there is no AST node for components, + * instead using fields, since records can only have fields declared for components. + */ +public class JRecordType extends JClassType { + public JRecordType(SourceInfo info, String name) { + super(info, name, false, true); + } +} diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/RuntimeConstants.java b/dev/core/src/com/google/gwt/dev/jjs/ast/RuntimeConstants.java index 15f5b8e2487..5dc68da4322 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/RuntimeConstants.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/RuntimeConstants.java @@ -43,6 +43,8 @@ public class RuntimeConstants { public static final String CLASS_CREATE_FOR_PRIMITIVE = "Class.createForPrimitive"; public static final String CLASS_CREATE_FOR_INTERFACE = "Class.createForInterface"; + public static final String CLASS_GET_SIMPLE_NAME = "Class.getSimpleName"; + public static final String COLLAPSED_PROPERTY_HOLDER_GET_PERMUTATION_ID = "CollapsedPropertyHolder.getPermutationId"; @@ -97,4 +99,6 @@ public class RuntimeConstants { public static final String RUNTIME_UNIQUE_ID = "Runtime.uniqueId"; public static final String UTIL_MAKE_ENUM_NAME = "Util.makeEnumName"; + + public static final String OBJECTS_HASH = "Objects.hash"; } diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java index 1b0ce72dfab..023b443edea 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java @@ -82,6 +82,7 @@ import com.google.gwt.dev.jjs.ast.JPrefixOperation; import com.google.gwt.dev.jjs.ast.JPrimitiveType; import com.google.gwt.dev.jjs.ast.JProgram; +import com.google.gwt.dev.jjs.ast.JRecordType; import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JReturnStatement; import com.google.gwt.dev.jjs.ast.JStatement; @@ -240,7 +241,6 @@ import java.util.ListIterator; import java.util.Map; import java.util.Map.Entry; -import java.util.Optional; import java.util.Set; /** @@ -2672,10 +2672,6 @@ public JInterfaceType apply(ReferenceBinding referenceBinding) { processEnumType((JEnumType) type); } - if (x.isRecord()) { - writeRecordMethods(type); - } - if (type instanceof JClassType && type.isJsNative()) { maybeImplementJavaLangObjectMethodsOnNativeClass(type); } @@ -2684,142 +2680,6 @@ public JInterfaceType apply(ReferenceBinding referenceBinding) { curClass = classStack.pop(); } - private void writeRecordMethods(JDeclaredType type) { - // This is a record type, and any methods that were declared but not referenced now need - // to be defined. These include the field-named method accessors, equals/hashCode and - // toString. If not defined, we'll synthesize them based on the record components. - SourceInfo info = type.getSourceInfo(); - for (JMethod method : type.getMethods()) { - if (method.getBody() != null) { - // If there is a body, that means the record has its own declaration of this method, and - // we should not re-declare it. - continue; - } - - if (method.getName().equals(TO_STRING_METHOD_NAME) && method.getParams().isEmpty()) { - List args = new ArrayList<>(); - - // Concatenate type with []s and component values - MethodBinding getSimpleMethod = curCud.scope.getJavaLangClass() - .getExactMethod("getSimpleName".toCharArray(), Binding.NO_TYPES, curCud.scope); - JMethod classGetSimpleName = typeMap.get(getSimpleMethod); - JExpression toStrExpr = new JMethodCall(info, new JClassLiteral(info, type), - classGetSimpleName); - - args.add(new JStringLiteral(info, "[", javaLangString)); - List fields = type.getFields(); - for (int i = 0; i < fields.size(); i++) { - if (i != 0) { - args.add(new JStringLiteral(info, ", ", javaLangString)); - } - JField field = fields.get(i); - args.add(new JStringLiteral(info, field.getName() + "=", javaLangString)); - args.add(new JFieldRef(info, new JThisRef(info, type), field, type)); - } - args.add(new JStringLiteral(info, "]", javaLangString)); - for (JExpression arg : args) { - toStrExpr = new JBinaryOperation(info, javaLangString, JBinaryOperator.CONCAT, - toStrExpr, - arg); - } - - JMethodBody body = new JMethodBody(info); - body.getBlock().addStmt(toStrExpr.makeReturnStatement()); - method.setBody(body); - } else if (method.getName().equals(EQUALS_METHOD_NAME) && method.getParams().size() == 1 - && method.getParams().get(0).getType().equals(javaLangObject)) { - JMethodBody body = new JMethodBody(info); - JParameter otherParam = method.getParams().get(0); - - // Equals is built from first == check between this and other, as a fast path for the - // same object and also to ensure that other isn't null. Then - // MyRecord.class == other.getClass(), and now we know they're the same type and can cast - // safely to access fields for the rest. - JBinaryOperation eq = - new JBinaryOperation(info, JPrimitiveType.BOOLEAN, JBinaryOperator.EQ, - new JThisRef(info, type), - otherParam.createRef(info)); - body.getBlock().addStmt(new JIfStatement(info, eq, - JBooleanLiteral.TRUE.makeReturnStatement(), null)); - - JMethod getClass = javaLangObject.getMethods().get(GET_CLASS_METHOD_INDEX); - JBinaryOperation sameTypeCheck = - new JBinaryOperation(info, JPrimitiveType.BOOLEAN, JBinaryOperator.EQ, - new JClassLiteral(info, type), - new JMethodCall(info, otherParam.createRef(info), getClass) - ); - body.getBlock().addStmt(new JIfStatement(info, sameTypeCheck, - JBooleanLiteral.FALSE.makeReturnStatement(), null)); - - // Create a local to assign to and compare each component - JLocal typedOther = JProgram.createLocal(info, "other", type, true, body); - // We can use an unsafe cast since we know the check will succeed - JUnsafeTypeCoercion uncheckedCast = - new JUnsafeTypeCoercion(info, type, otherParam.createRef(info)); - JBinaryOperation uncheckedAssign = new JBinaryOperation(info, type, JBinaryOperator.ASG, - typedOther.createRef(info), uncheckedCast); - body.getBlock().addStmt(uncheckedAssign.makeStatement()); - - JExpression componentCheck = JBooleanLiteral.TRUE; - for (JField field : type.getFields()) { - if (!field.isStatic()) { - JBinaryOperation jBinaryOperation = new JBinaryOperation(info, JPrimitiveType.BOOLEAN, - JBinaryOperator.EQ, - new JFieldRef(info, new JThisRef(info, type), field, type), - new JFieldRef(info, typedOther.createRef(info), field, type)); - if (componentCheck != JBooleanLiteral.TRUE) { - componentCheck = new JBinaryOperation(info, JPrimitiveType.BOOLEAN, - JBinaryOperator.AND, - componentCheck, - jBinaryOperation); - } else { - componentCheck = jBinaryOperation; - } - } - } - - body.getBlock().addStmt(componentCheck.makeReturnStatement()); - method.setBody(body); - } else if (method.getName().equals(HASHCODE_METHOD_NAME) && method.getParams().isEmpty()) { - final JExpression hashcodeStatement; - if (type.getFields().isEmpty()) { - // No fields, just hashcode 0 - hashcodeStatement = new JIntLiteral(info, 0); - } else { - List exprs = Lists.newArrayListWithCapacity(type.getFields().size()); - for (JField field : type.getFields()) { - JFieldRef jFieldRef = new JFieldRef(info, new JThisRef(info, type), field, type); - exprs.add(maybeInsertCasts(jFieldRef, javaLangObject)); - } - hashcodeStatement = new JMethodCall(info, null, OBJECTS_HASH_METHOD, exprs); - } - JMethodBody body = new JMethodBody(info); - body.getBlock().addStmt(hashcodeStatement.makeReturnStatement()); - - method.setBody(body); - } else if (method.getParams().isEmpty()) { - // Check if it has the same name+type as a field - Optional matchingField = type.getFields().stream() - .filter(f -> f.getName().equals(method.getName())) - .filter(f -> f.getType().equals(method.getType())) - .findFirst(); - if (matchingField.isPresent()) { - JField field = matchingField.get(); - // We can pick a more specific source for this than the others, use the "field" itself - SourceInfo sourceInfo = field.getSourceInfo(); - - // Create a simple accessor method and bind it, so it can be used anywhere outside this - // type. - JFieldRef fieldReference = new JFieldRef(sourceInfo, new JThisRef(sourceInfo, type), - field, type); - JMethodBody body = new JMethodBody(sourceInfo); - body.getBlock().addStmt(fieldReference.makeReturnStatement()); - method.setBody(body); - } - } - } - } - protected JBlock pop(Block x) { return (x == null) ? null : (JBlock) pop(); } @@ -4135,10 +3995,6 @@ private boolean isBinaryBinding(ReferenceBinding binding) { JMethod.getExternalizedMethod("com.google.gwt.lang.Cast", "getClass(Ljava/lang/Object;)Ljava/lang/Class;", true); - private static JMethod OBJECTS_HASH_METHOD = - JMethod.getExternalizedMethod("java.util.Objects", - "hash([Ljava/lang/Object;)I", true); - private List processImpl() { CompilationUnitDeclaration cud = curCud.cud; if (cud.types == null) { @@ -4580,8 +4436,12 @@ private void createTypes(TypeDeclaration x) { JDeclaredType type; if (binding.isClass()) { - type = new JClassType( - info, name, binding.isAbstract(), binding.isFinal() || binding.isAnonymousType()); + if (binding.isRecord()) { + type = new JRecordType(info, name); + } else { + type = new JClassType( + info, name, binding.isAbstract(), binding.isFinal() || binding.isAnonymousType()); + } } else if (binding.isInterface() || binding.isAnnotationType()) { type = new JInterfaceType(info, name); } else if (binding.isEnum()) { diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java index 3006bda578b..598218a9ce1 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java @@ -37,6 +37,7 @@ import com.google.gwt.dev.jjs.ast.JParameter; import com.google.gwt.dev.jjs.ast.JPrimitiveType; import com.google.gwt.dev.jjs.ast.JProgram; +import com.google.gwt.dev.jjs.ast.JRecordType; import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JRuntimeTypeReference; import com.google.gwt.dev.jjs.ast.JType; @@ -87,6 +88,7 @@ public class ImplementClassLiteralsAsFields { literalFactoryMethodByTypeClass = new ImmutableMap.Builder() .put(JEnumType.class, ClassLiteralFactoryMethod.CREATE_FOR_ENUM) .put(JClassType.class, ClassLiteralFactoryMethod.CREATE_FOR_CLASS) + .put(JRecordType.class, ClassLiteralFactoryMethod.CREATE_FOR_CLASS) .put(JInterfaceType.class, ClassLiteralFactoryMethod.CREATE_FOR_INTERFACE) .put(JPrimitiveType.class, ClassLiteralFactoryMethod.CREATE_FOR_PRIMITIVE) .build(); diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementRecordComponents.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementRecordComponents.java new file mode 100644 index 00000000000..0021e3f0e19 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementRecordComponents.java @@ -0,0 +1,231 @@ +/* + * Copyright 2024 GWT Project Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.dev.jjs.impl; + +import com.google.gwt.dev.jjs.SourceInfo; +import com.google.gwt.dev.jjs.ast.JBinaryOperation; +import com.google.gwt.dev.jjs.ast.JBinaryOperator; +import com.google.gwt.dev.jjs.ast.JBooleanLiteral; +import com.google.gwt.dev.jjs.ast.JClassLiteral; +import com.google.gwt.dev.jjs.ast.JClassType; +import com.google.gwt.dev.jjs.ast.JDeclaredType; +import com.google.gwt.dev.jjs.ast.JExpression; +import com.google.gwt.dev.jjs.ast.JField; +import com.google.gwt.dev.jjs.ast.JFieldRef; +import com.google.gwt.dev.jjs.ast.JIfStatement; +import com.google.gwt.dev.jjs.ast.JIntLiteral; +import com.google.gwt.dev.jjs.ast.JLocal; +import com.google.gwt.dev.jjs.ast.JMethod; +import com.google.gwt.dev.jjs.ast.JMethodBody; +import com.google.gwt.dev.jjs.ast.JMethodCall; +import com.google.gwt.dev.jjs.ast.JParameter; +import com.google.gwt.dev.jjs.ast.JPrimitiveType; +import com.google.gwt.dev.jjs.ast.JProgram; +import com.google.gwt.dev.jjs.ast.JRecordType; +import com.google.gwt.dev.jjs.ast.JStringLiteral; +import com.google.gwt.dev.jjs.ast.JThisRef; +import com.google.gwt.dev.jjs.ast.JUnsafeTypeCoercion; +import com.google.gwt.dev.jjs.ast.RuntimeConstants; +import com.google.gwt.thirdparty.guava.common.collect.Lists; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Implements the methods required for a Java Record type, based on its components/fields. + */ +public class ImplementRecordComponents { + + public static void exec(JProgram program) { + new ImplementRecordComponents(program).execImpl(); + } + + private final JProgram program; + private final AutoboxUtils autoboxUtils; + private final JMethod getClassMethod; + private final JMethod getSimpleNameMethod; + private final JClassType javaLangString; + + private ImplementRecordComponents(JProgram program) { + this.program = program; + this.autoboxUtils = new AutoboxUtils(program); + + getClassMethod = program.getIndexedMethod(RuntimeConstants.OBJECT_GET_CLASS); + getSimpleNameMethod = program.getIndexedMethod(RuntimeConstants.CLASS_GET_SIMPLE_NAME); + javaLangString = program.getTypeJavaLangString(); + } + + private void execImpl() { + for (JDeclaredType type : program.getDeclaredTypes()) { + if (type instanceof JRecordType) { + implementRecordComponents((JRecordType) type); + } + } + } + + private void implementRecordComponents(JRecordType type) { + // This is a record type, and any methods that were declared but not referenced now need + // to be defined. These include the field-named method accessors, equals/hashCode and + // toString. If not defined, we'll synthesize them based on the record components. + SourceInfo info = type.getSourceInfo(); + for (JMethod method : type.getMethods()) { + if (method.getBody() != null) { + // If there is a body, that means the record has its own declaration of this method, and + // we should not re-declare it. + continue; + } + + if (method.getName().equals(GwtAstBuilder.TO_STRING_METHOD_NAME) + && method.getParams().isEmpty()) { + implementToString(type, method, info); + } else if (method.getName().equals(GwtAstBuilder.EQUALS_METHOD_NAME) + && method.getParams().size() == 1 + && method.getParams().get(0).getType().equals(program.getTypeJavaLangObject())) { + implementEquals(type, method, info); + } else if (method.getName().equals(GwtAstBuilder.HASHCODE_METHOD_NAME) + && method.getParams().isEmpty()) { + implementHashCode(type, method, info); + } else if (method.getParams().isEmpty()) { + // Check if it has the same name+type as a component/field + Optional matchingField = type.getFields().stream() + .filter(f -> f.getName().equals(method.getName())) + .filter(f -> f.getType().equals(method.getType())) + .findFirst(); + matchingField.ifPresent(f -> implementComponentAccessor(type, method, f)); + } + } + } + + private static void implementComponentAccessor(JRecordType type, JMethod method, JField field) { + // We can pick a more specific source for this than the others, use the "field" itself. + SourceInfo info = field.getSourceInfo(); + + // Create a simple accessor method and bind it, so it can be used anywhere outside this type. + JFieldRef fieldReference = new JFieldRef(info, new JThisRef(info, type), + field, type); + JMethodBody body = new JMethodBody(info); + body.getBlock().addStmt(fieldReference.makeReturnStatement()); + method.setBody(body); + } + + private void implementHashCode(JRecordType type, JMethod method, SourceInfo info) { + final JExpression hashcodeStatement; + if (type.getFields().isEmpty()) { + // No fields, just emit hashcode=0 + hashcodeStatement = new JIntLiteral(info, 0); + } else { + List exprs = Lists.newArrayListWithCapacity(type.getFields().size()); + for (JField field : type.getFields()) { + JFieldRef jFieldRef = new JFieldRef(info, new JThisRef(info, type), field, type); + if (jFieldRef.getType().isPrimitiveType()) { + exprs.add(autoboxUtils.box(jFieldRef, (JPrimitiveType) jFieldRef.getType())); + } else { + exprs.add(jFieldRef); + } + } + JMethod hash = program.getIndexedMethod(RuntimeConstants.OBJECTS_HASH); + hashcodeStatement = new JMethodCall(info, null, hash, exprs); + } + JMethodBody body = new JMethodBody(info); + body.getBlock().addStmt(hashcodeStatement.makeReturnStatement()); + + method.setBody(body); + } + + private void implementEquals(JRecordType type, JMethod method, SourceInfo info) { + JMethodBody body = new JMethodBody(info); + JParameter otherParam = method.getParams().get(0); + + // Equals is built from first == check between this and other, as a fast path for the same + // object and also to ensure that other isn't null. Then MyRecord.class == other.getClass(), + // and now we know they're the same type and can cast safely to access fields for the rest. + JBinaryOperation eq = + new JBinaryOperation(info, JPrimitiveType.BOOLEAN, JBinaryOperator.EQ, + new JThisRef(info, type), + otherParam.createRef(info)); + body.getBlock().addStmt(new JIfStatement(info, eq, + JBooleanLiteral.TRUE.makeReturnStatement(), null)); + + JBinaryOperation sameTypeCheck = + new JBinaryOperation(info, JPrimitiveType.BOOLEAN, JBinaryOperator.EQ, + new JClassLiteral(info, type), + new JMethodCall(info, otherParam.createRef(info), getClassMethod)); + body.getBlock().addStmt(new JIfStatement(info, sameTypeCheck, + JBooleanLiteral.FALSE.makeReturnStatement(), null)); + + // Create a local to assign to and compare each component + JLocal typedOther = JProgram.createLocal(info, "other", type, true, body); + // We can use an unsafe cast since we know the check will succeed + JUnsafeTypeCoercion uncheckedCast = + new JUnsafeTypeCoercion(info, type, otherParam.createRef(info)); + JBinaryOperation uncheckedAssign = new JBinaryOperation(info, type, JBinaryOperator.ASG, + typedOther.createRef(info), uncheckedCast); + body.getBlock().addStmt(uncheckedAssign.makeStatement()); + + JExpression componentCheck = JBooleanLiteral.TRUE; + for (JField field : type.getFields()) { + if (!field.isStatic()) { + JBinaryOperation jBinaryOperation = new JBinaryOperation(info, JPrimitiveType.BOOLEAN, + JBinaryOperator.EQ, + new JFieldRef(info, new JThisRef(info, type), field, type), + new JFieldRef(info, typedOther.createRef(info), field, type)); + if (componentCheck != JBooleanLiteral.TRUE) { + componentCheck = new JBinaryOperation(info, JPrimitiveType.BOOLEAN, + JBinaryOperator.AND, + componentCheck, + jBinaryOperation); + } else { + componentCheck = jBinaryOperation; + } + } + } + + body.getBlock().addStmt(componentCheck.makeReturnStatement()); + method.setBody(body); + } + + private void implementToString(JRecordType type, JMethod method, SourceInfo info) { + List args = new ArrayList<>(); + + // Concatenate type with []s and component values, assigning to toStrExpr as we append + // more concat operations. Using getClass().getSimpleName() rather than a string literal + // allows the toString implementation to emit the obfuscated class name. + JMethodCall getClass = new JMethodCall(info, new JThisRef(info, type), getClassMethod); + JExpression toStrExpr = new JMethodCall(info, getClass, getSimpleNameMethod); + + args.add(new JStringLiteral(info, "[", javaLangString)); + List fields = type.getFields(); + for (int i = 0; i < fields.size(); i++) { + if (i != 0) { + args.add(new JStringLiteral(info, ", ", javaLangString)); + } + JField field = fields.get(i); + args.add(new JStringLiteral(info, field.getName() + "=", javaLangString)); + args.add(new JFieldRef(info, new JThisRef(info, type), field, type)); + } + args.add(new JStringLiteral(info, "]", javaLangString)); + for (JExpression arg : args) { + toStrExpr = new JBinaryOperation(info, javaLangString, JBinaryOperator.CONCAT, + toStrExpr, + arg); + } + + JMethodBody body = new JMethodBody(info); + body.getBlock().addStmt(toStrExpr.makeReturnStatement()); + method.setBody(body); + } +}