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