equals generation for data classes

This commit is contained in:
Alex Tkachman
2012-09-17 18:48:17 +03:00
parent 8aa13a301c
commit 8e463ac0ac
11 changed files with 250 additions and 117 deletions
@@ -126,7 +126,7 @@ public class ClosureCodegen extends GenerationStateAware {
}
else if (state.getClassBuilderMode() == ClassBuilderMode.FULL) {
mv.visitCode();
initSingletonField(fun, name.getAsmType(), cv, iv);
initSingletonField(name.getAsmType(), iv);
mv.visitInsn(RETURN);
FunctionCodegen.endVisit(mv, "<clinit>", fun);
}
@@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.containers.Stack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -44,6 +45,7 @@ import org.jetbrains.jet.lang.resolve.name.Name;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.types.lang.JetStandardClasses;
import org.jetbrains.jet.lang.types.lang.JetStandardLibrary;
import org.jetbrains.jet.lexer.JetTokens;
import java.util.*;
@@ -365,7 +367,7 @@ public class CodegenUtil {
}
}
public static void initSingletonField(PsiElement element, Type classAsmType, ClassBuilder builder, InstructionAdapter iv) {
public static void initSingletonField(Type classAsmType, InstructionAdapter iv) {
iv.anew(classAsmType);
iv.dup();
iv.invokespecial(classAsmType.getInternalName(), "<init>", "()V");
@@ -438,4 +440,99 @@ public class CodegenUtil {
iv.xor(Type.LONG_TYPE);
mv.visitInsn(L2I);
}
static StackValue compareExpressionsOnStack(InstructionAdapter v, IElementType opToken, Type operandType) {
if (operandType.getSort() == Type.OBJECT) {
v.invokeinterface("java/lang/Comparable", "compareTo", "(Ljava/lang/Object;)I");
v.iconst(0);
operandType = Type.INT_TYPE;
}
return StackValue.cmp(opToken, operandType);
}
static StackValue generateNullSafeEquals(
InstructionAdapter v,
IElementType opToken,
boolean leftNullable,
boolean rightNullable
) {
if (!leftNullable) {
v.invokevirtual("java/lang/Object", "equals", "(Ljava/lang/Object;)Z");
if (opToken == JetTokens.EXCLEQ) {
invertBoolean(v);
}
}
else {
if (rightNullable) {
v.dup2(); // left right left right
Label rightNull = new Label();
v.ifnull(rightNull);
Label leftNull = new Label();
v.ifnull(leftNull);
v.invokevirtual("java/lang/Object", "equals", "(Ljava/lang/Object;)Z");
if (opToken == JetTokens.EXCLEQ || opToken == JetTokens.EXCLEQEQEQ) {
invertBoolean(v);
}
Label end = new Label();
v.goTo(end);
v.mark(rightNull);
// left right left
Label bothNull = new Label();
v.ifnull(bothNull);
v.mark(leftNull);
v.pop2();
v.iconst(opToken == JetTokens.EXCLEQ || opToken == JetTokens.EXCLEQEQEQ ? 1 : 0);
v.goTo(end);
v.mark(bothNull);
v.pop2();
v.iconst(opToken == JetTokens.EXCLEQ || opToken == JetTokens.EXCLEQEQEQ ? 0 : 1);
v.mark(end);
}
else {
v.dup2(); // left right left right
v.pop();
Label leftNull = new Label();
v.ifnull(leftNull);
v.invokevirtual("java/lang/Object", "equals", "(Ljava/lang/Object;)Z");
if (opToken == JetTokens.EXCLEQ || opToken == JetTokens.EXCLEQEQEQ) {
invertBoolean(v);
}
Label end = new Label();
v.goTo(end);
// left right
v.mark(leftNull);
v.pop2();
v.iconst(opToken == JetTokens.EXCLEQ ? 1 : 0);
v.mark(end);
}
}
return StackValue.onStack(Type.BOOLEAN_TYPE);
}
static void invertBoolean(InstructionAdapter v) {
v.iconst(1);
v.xor(Type.INT_TYPE);
}
public static StackValue generateEqualsForExpressionsOnStack(
InstructionAdapter v,
IElementType opToken,
Type leftType,
Type rightType,
boolean leftNullable,
boolean rightNullable
) {
if ((isNumberPrimitive(leftType) || leftType.getSort() == Type.BOOLEAN) && leftType == rightType) {
return compareExpressionsOnStack(v, opToken, leftType);
}
else {
if (opToken == JetTokens.EQEQEQ || opToken == JetTokens.EXCLEQEQEQ) {
return StackValue.cmp(opToken, leftType);
}
else {
return generateNullSafeEquals(v, opToken, leftNullable, rightNullable);
}
}
}
}
@@ -2256,7 +2256,7 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
else {
invokeFunctionByReference(expression.getOperationReference());
if (inverted) {
invertBoolean();
invertBoolean(v);
}
}
return StackValue.onStack(Type.BOOLEAN_TYPE);
@@ -2297,7 +2297,7 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
v.and(Type.INT_TYPE);
if (inverted) {
invertBoolean();
invertBoolean(v);
}
}
@@ -2357,10 +2357,10 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
if (isPrimitive(leftType)) // both are primitive
{
return generateEqualsForExpressionsOnStack(opToken, leftType, rightType, false, false);
return generateEqualsForExpressionsOnStack(v, opToken, leftType, rightType, false, false);
}
return generateEqualsForExpressionsOnStack(opToken, leftType, rightType, leftJetType.isNullable(), rightJetType.isNullable());
return generateEqualsForExpressionsOnStack(v, opToken, leftType, rightType, leftJetType.isNullable(), rightJetType.isNullable());
}
private StackValue genCmpWithNull(JetExpression exp, Type expType, IElementType opToken) {
@@ -2379,81 +2379,6 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
return StackValue.onStack(Type.BOOLEAN_TYPE);
}
public StackValue generateEqualsForExpressionsOnStack(
IElementType opToken,
Type leftType,
Type rightType,
boolean leftNullable,
boolean rightNullable
) {
if ((CodegenUtil.isNumberPrimitive(leftType) || leftType.getSort() == Type.BOOLEAN) && leftType == rightType) {
return compareExpressionsOnStack(opToken, leftType);
}
else {
if (opToken == JetTokens.EQEQEQ || opToken == JetTokens.EXCLEQEQEQ) {
return StackValue.cmp(opToken, leftType);
}
else {
return generateNullSafeEquals(opToken, leftNullable, rightNullable);
}
}
}
private StackValue generateNullSafeEquals(IElementType opToken, boolean leftNullable, boolean rightNullable) {
if (!leftNullable) {
v.invokevirtual("java/lang/Object", "equals", "(Ljava/lang/Object;)Z");
if (opToken == JetTokens.EXCLEQ) {
invertBoolean();
}
}
else {
if (rightNullable) {
v.dup2(); // left right left right
Label rightNull = new Label();
v.ifnull(rightNull);
Label leftNull = new Label();
v.ifnull(leftNull);
v.invokevirtual("java/lang/Object", "equals", "(Ljava/lang/Object;)Z");
if (opToken == JetTokens.EXCLEQ || opToken == JetTokens.EXCLEQEQEQ) {
invertBoolean();
}
Label end = new Label();
v.goTo(end);
v.mark(rightNull);
// left right left
Label bothNull = new Label();
v.ifnull(bothNull);
v.mark(leftNull);
v.pop2();
v.iconst(opToken == JetTokens.EXCLEQ || opToken == JetTokens.EXCLEQEQEQ ? 1 : 0);
v.goTo(end);
v.mark(bothNull);
v.pop2();
v.iconst(opToken == JetTokens.EXCLEQ || opToken == JetTokens.EXCLEQEQEQ ? 0 : 1);
v.mark(end);
}
else {
v.dup2(); // left right left right
v.pop();
Label leftNull = new Label();
v.ifnull(leftNull);
v.invokevirtual("java/lang/Object", "equals", "(Ljava/lang/Object;)Z");
if (opToken == JetTokens.EXCLEQ || opToken == JetTokens.EXCLEQEQEQ) {
invertBoolean();
}
Label end = new Label();
v.goTo(end);
// left right
v.mark(leftNull);
v.pop2();
v.iconst(opToken == JetTokens.EXCLEQ ? 1 : 0);
v.mark(end);
}
}
return StackValue.onStack(Type.BOOLEAN_TYPE);
}
private StackValue generateElvis(JetBinaryExpression expression) {
final Type exprType = expressionType(expression);
JetType type = bindingContext.get(BindingContext.EXPRESSION_TYPE, expression.getLeft());
@@ -2490,16 +2415,7 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
private StackValue generateCompareOp(JetExpression left, JetExpression right, IElementType opToken, Type operandType) {
gen(left, operandType);
gen(right, operandType);
return compareExpressionsOnStack(opToken, operandType);
}
private StackValue compareExpressionsOnStack(IElementType opToken, Type operandType) {
if (operandType.getSort() == Type.OBJECT) {
v.invokeinterface("java/lang/Comparable", "compareTo", "(Ljava/lang/Object;)I");
v.iconst(0);
operandType = Type.INT_TYPE;
}
return StackValue.cmp(opToken, operandType);
return compareExpressionsOnStack(v, opToken, operandType);
}
private StackValue generateAssignmentExpression(JetBinaryExpression expression) {
@@ -3294,7 +3210,7 @@ The "returned" value of try expression with no finally is either the last expres
patternIsNullable = condJetType != null && condJetType.isNullable();
}
gen(patternExpression, condType);
return generateEqualsForExpressionsOnStack(JetTokens.EQEQ, subjectType, condType, expressionToMatchIsNullable,
return generateEqualsForExpressionsOnStack(v, JetTokens.EQEQ, subjectType, condType, expressionToMatchIsNullable,
patternIsNullable);
}
else {
@@ -3420,7 +3336,7 @@ The "returned" value of try expression with no finally is either the last expres
//invokeFunctionNoParams(op, Type.BOOLEAN_TYPE, v);
invokeFunctionByReference(operationReference);
if (inverted) {
invertBoolean();
invertBoolean(v);
}
}
return StackValue.onStack(Type.BOOLEAN_TYPE);
@@ -3446,11 +3362,6 @@ The "returned" value of try expression with no finally is either the last expres
invokeFunction(call, StackValue.none(), resolvedCall);
}
private void invertBoolean() {
v.iconst(1);
v.xor(Type.INT_TYPE);
}
private boolean isIntRangeExpr(JetExpression rangeExpression) {
if (rangeExpression instanceof JetBinaryExpression) {
JetBinaryExpression binaryExpression = (JetBinaryExpression) rangeExpression;
@@ -386,21 +386,66 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
}
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);
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();
final InstructionAdapter iv = new InstructionAdapter(mv);
mv.visitCode();
Label eq = new Label();
Label ne = new Label();
iv.load(0, OBJECT_TYPE);
iv.load(1, AsmTypeConstants.OBJECT_TYPE);
iv.ifacmpeq(eq);
iv.load(1, AsmTypeConstants.OBJECT_TYPE);
iv.instanceOf(classAsmType);
iv.ifeq(ne);
iv.load(1, AsmTypeConstants.OBJECT_TYPE);
iv.checkcast(classAsmType);
iv.store(2, AsmTypeConstants.OBJECT_TYPE);
for (PropertyDescriptor propertyDescriptor : properties) {
final JetType type = propertyDescriptor.getType();
final Type asmType = typeMapper.mapType(type);
genPropertyOnStack(iv, propertyDescriptor, 0);
genPropertyOnStack(iv, propertyDescriptor, 2);
if (asmType.getSort() == Type.ARRAY) {
final Type elementType = CodegenUtil.correctElementType(asmType);
if (elementType.getSort() == Type.OBJECT || elementType.getSort() == Type.ARRAY) {
iv.invokestatic("java/util/Arrays", "equals", "([Ljava/lang/Object;[Ljava/lang/Object;)Z");
}
else {
iv.invokestatic("java/util/Arrays", "equals", "([" + elementType.getDescriptor() + "[" + elementType.getDescriptor() + ")Z");
}
}
else {
final StackValue value =
generateEqualsForExpressionsOnStack(iv, JetTokens.EQEQ, asmType, asmType, type.isNullable(), type.isNullable());
value.put(Type.BOOLEAN_TYPE, iv);
}
iv.ifeq(ne);
}
iv.mark(eq);
iv.iconst(1);
iv.areturn(Type.INT_TYPE);
iv.mark(ne);
iv.iconst(0);
iv.areturn(Type.INT_TYPE);
FunctionCodegen.endVisit(mv, "equals", myClass);
}
private void generateDataClassHashCodeMethod(List<PropertyDescriptor> properties) {
final MethodVisitor mv = v.getVisitor().visitMethod(ACC_PUBLIC, "hashCode", "()I", null, null);
final InstructionAdapter iv = new InstructionAdapter(mv);
mv.visitCode();
boolean first = true;
for (PropertyDescriptor propertyDescriptor : properties) {
if (!first) {
@@ -408,7 +453,7 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
iv.mul(Type.INT_TYPE);
}
genPropertyOnStack(iv, propertyDescriptor);
genPropertyOnStack(iv, propertyDescriptor, 0);
Label ifNull = null;
if (propertyDescriptor.getType().isNullable()) {
@@ -438,14 +483,15 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
}
mv.visitInsn(IRETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
FunctionCodegen.endVisit(mv, "hashCode", myClass);
}
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.visitCode();
generateStringBuilderConstructor(iv);
boolean first = true;
@@ -459,7 +505,7 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
}
invokeAppendMethod(iv, JAVA_STRING_TYPE);
Type type = genPropertyOnStack(iv, propertyDescriptor);
Type type = genPropertyOnStack(iv, propertyDescriptor, 0);
if (type.getSort() == Type.ARRAY) {
final Type elementType = correctElementType(type);
@@ -486,8 +532,8 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
FunctionCodegen.endVisit(mv, "toString", myClass);
}
private Type genPropertyOnStack(InstructionAdapter iv, PropertyDescriptor propertyDescriptor) {
iv.load(0, classAsmType);
private Type genPropertyOnStack(InstructionAdapter iv, PropertyDescriptor propertyDescriptor, int index) {
iv.load(index, 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();
@@ -714,7 +760,7 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
staticInitializerChunks.add(new CodeChunk() {
@Override
public void generate(InstructionAdapter iv) {
initSingletonField(myClass, classAsmType, v, iv);
initSingletonField(classAsmType, iv);
}
});
}
@@ -20,13 +20,14 @@ 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.JetCallExpression;
import org.jetbrains.jet.lang.psi.JetExpression;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.java.AsmTypeConstants;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lexer.JetTokens;
@@ -74,8 +75,9 @@ public class Equals implements IntrinsicMethod {
codegen.gen(rightExpr).put(AsmTypeConstants.OBJECT_TYPE, v);
assert rightType != null;
return codegen
.generateEqualsForExpressionsOnStack(JetTokens.EQEQ, AsmTypeConstants.OBJECT_TYPE, AsmTypeConstants.OBJECT_TYPE, leftNullable,
return CodegenUtil
.generateEqualsForExpressionsOnStack(v, JetTokens.EQEQ, AsmTypeConstants.OBJECT_TYPE, AsmTypeConstants.OBJECT_TYPE,
leftNullable,
rightType.isNullable());
}
}
@@ -0,0 +1,6 @@
data class A(val v: Array<Int>)
fun box() : String {
if(A(array(0,1,2)) != A(array(0,1,2))) return "fail"
return "OK"
}
@@ -0,0 +1,13 @@
class Dummy {
fun equals(other: Any?) = true
}
open data class A(val v: Any)
class B(v: Any) : A(v)
fun box() : String {
val a = A(Dummy())
val b = B(Dummy())
return if(b == a) "OK" else "fail"
}
@@ -0,0 +1,6 @@
data class A(val v: IntArray)
fun box() : String {
if(A(intArray(0,1,2)) != A(intArray(0,1,2))) return "fail"
return "OK"
}
@@ -0,0 +1,11 @@
class Dummy {
fun equals(other: Any?) = true
}
data class A(val v: Any?)
fun box() : String {
val a = A(Dummy())
val b: A? = null
return if(a != b && b != a) "OK" else "fail"
}
@@ -0,0 +1,7 @@
data class A()
fun box() : String {
val a = A()
val b = a
return if(b == a) "OK" else "fail"
}
@@ -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.Hashcode.class, DataClassCodegenTestGenerated.Tostring.class})
@InnerTestClasses({DataClassCodegenTestGenerated.Equals.class, 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,6 +82,39 @@ public class DataClassCodegenTestGenerated extends AbstractDataClassCodegenTest
blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/unitComponent.kt");
}
@TestMetadata("compiler/testData/codegen/dataClasses/equals")
public static class Equals extends AbstractDataClassCodegenTest {
public void testAllFilesPresentInEquals() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.codegen.AbstractDataClassCodegenTest", new File("compiler/testData/codegen/dataClasses/equals"), "kt", true);
}
@TestMetadata("genericarray.kt")
public void testGenericarray() throws Exception {
blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/equals/genericarray.kt");
}
@TestMetadata("instanceof.kt")
public void testInstanceof() throws Exception {
blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/equals/instanceof.kt");
}
@TestMetadata("intarray.kt")
public void testIntarray() throws Exception {
blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/equals/intarray.kt");
}
@TestMetadata("nullother.kt")
public void testNullother() throws Exception {
blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/equals/nullother.kt");
}
@TestMetadata("sameinstance.kt")
public void testSameinstance() throws Exception {
blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/equals/sameinstance.kt");
}
}
@TestMetadata("compiler/testData/codegen/dataClasses/hashcode")
public static class Hashcode extends AbstractDataClassCodegenTest {
public void testAllFilesPresentInHashcode() throws Exception {
@@ -181,6 +214,7 @@ public class DataClassCodegenTestGenerated extends AbstractDataClassCodegenTest
public static Test suite() {
TestSuite suite = new TestSuite("DataClassCodegenTestGenerated");
suite.addTestSuite(DataClassCodegenTestGenerated.class);
suite.addTestSuite(Equals.class);
suite.addTestSuite(Hashcode.class);
suite.addTestSuite(Tostring.class);
return suite;