diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/ClassBodyCodegen.java b/compiler/backend/src/org/jetbrains/jet/codegen/ClassBodyCodegen.java index 73046f46afe..d11185aa1ec 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/ClassBodyCodegen.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/ClassBodyCodegen.java @@ -16,6 +16,7 @@ package org.jetbrains.jet.codegen; +import org.jetbrains.annotations.NotNull; import org.jetbrains.asm4.MethodVisitor; import org.jetbrains.asm4.Type; import org.jetbrains.asm4.commons.InstructionAdapter; @@ -116,7 +117,7 @@ public abstract class ClassBodyCodegen extends GenerationStateAware { } } - protected List getPrimaryConstructorParameters() { + protected @NotNull List getPrimaryConstructorParameters() { if (myClass instanceof JetClass) { return ((JetClass) myClass).getPrimaryConstructorParameters(); } diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/CodegenUtil.java b/compiler/backend/src/org/jetbrains/jet/codegen/CodegenUtil.java index 7ffa3ff9d97..25800b0e85c 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/CodegenUtil.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/CodegenUtil.java @@ -26,6 +26,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.asm4.MethodVisitor; import org.jetbrains.asm4.Type; import org.jetbrains.asm4.commons.InstructionAdapter; +import org.jetbrains.asm4.commons.Method; import org.jetbrains.jet.codegen.binding.CalculatedClosure; import org.jetbrains.jet.codegen.signature.BothSignatureWriter; import org.jetbrains.jet.codegen.signature.JvmMethodParameterKind; @@ -47,7 +48,7 @@ import java.util.*; import static org.jetbrains.asm4.Opcodes.*; 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 @@ -369,4 +370,16 @@ public class CodegenUtil { iv.invokespecial(classAsmType.getInternalName(), "", "()V"); 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", "", "()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()); + } } diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/jet/codegen/ExpressionCodegen.java index 020a0246315..079631ff225 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/ExpressionCodegen.java @@ -984,7 +984,7 @@ public class ExpressionCodegen extends JetVisitor implem return StackValue.constant(constantValue.toString(), type); } else { - generateStringBuilderConstructor(); + generateStringBuilderConstructor(v); for (JetStringTemplateEntry entry : expression.getEntries()) { if (entry instanceof JetStringTemplateEntryWithExpression) { invokeAppend(entry.getExpression()); @@ -994,7 +994,7 @@ public class ExpressionCodegen extends JetVisitor implem ? ((JetEscapeStringTemplateEntry) entry).getUnescapedValue() : entry.getText(); v.aconst(text); - invokeAppendMethod(JAVA_STRING_TYPE); + invokeAppendMethod(v, JAVA_STRING_TYPE); } } v.invokevirtual("java/lang/StringBuilder", "toString", "()Ljava/lang/String;"); @@ -2568,14 +2568,6 @@ public class ExpressionCodegen extends JetVisitor implem } } - public void generateStringBuilderConstructor() { - Type type = getType(StringBuilder.class); - v.anew(type); - v.dup(); - Method method = new Method("", Type.VOID_TYPE, new Type[0]); - v.invokespecial("java/lang/StringBuilder", method.getName(), method.getDescriptor()); - } - public void invokeAppend(final JetExpression expr) { if (expr instanceof JetBinaryExpression) { final JetBinaryExpression binaryExpression = (JetBinaryExpression) expr; @@ -2594,13 +2586,7 @@ public class ExpressionCodegen extends JetVisitor implem } Type exprType = expressionType(expr); gen(expr, exprType); - invokeAppendMethod(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()); + invokeAppendMethod(v, exprType.getSort() == Type.ARRAY ? OBJECT_TYPE : exprType); } @Nullable diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/FunctionCodegen.java b/compiler/backend/src/org/jetbrains/jet/codegen/FunctionCodegen.java index 2da89dfb972..d7cae2f6e1c 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/FunctionCodegen.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/FunctionCodegen.java @@ -322,10 +322,14 @@ public class FunctionCodegen extends GenerationStateAware { public static void genJetAnnotations( @NotNull GenerationState state, @NotNull FunctionDescriptor functionDescriptor, - @NotNull JvmMethodSignature jvmSignature, + @Nullable JvmMethodSignature jvmSignature, @Nullable String propertyTypeSignature, MethodVisitor mv ) { + if (jvmSignature == null) { + jvmSignature = state.getTypeMapper().mapToCallableMethod(functionDescriptor, false, OwnerKind.IMPLEMENTATION).getSignature(); + } + List paramDescrs = functionDescriptor.getValueParameters(); Modality modality = functionDescriptor.getModality(); ReceiverDescriptor receiverParameter = functionDescriptor.getReceiverParameter(); diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java b/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java index 9163f7850d6..74cd8b819b8 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java @@ -59,6 +59,7 @@ import static org.jetbrains.asm4.Opcodes.*; import static org.jetbrains.jet.codegen.CodegenUtil.*; import static org.jetbrains.jet.codegen.binding.CodegenBinding.*; 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; /** @@ -352,11 +353,103 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { generateEnumMethods(); - generateComponentFunctionsForDataClasses(); + generateFunctionsForDataClasses(); 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() { if (!myClass.hasPrimaryConstructor() || !JetStandardLibrary.isData(descriptor)) return; @@ -376,19 +469,12 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { Type componentType = typeMapper.mapReturnType(returnType); MethodVisitor mv = v.newMethod(myClass, - ACC_FINAL | getVisibilityAccessFlag(function), + FunctionCodegen.getMethodAsmFlags(function, OwnerKind.IMPLEMENTATION), function.getName().getName(), "()" + componentType.getDescriptor(), null, null); - JetMethodAnnotationWriter aw = JetMethodAnnotationWriter.visitAnnotation(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(); + FunctionCodegen.genJetAnnotations(state, function, null, null, mv); mv.visitCode(); InstructionAdapter iv = new InstructionAdapter(mv); @@ -754,7 +840,7 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { private void genSimpleSuperCall(InstructionAdapter iv) { iv.load(0, superClassAsmType); 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.invokespecial(superClassAsmType.getInternalName(), "", "(Ljava/lang/String;I)V"); } diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/intrinsics/Concat.java b/compiler/backend/src/org/jetbrains/jet/codegen/intrinsics/Concat.java index 10cc9d6d179..f488a74cb3a 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/intrinsics/Concat.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/intrinsics/Concat.java @@ -20,11 +20,12 @@ import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; import org.jetbrains.asm4.Type; 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.StackValue; import org.jetbrains.jet.codegen.state.GenerationState; import org.jetbrains.jet.lang.psi.JetExpression; +import org.jetbrains.jet.lang.resolve.java.AsmTypeConstants; import java.util.List; @@ -43,15 +44,15 @@ public class Concat implements IntrinsicMethod { @NotNull GenerationState state ) { if (receiver == null || receiver == StackValue.none()) { // LHS + RHS - codegen.generateStringBuilderConstructor(); + CodegenUtil.generateStringBuilderConstructor(v); codegen.invokeAppend(arguments.get(0)); // StringBuilder(LHS) codegen.invokeAppend(arguments.get(1)); } else { // LHS.plus(RHS) receiver.put(AsmTypeConstants.OBJECT_TYPE, v); - codegen.generateStringBuilderConstructor(); + CodegenUtil.generateStringBuilderConstructor(v); v.swap(); // StringBuilder LHS - codegen.invokeAppendMethod(expectedType); // StringBuilder(LHS) + CodegenUtil.invokeAppendMethod(v, expectedType); // StringBuilder(LHS) codegen.invokeAppend(arguments.get(0)); } diff --git a/compiler/testData/codegen/dataClasses/tostring/arrayParams.kt b/compiler/testData/codegen/dataClasses/tostring/arrayParams.kt new file mode 100644 index 00000000000..4ddedc80981 --- /dev/null +++ b/compiler/testData/codegen/dataClasses/tostring/arrayParams.kt @@ -0,0 +1,14 @@ +data class A(val x: Array?, val y: IntArray?) + +fun box(): String { + var ts = A(Array(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" +} diff --git a/compiler/testData/codegen/dataClasses/tostring/changingVarParam.kt b/compiler/testData/codegen/dataClasses/tostring/changingVarParam.kt new file mode 100644 index 00000000000..f25581fb18a --- /dev/null +++ b/compiler/testData/codegen/dataClasses/tostring/changingVarParam.kt @@ -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" +} diff --git a/compiler/testData/codegen/dataClasses/tostring/genericParam.kt b/compiler/testData/codegen/dataClasses/tostring/genericParam.kt new file mode 100644 index 00000000000..d751f3c6794 --- /dev/null +++ b/compiler/testData/codegen/dataClasses/tostring/genericParam.kt @@ -0,0 +1,11 @@ +data class A(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" +} diff --git a/compiler/testData/codegen/dataClasses/tostring/mixedParams.kt b/compiler/testData/codegen/dataClasses/tostring/mixedParams.kt new file mode 100644 index 00000000000..1d0cfdb46dd --- /dev/null +++ b/compiler/testData/codegen/dataClasses/tostring/mixedParams.kt @@ -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" +} diff --git a/compiler/testData/codegen/dataClasses/tostring/overriddenProperty.kt b/compiler/testData/codegen/dataClasses/tostring/overriddenProperty.kt new file mode 100644 index 00000000000..cac12ae4177 --- /dev/null +++ b/compiler/testData/codegen/dataClasses/tostring/overriddenProperty.kt @@ -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" +} diff --git a/compiler/testData/codegen/dataClasses/tostring/unitComponent.kt b/compiler/testData/codegen/dataClasses/tostring/unitComponent.kt new file mode 100644 index 00000000000..0b6485cb03a --- /dev/null +++ b/compiler/testData/codegen/dataClasses/tostring/unitComponent.kt @@ -0,0 +1,6 @@ +data class A(val x: Unit) + +fun box(): String { + val a = A(#()) + return if ("$a" == "A{x=()}") "OK" else "$a" +} diff --git a/compiler/tests/org/jetbrains/jet/codegen/DataClassCodegenTestGenerated.java b/compiler/tests/org/jetbrains/jet/codegen/DataClassCodegenTestGenerated.java index cc710df6e8e..90300810ad5 100644 --- a/compiler/tests/org/jetbrains/jet/codegen/DataClassCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/codegen/DataClassCodegenTestGenerated.java @@ -13,23 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.jetbrains.jet.codegen; -import junit.framework.Assert; import junit.framework.Test; import junit.framework.TestSuite; - -import java.io.File; import org.jetbrains.jet.JetTestUtils; +import org.jetbrains.jet.test.InnerTestClasses; 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 */ @TestMetadata("compiler/testData/codegen/dataClasses") +@InnerTestClasses({DataClassCodegenTestGenerated.Tostring.class}) public class DataClassCodegenTestGenerated extends AbstractDataClassCodegenTest { 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") @@ -82,4 +82,49 @@ public class DataClassCodegenTestGenerated extends AbstractDataClassCodegenTest 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; + } }