diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/CodegenUtil.java b/compiler/backend/src/org/jetbrains/jet/codegen/CodegenUtil.java index 787436e23b1..08df3358e80 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/CodegenUtil.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/CodegenUtil.java @@ -23,6 +23,7 @@ import com.intellij.psi.PsiElement; import com.intellij.util.containers.Stack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.asm4.Label; import org.jetbrains.asm4.MethodVisitor; import org.jetbrains.asm4.Type; import org.jetbrains.asm4.commons.InstructionAdapter; @@ -393,4 +394,48 @@ public class CodegenUtil { v.invokestatic("java/lang/String", "valueOf", "(" + type.getDescriptor() + ")Ljava/lang/String;"); return StackValue.onStack(JAVA_STRING_TYPE); } + + static void genHashCode(MethodVisitor mv, InstructionAdapter iv, Type type) { + if (type.getSort() == Type.ARRAY) { + final Type elementType = correctElementType(type); + if (elementType.getSort() == Type.OBJECT || elementType.getSort() == Type.ARRAY) { + iv.invokestatic("java/util/Arrays", "hashCode", "([Ljava/lang/Object;)I"); + } + else { + iv.invokestatic("java/util/Arrays", "hashCode", "(" + type.getDescriptor() + ")I"); + } + } + else if (type.getSort() == Type.OBJECT) { + iv.invokevirtual("java/lang/Object", "hashCode", "()I"); + } + else if (type.getSort() == Type.LONG) { + genLongHashCode(mv, iv); + } + else if (type.getSort() == Type.DOUBLE) { + iv.invokestatic("java/lang/Double", "doubleToLongBits", "(D)J"); + genLongHashCode(mv, iv); + } + else if (type.getSort() == Type.FLOAT) { + iv.invokestatic("java/lang/Float", "floatToIntBits", "(F)I"); + } + else if (type.getSort() == Type.BOOLEAN) { + Label end = new Label(); + iv.dup(); + iv.ifeq(end); + iv.pop(); + iv.iconst(1); + iv.mark(end); + } + else { // byte short char int + // do nothing + } + } + + private static void genLongHashCode(MethodVisitor mv, InstructionAdapter iv) { + iv.dup2(); + iv.iconst(32); + iv.ushr(Type.LONG_TYPE); + iv.xor(Type.LONG_TYPE); + mv.visitInsn(L2I); + } } diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java b/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java index 74cd8b819b8..09e9e14ec59 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java @@ -22,6 +22,7 @@ import com.intellij.openapi.util.Pair; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.asm4.AnnotationVisitor; +import org.jetbrains.asm4.Label; import org.jetbrains.asm4.MethodVisitor; import org.jetbrains.asm4.Type; import org.jetbrains.asm4.commons.InstructionAdapter; @@ -358,17 +359,33 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { generateClosureFields(context.closure, v, state.getTypeMapper()); } + private List getDataProperties() { + ArrayList result = Lists.newArrayList(); + for (JetParameter parameter : getPrimaryConstructorParameters()) { + if (parameter.getValOrVarNode() == null) continue; + + PropertyDescriptor propertyDescriptor = state.getBindingContext().get(BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, parameter); + assert propertyDescriptor != null; + + result.add(propertyDescriptor); + } + return result; + } + private void generateFunctionsForDataClasses() { if (!JetStandardLibrary.isData(descriptor)) return; generateComponentFunctionsForDataClasses(); - generateDataClassToStringMethod(); - generateDataClassHashCodeMethod(); - generateDataClassEqualsMethod(); + final List properties = getDataProperties(); + if (!properties.isEmpty()) { + generateDataClassToStringMethod(properties); + generateDataClassHashCodeMethod(properties); + generateDataClassEqualsMethod(properties); + } } - private void generateDataClassEqualsMethod() { + private void generateDataClassEqualsMethod(List properties) { // todo: this is fake implementation final MethodVisitor mv = v.getVisitor().visitMethod(ACC_PUBLIC, "equals", "(Ljava/lang/Object;)Z", null, null); @@ -380,50 +397,69 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { mv.visitEnd(); } - private void generateDataClassHashCodeMethod() { - // todo: this is fake implementation - + private void generateDataClassHashCodeMethod(List properties) { final MethodVisitor mv = v.getVisitor().visitMethod(ACC_PUBLIC, "hashCode", "()I", null, null); - mv.visitVarInsn(ALOAD, 0); - mv.visitMethodInsn(INVOKESPECIAL, superClassAsmType.getInternalName(), "hashCode", "()I"); + final InstructionAdapter iv = new InstructionAdapter(mv); + + boolean first = true; + for (PropertyDescriptor propertyDescriptor : properties) { + if (!first) { + iv.iconst(31); + iv.mul(Type.INT_TYPE); + } + + genPropertyOnStack(iv, propertyDescriptor); + + Label ifNull = null; + if (propertyDescriptor.getType().isNullable()) { + ifNull = new Label(); + + iv.dup(); + iv.ifnull(ifNull); + } + + genHashCode(mv, iv, typeMapper.mapType(propertyDescriptor.getType())); + + if (ifNull != null) { + Label end = new Label(); + iv.goTo(end); + iv.mark(ifNull); + iv.pop(); + iv.iconst(0); + iv.mark(end); + } + + if (first) { + first = false; + } + else { + iv.add(Type.INT_TYPE); + } + } + mv.visitInsn(IRETURN); - mv.visitMaxs(-1,-1); + mv.visitMaxs(-1, -1); mv.visitEnd(); } - private void generateDataClassToStringMethod() { + private void generateDataClassToStringMethod(List properties) { 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) { + for (PropertyDescriptor propertyDescriptor : properties) { + if (first) { iv.aconst(descriptor.getName() + "{" + propertyDescriptor.getName().getName()+"="); first = false; } else { iv.aconst(", " + propertyDescriptor.getName().getName()+"="); } - CodegenUtil.invokeAppendMethod(iv, JAVA_STRING_TYPE); + 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()); + Type type = genPropertyOnStack(iv, propertyDescriptor); if (type.getSort() == Type.ARRAY) { final Type elementType = correctElementType(type); @@ -438,11 +474,11 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { } } } - CodegenUtil.invokeAppendMethod(iv, type); + invokeAppendMethod(iv, type); } iv.aconst("}"); - CodegenUtil.invokeAppendMethod(iv, JAVA_STRING_TYPE); + invokeAppendMethod(iv, JAVA_STRING_TYPE); iv.invokevirtual("java/lang/StringBuilder", "toString", "()Ljava/lang/String;"); iv.areturn(JAVA_STRING_TYPE); @@ -450,6 +486,19 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { FunctionCodegen.endVisit(mv, "toString", myClass); } + private Type genPropertyOnStack(InstructionAdapter iv, PropertyDescriptor propertyDescriptor) { + 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(), propertyDescriptor.getName().getName(), type.getDescriptor()); + return type; + } + private void generateComponentFunctionsForDataClasses() { if (!myClass.hasPrimaryConstructor() || !JetStandardLibrary.isData(descriptor)) return; diff --git a/compiler/testData/codegen/dataClasses/hashcode/array.kt b/compiler/testData/codegen/dataClasses/hashcode/array.kt new file mode 100644 index 00000000000..b9eceabe0ea --- /dev/null +++ b/compiler/testData/codegen/dataClasses/hashcode/array.kt @@ -0,0 +1,6 @@ +data class A(val a: IntArray, var b: Array) + +fun box() : String { + if( A(intArray(1,2,3),array("239")).hashCode() != 31*java.util.Arrays.hashCode(intArray(0,1,2)) + "239".hashCode()) "fail" + return "OK" +} diff --git a/compiler/testData/codegen/dataClasses/hashcode/boolean.kt b/compiler/testData/codegen/dataClasses/hashcode/boolean.kt new file mode 100644 index 00000000000..afc41bbe2e7 --- /dev/null +++ b/compiler/testData/codegen/dataClasses/hashcode/boolean.kt @@ -0,0 +1,5 @@ +data class A(val a: Boolean) + +fun box() : String { + return if( A(true).hashCode()==1 && A(false).hashCode()==0 ) "OK" else "fail" +} diff --git a/compiler/testData/codegen/dataClasses/hashcode/byte.kt b/compiler/testData/codegen/dataClasses/hashcode/byte.kt new file mode 100644 index 00000000000..d01f082be59 --- /dev/null +++ b/compiler/testData/codegen/dataClasses/hashcode/byte.kt @@ -0,0 +1,7 @@ +data class A(val a: Byte) + +fun box() : String { + val v1 = A(-10.toByte()).hashCode() + val v2 = (-10.toByte() as Byte?)!!.hashCode() + return if( v1 == v2 ) "OK" else "$v1 $v2" +} diff --git a/compiler/testData/codegen/dataClasses/hashcode/char.kt b/compiler/testData/codegen/dataClasses/hashcode/char.kt new file mode 100644 index 00000000000..e2ac5e67291 --- /dev/null +++ b/compiler/testData/codegen/dataClasses/hashcode/char.kt @@ -0,0 +1,7 @@ +data class A(val a: Char) + +fun box() : String { + val v1 = A('a').hashCode() + val v2 = ('a' as Char?)!!.hashCode() + return if( v1 == v2 ) "OK" else "$v1 $v2" +} diff --git a/compiler/testData/codegen/dataClasses/hashcode/double.kt b/compiler/testData/codegen/dataClasses/hashcode/double.kt new file mode 100644 index 00000000000..f0899ad1b59 --- /dev/null +++ b/compiler/testData/codegen/dataClasses/hashcode/double.kt @@ -0,0 +1,7 @@ +data class A(val a: Double) + +fun box() : String { + val v1 = A(-10.toDouble()).hashCode() + val v2 = (-10.toDouble() as Double?)!!.hashCode() + return if( v1 == v2 ) "OK" else "$v1 $v2" +} diff --git a/compiler/testData/codegen/dataClasses/hashcode/float.kt b/compiler/testData/codegen/dataClasses/hashcode/float.kt new file mode 100644 index 00000000000..41dc25c43c4 --- /dev/null +++ b/compiler/testData/codegen/dataClasses/hashcode/float.kt @@ -0,0 +1,7 @@ +data class A(val a: Float) + +fun box() : String { + val v1 = A(-10.toFloat()).hashCode() + val v2 = (-10.toFloat() as Float?)!!.hashCode() + return if( v1 == v2 ) "OK" else "$v1 $v2" +} diff --git a/compiler/testData/codegen/dataClasses/hashcode/int.kt b/compiler/testData/codegen/dataClasses/hashcode/int.kt new file mode 100644 index 00000000000..0596e4c72af --- /dev/null +++ b/compiler/testData/codegen/dataClasses/hashcode/int.kt @@ -0,0 +1,7 @@ +data class A(val a: Int) + +fun box() : String { + val v1 = A(-10.toInt()).hashCode() + val v2 = (-10.toInt() as Int?)!!.hashCode() + return if( v1 == v2 ) "OK" else "$v1 $v2" +} diff --git a/compiler/testData/codegen/dataClasses/hashcode/long.kt b/compiler/testData/codegen/dataClasses/hashcode/long.kt new file mode 100644 index 00000000000..067593cb672 --- /dev/null +++ b/compiler/testData/codegen/dataClasses/hashcode/long.kt @@ -0,0 +1,7 @@ +data class A(val a: Long) + +fun box() : String { + val v1 = A(-10.toLong()).hashCode() + val v2 = (-10.toLong() as Long?)!!.hashCode() + return if( v1 == v2 ) "OK" else "$v1 $v2" +} diff --git a/compiler/testData/codegen/dataClasses/hashcode/null.kt b/compiler/testData/codegen/dataClasses/hashcode/null.kt new file mode 100644 index 00000000000..7bde4f3e135 --- /dev/null +++ b/compiler/testData/codegen/dataClasses/hashcode/null.kt @@ -0,0 +1,16 @@ +data class A(val a: Any?, var x: Int) +data class B(val a: Any?, x: Int) +data class C(val a: Int, var x: Int?) +data class D(val a: Int?) + +fun box() : String { + if( A(null,19).hashCode() != 19) "fail" + if( A(239,19).hashCode() != (239*31+19)) "fail" + if( B(null,19).hashCode() != 0) "fail" + if( B(239,19).hashCode() != 239) "fail" + if( C(239,19).hashCode() != (239*31+19)) "fail" + if( C(239,null).hashCode() != 239*31) "fail" + if( D(239).hashCode() != (239)) "fail" + if( D(null).hashCode() != 0) "fail" + return "OK" +} diff --git a/compiler/testData/codegen/dataClasses/hashcode/short.kt b/compiler/testData/codegen/dataClasses/hashcode/short.kt new file mode 100644 index 00000000000..88e26b6d05e --- /dev/null +++ b/compiler/testData/codegen/dataClasses/hashcode/short.kt @@ -0,0 +1,7 @@ +data class A(val a: Short) + +fun box() : String { + val v1 = A(-10.toShort()).hashCode() + val v2 = (-10.toShort() as Short?)!!.hashCode() + return if( v1 == v2 ) "OK" else "$v1 $v2" +} diff --git a/compiler/tests/org/jetbrains/jet/codegen/AbstractDataClassCodegenTest.java b/compiler/tests/org/jetbrains/jet/codegen/AbstractDataClassCodegenTest.java index a3c7448ac8c..c6918d0a351 100644 --- a/compiler/tests/org/jetbrains/jet/codegen/AbstractDataClassCodegenTest.java +++ b/compiler/tests/org/jetbrains/jet/codegen/AbstractDataClassCodegenTest.java @@ -16,7 +16,6 @@ package org.jetbrains.jet.codegen; -import org.jetbrains.jet.ConfigurationKind; import org.jetbrains.jet.test.generator.SimpleTestClassModel; import org.jetbrains.jet.test.generator.TestGenerator; @@ -31,7 +30,7 @@ public abstract class AbstractDataClassCodegenTest extends CodegenTestCase { @Override protected void setUp() throws Exception { super.setUp(); - createEnvironmentWithMockJdkAndIdeaAnnotations(ConfigurationKind.JDK_ONLY); + createEnvironmentWithFullJdk(); } public static void main(String[] args) throws IOException { diff --git a/compiler/tests/org/jetbrains/jet/codegen/DataClassCodegenTestGenerated.java b/compiler/tests/org/jetbrains/jet/codegen/DataClassCodegenTestGenerated.java index 90300810ad5..81e9910d74d 100644 --- a/compiler/tests/org/jetbrains/jet/codegen/DataClassCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/codegen/DataClassCodegenTestGenerated.java @@ -26,7 +26,7 @@ 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}) +@InnerTestClasses({DataClassCodegenTestGenerated.Hashcode.class, 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", true); @@ -82,11 +82,68 @@ public class DataClassCodegenTestGenerated extends AbstractDataClassCodegenTest blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/unitComponent.kt"); } + @TestMetadata("compiler/testData/codegen/dataClasses/hashcode") + public static class Hashcode extends AbstractDataClassCodegenTest { + public void testAllFilesPresentInHashcode() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.codegen.AbstractDataClassCodegenTest", new File("compiler/testData/codegen/dataClasses/hashcode"), "kt", true); + } + + @TestMetadata("array.kt") + public void testArray() throws Exception { + blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/hashcode/array.kt"); + } + + @TestMetadata("boolean.kt") + public void testBoolean() throws Exception { + blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/hashcode/boolean.kt"); + } + + @TestMetadata("byte.kt") + public void testByte() throws Exception { + blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/hashcode/byte.kt"); + } + + @TestMetadata("char.kt") + public void testChar() throws Exception { + blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/hashcode/char.kt"); + } + + @TestMetadata("double.kt") + public void testDouble() throws Exception { + blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/hashcode/double.kt"); + } + + @TestMetadata("float.kt") + public void testFloat() throws Exception { + blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/hashcode/float.kt"); + } + + @TestMetadata("int.kt") + public void testInt() throws Exception { + blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/hashcode/int.kt"); + } + + @TestMetadata("long.kt") + public void testLong() throws Exception { + blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/hashcode/long.kt"); + } + + @TestMetadata("null.kt") + public void testNull() throws Exception { + blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/hashcode/null.kt"); + } + + @TestMetadata("short.kt") + public void testShort() throws Exception { + blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/hashcode/short.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); + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.codegen.AbstractDataClassCodegenTest", new File("compiler/testData/codegen/dataClasses/tostring"), "kt", true); } @TestMetadata("arrayParams.kt") @@ -124,6 +181,7 @@ public class DataClassCodegenTestGenerated extends AbstractDataClassCodegenTest public static Test suite() { TestSuite suite = new TestSuite("DataClassCodegenTestGenerated"); suite.addTestSuite(DataClassCodegenTestGenerated.class); + suite.addTestSuite(Hashcode.class); suite.addTestSuite(Tostring.class); return suite; }