hashCode generation for data classes
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user