hashCode generation for data classes

This commit is contained in:
Alex Tkachman
2012-09-17 15:52:23 +03:00
parent d825fb25a0
commit 8c823486d7
14 changed files with 264 additions and 37 deletions
@@ -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);
}
}
@@ -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<PropertyDescriptor> getDataProperties() {
ArrayList<PropertyDescriptor> 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<PropertyDescriptor> properties = getDataProperties();
if (!properties.isEmpty()) {
generateDataClassToStringMethod(properties);
generateDataClassHashCodeMethod(properties);
generateDataClassEqualsMethod(properties);
}
}
private void generateDataClassEqualsMethod() {
private void generateDataClassEqualsMethod(List<PropertyDescriptor> 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<PropertyDescriptor> 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<PropertyDescriptor> 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;
@@ -0,0 +1,6 @@
data class A(val a: IntArray, var b: Array<String>)
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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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 {
@@ -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;
}