Fix == for inline classes with boxes

TODO generalize code generating object vs primitive equality

 #KT-25914 Fixed
 #KT-25981 Fixed
 #KT-25983 Fixed
This commit is contained in:
Dmitry Petrov
2018-08-09 18:30:09 +03:00
parent cd12772bc5
commit 4f6aa50417
10 changed files with 225 additions and 29 deletions
@@ -3210,9 +3210,32 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
@Nullable StackValue pregeneratedSubject,
@Nullable PrimitiveNumericComparisonInfo primitiveNumericComparisonInfo
) {
if (left == null || right == null) {
return StackValue.operation(Type.VOID_TYPE, v -> {
v.aconst(null);
v.athrow();
return Unit.INSTANCE;
});
}
KotlinType leftKotlinType = kotlinType(left);
KotlinType rightKotlinType = kotlinType(right);
boolean leftIsInlineClass = leftKotlinType != null && InlineClassesUtilsKt.isInlineClassType(leftKotlinType);
boolean rightIsInlineClass = rightKotlinType != null && InlineClassesUtilsKt.isInlineClassType(rightKotlinType);
Type leftType = pregeneratedSubject != null ? pregeneratedSubject.type : expressionType(left);
Type rightType = expressionType(right);
boolean leftIsInlineClassWithPrimitiveEquality = leftIsInlineClass && isInlineClassTypeWithPrimitiveEquality(leftKotlinType);
boolean rightIsInlineClassWithPrimitiveEquality = rightIsInlineClass && isInlineClassTypeWithPrimitiveEquality(rightKotlinType);
boolean leftHasPrimitiveEquality = isPrimitive(leftType) && (!leftIsInlineClass || leftIsInlineClassWithPrimitiveEquality);
boolean rightHasPrimitiveEquality = isPrimitive(rightType) && (!rightIsInlineClass || rightIsInlineClassWithPrimitiveEquality);
if (leftIsInlineClass && !leftIsInlineClassWithPrimitiveEquality || rightIsInlineClass && !rightIsInlineClassWithPrimitiveEquality) {
return generateEqualityWithInlineClassesBoxing(left, leftType, right, rightType, opToken, pregeneratedSubject);
}
if (KtPsiUtil.isNullConstant(left)) {
return genCmpWithNull(right, opToken, null);
}
@@ -3221,51 +3244,55 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
return genCmpWithNull(left, opToken, pregeneratedSubject);
}
if (isIntZero(left, leftType) && isIntPrimitive(rightType)) {
if (isIntZero(left, leftType) && rightHasPrimitiveEquality && isIntPrimitive(rightType)) {
return genCmpWithZero(right, opToken, null);
}
if (isIntZero(right, rightType) && isIntPrimitive(leftType)) {
if (isIntZero(right, rightType) && leftHasPrimitiveEquality && isIntPrimitive(leftType)) {
return genCmpWithZero(left, opToken, pregeneratedSubject);
}
if (pregeneratedSubject == null && left instanceof KtSafeQualifiedExpression &&
isSelectorPureNonNullType((KtSafeQualifiedExpression) left) && isPrimitive(rightType)) {
isSelectorPureNonNullType((KtSafeQualifiedExpression) left) &&
isPrimitive(rightType) && rightHasPrimitiveEquality) {
return genCmpSafeCallToPrimitive((KtSafeQualifiedExpression) left, right, rightType, opToken);
}
if (isPrimitive(leftType) && right instanceof KtSafeQualifiedExpression &&
if (isPrimitive(leftType) && leftHasPrimitiveEquality &&
right instanceof KtSafeQualifiedExpression &&
isSelectorPureNonNullType(((KtSafeQualifiedExpression) right))) {
return genCmpPrimitiveToSafeCall(left, leftType, (KtSafeQualifiedExpression) right, opToken, pregeneratedSubject);
}
if (BoxedToPrimitiveEquality.isApplicable(opToken, leftType, rightType)) {
return BoxedToPrimitiveEquality.create(
opToken,
genLazyUnlessProvided(pregeneratedSubject, left, leftType), leftType,
genLazy(right, rightType), rightType,
myFrameMap
);
}
if (!leftIsInlineClass && !rightIsInlineClass) {
if (BoxedToPrimitiveEquality.isApplicable(opToken, leftType, rightType)) {
return BoxedToPrimitiveEquality.create(
opToken,
genLazyUnlessProvided(pregeneratedSubject, left, leftType), leftType,
genLazy(right, rightType), rightType,
myFrameMap
);
}
if (PrimitiveToBoxedEquality.isApplicable(opToken, leftType, rightType)) {
return PrimitiveToBoxedEquality.create(
opToken,
genLazyUnlessProvided(pregeneratedSubject, left, leftType), leftType,
genLazy(right, rightType), rightType
);
}
if (PrimitiveToBoxedEquality.isApplicable(opToken, leftType, rightType)) {
return PrimitiveToBoxedEquality.create(
opToken,
genLazyUnlessProvided(pregeneratedSubject, left, leftType), leftType,
genLazy(right, rightType), rightType
);
}
if (PrimitiveToObjectEquality.isApplicable(opToken, leftType, rightType)) {
return PrimitiveToObjectEquality.create(
opToken,
genLazyUnlessProvided(pregeneratedSubject, left, leftType), leftType,
genLazy(right, rightType), rightType
);
if (PrimitiveToObjectEquality.isApplicable(opToken, leftType, rightType)) {
return PrimitiveToObjectEquality.create(
opToken,
genLazyUnlessProvided(pregeneratedSubject, left, leftType), leftType,
genLazy(right, rightType), rightType
);
}
}
if (isPrimitive(leftType) != isPrimitive(rightType)) {
if (!leftIsInlineClass && !rightIsInlineClass && isPrimitive(leftType) != isPrimitive(rightType)) {
leftType = boxType(leftType);
rightType = boxType(rightType);
}
@@ -3297,6 +3324,38 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
);
}
private StackValue generateEqualityWithInlineClassesBoxing(
@NotNull KtExpression left,
@NotNull Type leftType,
@NotNull KtExpression right,
@NotNull Type rightType,
@NotNull IElementType opToken,
@Nullable StackValue pregeneratedSubject
) {
KotlinType leftKotlinType = kotlinType(left);
KotlinType rightKotlinType = kotlinType(right);
StackValue leftValue = genLazyUnlessProvided(pregeneratedSubject, left, leftType);
StackValue rightValue = genLazy(right, rightType);
return StackValue.operation(Type.BOOLEAN_TYPE, v -> {
KotlinType nullableAnyType = state.getModule().getBuiltIns().getNullableAnyType();
leftValue.put(leftType, leftKotlinType, v);
StackValue.coerce(leftType, leftKotlinType, AsmTypes.OBJECT_TYPE, nullableAnyType, v);
rightValue.put(rightType, rightKotlinType, v);
StackValue.coerce(rightType, rightKotlinType, AsmTypes.OBJECT_TYPE, nullableAnyType, v);
genAreEqualCall(v);
if (opToken == KtTokens.EXCLEQ || opToken == KtTokens.EXCLEQEQEQ) {
genInvertBoolean(v);
}
return Unit.INSTANCE;
});
}
private boolean isEnumExpression(@Nullable KtExpression expression) {
KotlinType expressionType = bindingContext.getType(expression);
if (expressionType == null) return false;
@@ -3362,8 +3421,8 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
/*tries to use IEEE 754 arithmetic*/
private StackValue genEqualsForExpressionsPreferIeee754Arithmetic(
@Nullable KtExpression left,
@Nullable KtExpression right,
@NotNull KtExpression left,
@NotNull KtExpression right,
@NotNull IElementType opToken,
@NotNull Type leftType,
@NotNull Type rightType,
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.codegen
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.UnsignedTypes
import org.jetbrains.kotlin.codegen.context.CodegenContext
import org.jetbrains.kotlin.codegen.context.FieldOwnerContext
import org.jetbrains.kotlin.codegen.context.PackageContext
@@ -36,6 +37,7 @@ import org.jetbrains.kotlin.resolve.annotations.hasJvmStaticAnnotation
import org.jetbrains.kotlin.resolve.calls.callUtil.getFirstArgumentExpression
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.isInlineClassType
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.kotlin.resolve.scopes.receivers.TransientReceiver
@@ -322,7 +324,7 @@ fun initializeVariablesForDestructuredLambdaParameters(codegen: ExpressionCodege
val destructuringDeclaration =
(DescriptorToSourceUtils.descriptorToDeclaration(parameterDescriptor) as? KtParameter)?.destructuringDeclaration
?: error("Destructuring declaration for descriptor $parameterDescriptor not found")
?: error("Destructuring declaration for descriptor $parameterDescriptor not found")
codegen.initializeDestructuringDeclarationVariables(
destructuringDeclaration,
@@ -418,4 +420,14 @@ fun MethodNode.textifyMethodNode(): String {
val sw = StringWriter()
text.print(PrintWriter(sw))
return "$sw"
}
fun KotlinType.isInlineClassTypeWithPrimitiveEquality(): Boolean {
if (!isInlineClassType()) return false
// Always treat unsigned types as inline classes with primitive equality
if (UnsignedTypes.isUnsignedType(this)) return true
// TODO support other inline classes that can be compared as underlying primitives
return false
}
@@ -0,0 +1,16 @@
// !LANGUAGE: +InlineClasses
// IGNORE_BACKEND: JVM_IR, JS_IR, JS
inline class InlineFloat(val data: Float)
inline class InlineDouble(val data: Double)
fun box(): String {
if (InlineFloat(0.0f) == InlineFloat(-0.0f)) throw AssertionError()
if (InlineFloat(Float.NaN) != InlineFloat(Float.NaN)) throw AssertionError()
if (InlineDouble(0.0) == InlineDouble(-0.0)) throw AssertionError()
if (InlineDouble(Double.NaN) != InlineDouble(Double.NaN)) throw AssertionError()
return "OK"
}
@@ -0,0 +1,14 @@
// !LANGUAGE: +InlineClasses
// IGNORE_BACKEND: JVM_IR
inline class Z(val data: Int) {
override fun equals(other: Any?): Boolean =
other is Z &&
data % 256 == other.data % 256
}
fun box(): String {
if (Z(0) != Z(256)) throw AssertionError()
return "OK"
}
@@ -0,0 +1,20 @@
// !LANGUAGE: +InlineClasses
// IGNORE_BACKEND: JVM_IR, JS_IR, JS
inline class Z(val value: Int)
fun eq(a: Z?, b: Z) = a == b
fun eqq(a: Z, b: Z?) = a == b
fun box(): String {
if (!eq(Z(1), Z(1))) throw AssertionError()
if (eq(Z(1), Z(2))) throw AssertionError()
if (eq(null, Z(0))) throw AssertionError()
if (!eqq(Z(1), Z(1))) throw AssertionError()
if (eqq(Z(1), Z(2))) throw AssertionError()
if (eqq(Z(0), null)) throw AssertionError()
return "OK"
}
@@ -11445,6 +11445,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassAsLastExpressionInInLambda.kt");
}
@TestMetadata("inlineClassEqualityShouldUseTotalOrderForFloatingPointData.kt")
public void testInlineClassEqualityShouldUseTotalOrderForFloatingPointData() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassEqualityShouldUseTotalOrderForFloatingPointData.kt");
}
@TestMetadata("inlineClassImplementsCollection.kt")
public void testInlineClassImplementsCollection() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassImplementsCollection.kt");
@@ -11455,6 +11460,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassValuesInsideStrings.kt");
}
@TestMetadata("inlineClassWithCustomEquals.kt")
public void testInlineClassWithCustomEquals() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassWithCustomEquals.kt");
}
@TestMetadata("inlineClassesCheckCast.kt")
public void testInlineClassesCheckCast() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassesCheckCast.kt");
@@ -11490,6 +11500,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/inlineClasses/noAssertionsOnInlineClassBasedOnNullableType.kt");
}
@TestMetadata("nullableEqeqNonNull.kt")
public void testNullableEqeqNonNull() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/nullableEqeqNonNull.kt");
}
@TestMetadata("passInlineClassAsVararg.kt")
public void testPassInlineClassAsVararg() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/passInlineClassAsVararg.kt");
@@ -11445,6 +11445,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassAsLastExpressionInInLambda.kt");
}
@TestMetadata("inlineClassEqualityShouldUseTotalOrderForFloatingPointData.kt")
public void testInlineClassEqualityShouldUseTotalOrderForFloatingPointData() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassEqualityShouldUseTotalOrderForFloatingPointData.kt");
}
@TestMetadata("inlineClassImplementsCollection.kt")
public void testInlineClassImplementsCollection() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassImplementsCollection.kt");
@@ -11455,6 +11460,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassValuesInsideStrings.kt");
}
@TestMetadata("inlineClassWithCustomEquals.kt")
public void testInlineClassWithCustomEquals() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassWithCustomEquals.kt");
}
@TestMetadata("inlineClassesCheckCast.kt")
public void testInlineClassesCheckCast() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassesCheckCast.kt");
@@ -11490,6 +11500,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/inlineClasses/noAssertionsOnInlineClassBasedOnNullableType.kt");
}
@TestMetadata("nullableEqeqNonNull.kt")
public void testNullableEqeqNonNull() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/nullableEqeqNonNull.kt");
}
@TestMetadata("passInlineClassAsVararg.kt")
public void testPassInlineClassAsVararg() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/passInlineClassAsVararg.kt");
@@ -11445,6 +11445,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassAsLastExpressionInInLambda.kt");
}
@TestMetadata("inlineClassEqualityShouldUseTotalOrderForFloatingPointData.kt")
public void testInlineClassEqualityShouldUseTotalOrderForFloatingPointData() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassEqualityShouldUseTotalOrderForFloatingPointData.kt");
}
@TestMetadata("inlineClassImplementsCollection.kt")
public void testInlineClassImplementsCollection() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassImplementsCollection.kt");
@@ -11455,6 +11460,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassValuesInsideStrings.kt");
}
@TestMetadata("inlineClassWithCustomEquals.kt")
public void testInlineClassWithCustomEquals() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassWithCustomEquals.kt");
}
@TestMetadata("inlineClassesCheckCast.kt")
public void testInlineClassesCheckCast() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassesCheckCast.kt");
@@ -11490,6 +11500,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/inlineClasses/noAssertionsOnInlineClassBasedOnNullableType.kt");
}
@TestMetadata("nullableEqeqNonNull.kt")
public void testNullableEqeqNonNull() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/nullableEqeqNonNull.kt");
}
@TestMetadata("passInlineClassAsVararg.kt")
public void testPassInlineClassAsVararg() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/passInlineClassAsVararg.kt");
@@ -10025,11 +10025,21 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassAsLastExpressionInInLambda.kt");
}
@TestMetadata("inlineClassEqualityShouldUseTotalOrderForFloatingPointData.kt")
public void testInlineClassEqualityShouldUseTotalOrderForFloatingPointData() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassEqualityShouldUseTotalOrderForFloatingPointData.kt");
}
@TestMetadata("inlineClassValuesInsideStrings.kt")
public void testInlineClassValuesInsideStrings() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassValuesInsideStrings.kt");
}
@TestMetadata("inlineClassWithCustomEquals.kt")
public void testInlineClassWithCustomEquals() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassWithCustomEquals.kt");
}
@TestMetadata("inlineClassesCheckCast.kt")
public void testInlineClassesCheckCast() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassesCheckCast.kt");
@@ -10065,6 +10075,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/inlineClasses/noAssertionsOnInlineClassBasedOnNullableType.kt");
}
@TestMetadata("nullableEqeqNonNull.kt")
public void testNullableEqeqNonNull() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/nullableEqeqNonNull.kt");
}
@TestMetadata("passInlineClassAsVararg.kt")
public void testPassInlineClassAsVararg() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/passInlineClassAsVararg.kt");
@@ -11075,11 +11075,21 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassAsLastExpressionInInLambda.kt");
}
@TestMetadata("inlineClassEqualityShouldUseTotalOrderForFloatingPointData.kt")
public void testInlineClassEqualityShouldUseTotalOrderForFloatingPointData() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassEqualityShouldUseTotalOrderForFloatingPointData.kt");
}
@TestMetadata("inlineClassValuesInsideStrings.kt")
public void testInlineClassValuesInsideStrings() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassValuesInsideStrings.kt");
}
@TestMetadata("inlineClassWithCustomEquals.kt")
public void testInlineClassWithCustomEquals() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassWithCustomEquals.kt");
}
@TestMetadata("inlineClassesCheckCast.kt")
public void testInlineClassesCheckCast() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassesCheckCast.kt");
@@ -11115,6 +11125,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/inlineClasses/noAssertionsOnInlineClassBasedOnNullableType.kt");
}
@TestMetadata("nullableEqeqNonNull.kt")
public void testNullableEqeqNonNull() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/nullableEqeqNonNull.kt");
}
@TestMetadata("passInlineClassAsVararg.kt")
public void testPassInlineClassAsVararg() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/passInlineClassAsVararg.kt");