toString() method for data classes and fake implementation for hashCode/equals

This commit is contained in:
Alex Tkachman
2012-09-16 15:56:48 +03:00
parent e317764a88
commit 58af365eb2
13 changed files with 236 additions and 40 deletions
@@ -16,6 +16,7 @@
package org.jetbrains.jet.codegen; package org.jetbrains.jet.codegen;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.asm4.MethodVisitor; import org.jetbrains.asm4.MethodVisitor;
import org.jetbrains.asm4.Type; import org.jetbrains.asm4.Type;
import org.jetbrains.asm4.commons.InstructionAdapter; import org.jetbrains.asm4.commons.InstructionAdapter;
@@ -116,7 +117,7 @@ public abstract class ClassBodyCodegen extends GenerationStateAware {
} }
} }
protected List<JetParameter> getPrimaryConstructorParameters() { protected @NotNull List<JetParameter> getPrimaryConstructorParameters() {
if (myClass instanceof JetClass) { if (myClass instanceof JetClass) {
return ((JetClass) myClass).getPrimaryConstructorParameters(); return ((JetClass) myClass).getPrimaryConstructorParameters();
} }
@@ -26,6 +26,7 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.asm4.MethodVisitor; import org.jetbrains.asm4.MethodVisitor;
import org.jetbrains.asm4.Type; import org.jetbrains.asm4.Type;
import org.jetbrains.asm4.commons.InstructionAdapter; import org.jetbrains.asm4.commons.InstructionAdapter;
import org.jetbrains.asm4.commons.Method;
import org.jetbrains.jet.codegen.binding.CalculatedClosure; import org.jetbrains.jet.codegen.binding.CalculatedClosure;
import org.jetbrains.jet.codegen.signature.BothSignatureWriter; import org.jetbrains.jet.codegen.signature.BothSignatureWriter;
import org.jetbrains.jet.codegen.signature.JvmMethodParameterKind; import org.jetbrains.jet.codegen.signature.JvmMethodParameterKind;
@@ -47,7 +48,7 @@ import java.util.*;
import static org.jetbrains.asm4.Opcodes.*; import static org.jetbrains.asm4.Opcodes.*;
import static org.jetbrains.jet.lang.resolve.DescriptorUtils.isClassObject; import static org.jetbrains.jet.lang.resolve.DescriptorUtils.isClassObject;
import static org.jetbrains.jet.lang.resolve.java.AsmTypeConstants.OBJECT_TYPE; import static org.jetbrains.jet.lang.resolve.java.AsmTypeConstants.*;
/** /**
* @author abreslav * @author abreslav
@@ -369,4 +370,16 @@ public class CodegenUtil {
iv.invokespecial(classAsmType.getInternalName(), "<init>", "()V"); iv.invokespecial(classAsmType.getInternalName(), "<init>", "()V");
iv.putstatic(classAsmType.getInternalName(), JvmAbi.INSTANCE_FIELD, classAsmType.getDescriptor()); iv.putstatic(classAsmType.getInternalName(), JvmAbi.INSTANCE_FIELD, classAsmType.getDescriptor());
} }
public static void generateStringBuilderConstructor(InstructionAdapter v) {
v.visitTypeInsn(NEW, "java/lang/StringBuilder");
v.dup();
v.invokespecial("java/lang/StringBuilder", "<init>", "()V");
}
public static void invokeAppendMethod(InstructionAdapter v, Type exprType) {
Method appendDescriptor = new Method("append", getType(StringBuilder.class),
new Type[] {exprType.getSort() == Type.OBJECT ? (exprType.equals(JAVA_STRING_TYPE) ? JAVA_STRING_TYPE : OBJECT_TYPE) : exprType});
v.invokevirtual("java/lang/StringBuilder", "append", appendDescriptor.getDescriptor());
}
} }
@@ -984,7 +984,7 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
return StackValue.constant(constantValue.toString(), type); return StackValue.constant(constantValue.toString(), type);
} }
else { else {
generateStringBuilderConstructor(); generateStringBuilderConstructor(v);
for (JetStringTemplateEntry entry : expression.getEntries()) { for (JetStringTemplateEntry entry : expression.getEntries()) {
if (entry instanceof JetStringTemplateEntryWithExpression) { if (entry instanceof JetStringTemplateEntryWithExpression) {
invokeAppend(entry.getExpression()); invokeAppend(entry.getExpression());
@@ -994,7 +994,7 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
? ((JetEscapeStringTemplateEntry) entry).getUnescapedValue() ? ((JetEscapeStringTemplateEntry) entry).getUnescapedValue()
: entry.getText(); : entry.getText();
v.aconst(text); v.aconst(text);
invokeAppendMethod(JAVA_STRING_TYPE); invokeAppendMethod(v, JAVA_STRING_TYPE);
} }
} }
v.invokevirtual("java/lang/StringBuilder", "toString", "()Ljava/lang/String;"); v.invokevirtual("java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
@@ -2568,14 +2568,6 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
} }
} }
public void generateStringBuilderConstructor() {
Type type = getType(StringBuilder.class);
v.anew(type);
v.dup();
Method method = new Method("<init>", Type.VOID_TYPE, new Type[0]);
v.invokespecial("java/lang/StringBuilder", method.getName(), method.getDescriptor());
}
public void invokeAppend(final JetExpression expr) { public void invokeAppend(final JetExpression expr) {
if (expr instanceof JetBinaryExpression) { if (expr instanceof JetBinaryExpression) {
final JetBinaryExpression binaryExpression = (JetBinaryExpression) expr; final JetBinaryExpression binaryExpression = (JetBinaryExpression) expr;
@@ -2594,13 +2586,7 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
} }
Type exprType = expressionType(expr); Type exprType = expressionType(expr);
gen(expr, exprType); gen(expr, exprType);
invokeAppendMethod(exprType.getSort() == Type.ARRAY ? OBJECT_TYPE : exprType); invokeAppendMethod(v, exprType.getSort() == Type.ARRAY ? OBJECT_TYPE : exprType);
}
public void invokeAppendMethod(Type exprType) {
Method appendDescriptor = new Method("append", getType(StringBuilder.class),
new Type[] {exprType.getSort() == Type.OBJECT ? OBJECT_TYPE : exprType});
v.invokevirtual("java/lang/StringBuilder", "append", appendDescriptor.getDescriptor());
} }
@Nullable @Nullable
@@ -322,10 +322,14 @@ public class FunctionCodegen extends GenerationStateAware {
public static void genJetAnnotations( public static void genJetAnnotations(
@NotNull GenerationState state, @NotNull GenerationState state,
@NotNull FunctionDescriptor functionDescriptor, @NotNull FunctionDescriptor functionDescriptor,
@NotNull JvmMethodSignature jvmSignature, @Nullable JvmMethodSignature jvmSignature,
@Nullable String propertyTypeSignature, @Nullable String propertyTypeSignature,
MethodVisitor mv MethodVisitor mv
) { ) {
if (jvmSignature == null) {
jvmSignature = state.getTypeMapper().mapToCallableMethod(functionDescriptor, false, OwnerKind.IMPLEMENTATION).getSignature();
}
List<ValueParameterDescriptor> paramDescrs = functionDescriptor.getValueParameters(); List<ValueParameterDescriptor> paramDescrs = functionDescriptor.getValueParameters();
Modality modality = functionDescriptor.getModality(); Modality modality = functionDescriptor.getModality();
ReceiverDescriptor receiverParameter = functionDescriptor.getReceiverParameter(); ReceiverDescriptor receiverParameter = functionDescriptor.getReceiverParameter();
@@ -59,6 +59,7 @@ import static org.jetbrains.asm4.Opcodes.*;
import static org.jetbrains.jet.codegen.CodegenUtil.*; import static org.jetbrains.jet.codegen.CodegenUtil.*;
import static org.jetbrains.jet.codegen.binding.CodegenBinding.*; import static org.jetbrains.jet.codegen.binding.CodegenBinding.*;
import static org.jetbrains.jet.lang.resolve.BindingContextUtils.callableDescriptorToDeclaration; import static org.jetbrains.jet.lang.resolve.BindingContextUtils.callableDescriptorToDeclaration;
import static org.jetbrains.jet.lang.resolve.java.AsmTypeConstants.JAVA_STRING_TYPE;
import static org.jetbrains.jet.lang.resolve.java.AsmTypeConstants.OBJECT_TYPE; import static org.jetbrains.jet.lang.resolve.java.AsmTypeConstants.OBJECT_TYPE;
/** /**
@@ -352,11 +353,103 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
generateEnumMethods(); generateEnumMethods();
generateComponentFunctionsForDataClasses(); generateFunctionsForDataClasses();
generateClosureFields(context.closure, v, state.getTypeMapper()); generateClosureFields(context.closure, v, state.getTypeMapper());
} }
private void generateFunctionsForDataClasses() {
if (!JetStandardLibrary.isData(descriptor)) return;
generateComponentFunctionsForDataClasses();
generateDataClassToStringMethod();
generateDataClassHashCodeMethod();
generateDataClassEqualsMethod();
}
private void generateDataClassEqualsMethod() {
// todo: this is fake implementation
final MethodVisitor mv = v.getVisitor().visitMethod(ACC_PUBLIC, "equals", "(Ljava/lang/Object;)Z", null, null);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKESPECIAL, superClassAsmType.getInternalName(), "equals", "(Ljava/lang/Object;)Z");
mv.visitInsn(IRETURN);
mv.visitMaxs(-1,-1);
mv.visitEnd();
}
private void generateDataClassHashCodeMethod() {
// todo: this is fake implementation
final MethodVisitor mv = v.getVisitor().visitMethod(ACC_PUBLIC, "hashCode", "()I", null, null);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, superClassAsmType.getInternalName(), "hashCode", "()I");
mv.visitInsn(IRETURN);
mv.visitMaxs(-1,-1);
mv.visitEnd();
}
private void generateDataClassToStringMethod() {
final MethodVisitor mv = v.getVisitor().visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null);
final InstructionAdapter iv = new InstructionAdapter(mv);
mv.visitVarInsn(ALOAD, 0);
generateStringBuilderConstructor(iv);
boolean first = true;
for (JetParameter parameter : getPrimaryConstructorParameters()) {
if (parameter.getValOrVarNode() == null) continue;
PropertyDescriptor propertyDescriptor = state.getBindingContext().get(BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, parameter);
assert propertyDescriptor != null;
if(first) {
iv.aconst(descriptor.getName() + "{" + propertyDescriptor.getName().getName()+"=");
first = false;
}
else {
iv.aconst(", " + propertyDescriptor.getName().getName()+"=");
}
CodegenUtil.invokeAppendMethod(iv, JAVA_STRING_TYPE);
iv.load(0, classAsmType);
// todo: seems to be more correct - need to be changed in sync with 'componentX' generation and related tests
// final Method
// method = typeMapper.mapGetterSignature(propertyDescriptor, OwnerKind.IMPLEMENTATION).getJvmMethodSignature().getAsmMethod();
//
// iv.invokevirtual(classAsmType.getInternalName(), method.getName(), method.getDescriptor());
// final Type type = method.getReturnType();
Type type = typeMapper.mapType(propertyDescriptor);
iv.getfield(classAsmType.getInternalName(), parameter.getName(), type.getDescriptor());
if (type.getSort() == Type.ARRAY) {
final Type elementType = correctElementType(type);
if (elementType.getSort() == Type.OBJECT || elementType.getSort() == Type.ARRAY) {
iv.invokestatic("java/util/Arrays", "toString", "([Ljava/lang/Object;)Ljava/lang/String;");
type = JAVA_STRING_TYPE;
}
else {
if (elementType.getSort() != Type.CHAR) {
iv.invokestatic("java/util/Arrays", "toString", "(" + type.getDescriptor() + ")Ljava/lang/String;");
type = JAVA_STRING_TYPE;
}
}
}
CodegenUtil.invokeAppendMethod(iv, type);
}
iv.aconst("}");
CodegenUtil.invokeAppendMethod(iv, JAVA_STRING_TYPE);
iv.invokevirtual("java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
iv.areturn(JAVA_STRING_TYPE);
FunctionCodegen.endVisit(mv, "toString", myClass);
}
private void generateComponentFunctionsForDataClasses() { private void generateComponentFunctionsForDataClasses() {
if (!myClass.hasPrimaryConstructor() || !JetStandardLibrary.isData(descriptor)) return; if (!myClass.hasPrimaryConstructor() || !JetStandardLibrary.isData(descriptor)) return;
@@ -376,19 +469,12 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
Type componentType = typeMapper.mapReturnType(returnType); Type componentType = typeMapper.mapReturnType(returnType);
MethodVisitor mv = v.newMethod(myClass, MethodVisitor mv = v.newMethod(myClass,
ACC_FINAL | getVisibilityAccessFlag(function), FunctionCodegen.getMethodAsmFlags(function, OwnerKind.IMPLEMENTATION),
function.getName().getName(), function.getName().getName(),
"()" + componentType.getDescriptor(), "()" + componentType.getDescriptor(),
null, null); null, null);
JetMethodAnnotationWriter aw = JetMethodAnnotationWriter.visitAnnotation(mv); FunctionCodegen.genJetAnnotations(state, function, null, null, mv);
BitSet kotlinFlags = getFlagsForVisibility(function.getVisibility());
aw.writeFlags(kotlinFlags);
aw.writeKind(DescriptorKindUtils.kindToInt(function.getKind()));
aw.writeNullableReturnType(returnType.isNullable());
JvmMethodSignature jvmMethodSignature = typeMapper.mapToCallableMethod(function, false, kind).getSignature();
aw.writeReturnType(jvmMethodSignature.getKotlinReturnType());
aw.visitEnd();
mv.visitCode(); mv.visitCode();
InstructionAdapter iv = new InstructionAdapter(mv); InstructionAdapter iv = new InstructionAdapter(mv);
@@ -754,7 +840,7 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
private void genSimpleSuperCall(InstructionAdapter iv) { private void genSimpleSuperCall(InstructionAdapter iv) {
iv.load(0, superClassAsmType); iv.load(0, superClassAsmType);
if (descriptor.getKind() == ClassKind.ENUM_CLASS || descriptor.getKind() == ClassKind.ENUM_ENTRY) { if (descriptor.getKind() == ClassKind.ENUM_CLASS || descriptor.getKind() == ClassKind.ENUM_ENTRY) {
iv.load(1, AsmTypeConstants.JAVA_STRING_TYPE); iv.load(1, JAVA_STRING_TYPE);
iv.load(2, Type.INT_TYPE); iv.load(2, Type.INT_TYPE);
iv.invokespecial(superClassAsmType.getInternalName(), "<init>", "(Ljava/lang/String;I)V"); iv.invokespecial(superClassAsmType.getInternalName(), "<init>", "(Ljava/lang/String;I)V");
} }
@@ -20,11 +20,12 @@ import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.asm4.Type; import org.jetbrains.asm4.Type;
import org.jetbrains.asm4.commons.InstructionAdapter; import org.jetbrains.asm4.commons.InstructionAdapter;
import org.jetbrains.jet.lang.resolve.java.AsmTypeConstants; import org.jetbrains.jet.codegen.CodegenUtil;
import org.jetbrains.jet.codegen.ExpressionCodegen; import org.jetbrains.jet.codegen.ExpressionCodegen;
import org.jetbrains.jet.codegen.StackValue; import org.jetbrains.jet.codegen.StackValue;
import org.jetbrains.jet.codegen.state.GenerationState; import org.jetbrains.jet.codegen.state.GenerationState;
import org.jetbrains.jet.lang.psi.JetExpression; import org.jetbrains.jet.lang.psi.JetExpression;
import org.jetbrains.jet.lang.resolve.java.AsmTypeConstants;
import java.util.List; import java.util.List;
@@ -43,15 +44,15 @@ public class Concat implements IntrinsicMethod {
@NotNull GenerationState state @NotNull GenerationState state
) { ) {
if (receiver == null || receiver == StackValue.none()) { // LHS + RHS if (receiver == null || receiver == StackValue.none()) { // LHS + RHS
codegen.generateStringBuilderConstructor(); CodegenUtil.generateStringBuilderConstructor(v);
codegen.invokeAppend(arguments.get(0)); // StringBuilder(LHS) codegen.invokeAppend(arguments.get(0)); // StringBuilder(LHS)
codegen.invokeAppend(arguments.get(1)); codegen.invokeAppend(arguments.get(1));
} }
else { // LHS.plus(RHS) else { // LHS.plus(RHS)
receiver.put(AsmTypeConstants.OBJECT_TYPE, v); receiver.put(AsmTypeConstants.OBJECT_TYPE, v);
codegen.generateStringBuilderConstructor(); CodegenUtil.generateStringBuilderConstructor(v);
v.swap(); // StringBuilder LHS v.swap(); // StringBuilder LHS
codegen.invokeAppendMethod(expectedType); // StringBuilder(LHS) CodegenUtil.invokeAppendMethod(v, expectedType); // StringBuilder(LHS)
codegen.invokeAppend(arguments.get(0)); codegen.invokeAppend(arguments.get(0));
} }
@@ -0,0 +1,14 @@
data class A(val x: Array<Int>?, val y: IntArray?)
fun box(): String {
var ts = A(Array<Int>(2, {it}), IntArray(3)).toString()
if(ts != "A{x=[0, 1], y=[0, 0, 0]}") return ts
ts = A(null, IntArray(3)).toString()
if(ts != "A{x=null, y=[0, 0, 0]}") return ts
ts = A(null, null).toString()
if(ts != "A{x=null, y=null}") return ts
return "OK"
}
@@ -0,0 +1,11 @@
data class A(var string: String)
fun box(): String {
val a = A("Fail")
if(a.toString() != "A{string=Fail}") return "fail"
a.string = "OK"
if("$a" != "A{string=OK}") return a.toString()
return "OK"
}
@@ -0,0 +1,11 @@
data class A<T>(val x: T)
fun box(): String {
val a = A(42)
if ("$a" != "A{x=42}") return "$a"
val b = A(239.toLong())
if ("$b" != "A{x=239}") return "$b"
return "OK"
}
@@ -0,0 +1,7 @@
data class A(var x: Int, y: Int, val z: Int?)
fun box(): String {
val a = A(1, 2, null)
if("$a" != "A{x=1, z=null}") return "$a"
return "OK"
}
@@ -0,0 +1,11 @@
open data class A(open val x: String)
class B : A("OK") {
override val x: String = "Fail"
}
fun foo(a: A) = a
fun box(): String {
return if ("${foo(B())}" == "A{x=OK}") "OK" else "fail"
}
@@ -0,0 +1,6 @@
data class A(val x: Unit)
fun box(): String {
val a = A(#())
return if ("$a" == "A{x=()}") "OK" else "$a"
}
@@ -13,23 +13,23 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.jet.codegen; package org.jetbrains.jet.codegen;
import junit.framework.Assert;
import junit.framework.Test; import junit.framework.Test;
import junit.framework.TestSuite; import junit.framework.TestSuite;
import java.io.File;
import org.jetbrains.jet.JetTestUtils; import org.jetbrains.jet.JetTestUtils;
import org.jetbrains.jet.test.InnerTestClasses;
import org.jetbrains.jet.test.TestMetadata; import org.jetbrains.jet.test.TestMetadata;
import org.jetbrains.jet.codegen.AbstractDataClassCodegenTest; import java.io.File;
/** This class is generated by {@link org.jetbrains.jet.codegen.AbstractDataClassCodegenTest}. DO NOT MODIFY MANUALLY */ /** This class is generated by {@link org.jetbrains.jet.codegen.AbstractDataClassCodegenTest}. DO NOT MODIFY MANUALLY */
@TestMetadata("compiler/testData/codegen/dataClasses") @TestMetadata("compiler/testData/codegen/dataClasses")
@InnerTestClasses({DataClassCodegenTestGenerated.Tostring.class})
public class DataClassCodegenTestGenerated extends AbstractDataClassCodegenTest { public class DataClassCodegenTestGenerated extends AbstractDataClassCodegenTest {
public void testAllFilesPresentInDataClasses() throws Exception { public void testAllFilesPresentInDataClasses() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.codegen.AbstractDataClassCodegenTest", new File("compiler/testData/codegen/dataClasses"), "kt", false); JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.codegen.AbstractDataClassCodegenTest", new File("compiler/testData/codegen/dataClasses"), "kt", true);
} }
@TestMetadata("arrayParams.kt") @TestMetadata("arrayParams.kt")
@@ -82,4 +82,49 @@ public class DataClassCodegenTestGenerated extends AbstractDataClassCodegenTest
blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/unitComponent.kt"); blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/unitComponent.kt");
} }
@TestMetadata("compiler/testData/codegen/dataClasses/tostring")
public static class Tostring extends AbstractDataClassCodegenTest {
public void testAllFilesPresentInTostring() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.codegen.AbstractDataClassCodegenTest",
new File("compiler/testData/codegen/dataClasses/tostring"), "kt", true);
}
@TestMetadata("arrayParams.kt")
public void testArrayParams() throws Exception {
blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/tostring/arrayParams.kt");
}
@TestMetadata("changingVarParam.kt")
public void testChangingVarParam() throws Exception {
blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/tostring/changingVarParam.kt");
}
@TestMetadata("genericParam.kt")
public void testGenericParam() throws Exception {
blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/tostring/genericParam.kt");
}
@TestMetadata("mixedParams.kt")
public void testMixedParams() throws Exception {
blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/tostring/mixedParams.kt");
}
@TestMetadata("overriddenProperty.kt")
public void testOverriddenProperty() throws Exception {
blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/tostring/overriddenProperty.kt");
}
@TestMetadata("unitComponent.kt")
public void testUnitComponent() throws Exception {
blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/tostring/unitComponent.kt");
}
}
public static Test suite() {
TestSuite suite = new TestSuite("DataClassCodegenTestGenerated");
suite.addTestSuite(DataClassCodegenTestGenerated.class);
suite.addTestSuite(Tostring.class);
return suite;
}
} }