Generate compareTo() properly
Take into account: 1) resolved call to compareTo (instead of always calling Comparable's method) 2) types of both caller and callee, when primitive, to avoid wrong casting (instead of always using caller's type) #KT-3078 Fixed
This commit is contained in:
@@ -311,15 +311,6 @@ public class AsmUtil {
|
||||
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 genNullSafeEquals(
|
||||
InstructionAdapter v,
|
||||
IElementType opToken,
|
||||
@@ -394,7 +385,7 @@ public class AsmUtil {
|
||||
boolean rightNullable
|
||||
) {
|
||||
if ((isNumberPrimitive(leftType) || leftType.getSort() == Type.BOOLEAN) && leftType == rightType) {
|
||||
return compareExpressionsOnStack(v, opToken, leftType);
|
||||
return StackValue.cmp(opToken, leftType);
|
||||
}
|
||||
else {
|
||||
if (opToken == JetTokens.EQEQEQ || opToken == JetTokens.EXCLEQEQEQ) {
|
||||
|
||||
@@ -2311,7 +2311,7 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
|
||||
}
|
||||
else if (opToken == JetTokens.LT || opToken == JetTokens.LTEQ ||
|
||||
opToken == JetTokens.GT || opToken == JetTokens.GTEQ) {
|
||||
return generateCompareOp(expression.getLeft(), expression.getRight(), opToken, expressionType(expression.getLeft()));
|
||||
return generateCompareTo(expression);
|
||||
}
|
||||
else if (opToken == JetTokens.ELVIS) {
|
||||
return generateElvis(expression);
|
||||
@@ -2519,10 +2519,25 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
|
||||
return StackValue.onStack(exprType);
|
||||
}
|
||||
|
||||
private StackValue generateCompareOp(JetExpression left, JetExpression right, IElementType opToken, Type operandType) {
|
||||
gen(left, operandType);
|
||||
gen(right, operandType);
|
||||
return compareExpressionsOnStack(v, opToken, operandType);
|
||||
private StackValue generateCompareTo(JetBinaryExpression expression) {
|
||||
DeclarationDescriptor target = bindingContext.get(BindingContext.REFERENCE_TARGET, expression.getOperationReference());
|
||||
assert target instanceof FunctionDescriptor : "compareTo target should be a function: " + target;
|
||||
FunctionDescriptor descriptor = (FunctionDescriptor) target;
|
||||
|
||||
StackValue receiver = gen(expression.getLeft());
|
||||
Callable callable = resolveToCallable(descriptor, false);
|
||||
|
||||
StackValue result;
|
||||
if (callable instanceof IntrinsicMethod) {
|
||||
result = ((IntrinsicMethod) callable).generate(
|
||||
this, v, Type.INT_TYPE, expression, Collections.singletonList(expression.getRight()), receiver, state);
|
||||
} else {
|
||||
result = invokeOperation(expression, descriptor, (CallableMethod) callable);
|
||||
}
|
||||
|
||||
result.put(Type.INT_TYPE, v);
|
||||
v.iconst(0);
|
||||
return StackValue.cmp(expression.getOperationToken(), Type.INT_TYPE);
|
||||
}
|
||||
|
||||
private StackValue generateAssignmentExpression(JetBinaryExpression expression) {
|
||||
|
||||
@@ -28,6 +28,8 @@ import org.jetbrains.jet.lang.psi.JetExpression;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.jetbrains.jet.codegen.AsmUtil.isPrimitive;
|
||||
|
||||
/**
|
||||
* @author alex.tkachman
|
||||
*/
|
||||
@@ -43,21 +45,25 @@ public class CompareTo implements IntrinsicMethod {
|
||||
@NotNull GenerationState state
|
||||
) {
|
||||
assert arguments != null;
|
||||
receiver.put(receiver.type, v);
|
||||
codegen.gen(arguments.get(0), receiver.type);
|
||||
if (receiver.type == Type.BYTE_TYPE || receiver.type == Type.SHORT_TYPE || receiver.type == Type.CHAR_TYPE) {
|
||||
JetExpression argument = arguments.get(0);
|
||||
Type type = comparisonOperandType(receiver.type, codegen.expressionType(argument));
|
||||
|
||||
receiver.put(type, v);
|
||||
codegen.gen(argument, type);
|
||||
|
||||
if (type == Type.BYTE_TYPE || type == Type.SHORT_TYPE || type == Type.CHAR_TYPE) {
|
||||
v.sub(Type.INT_TYPE);
|
||||
}
|
||||
else if (receiver.type == Type.INT_TYPE) {
|
||||
else if (type == Type.INT_TYPE) {
|
||||
v.invokestatic("jet/runtime/Intrinsics", "compare", "(II)I");
|
||||
}
|
||||
else if (receiver.type == Type.LONG_TYPE) {
|
||||
else if (type == Type.LONG_TYPE) {
|
||||
v.invokestatic("jet/runtime/Intrinsics", "compare", "(JJ)I");
|
||||
}
|
||||
else if (receiver.type == Type.FLOAT_TYPE) {
|
||||
else if (type == Type.FLOAT_TYPE) {
|
||||
v.invokestatic("java/lang/Float", "compare", "(FF)I");
|
||||
}
|
||||
else if (receiver.type == Type.DOUBLE_TYPE) {
|
||||
else if (type == Type.DOUBLE_TYPE) {
|
||||
v.invokestatic("java/lang/Double", "compare", "(DD)I");
|
||||
}
|
||||
else {
|
||||
@@ -65,4 +71,11 @@ public class CompareTo implements IntrinsicMethod {
|
||||
}
|
||||
return StackValue.onStack(Type.INT_TYPE);
|
||||
}
|
||||
|
||||
private static Type comparisonOperandType(Type left, Type right) {
|
||||
if (left == Type.DOUBLE_TYPE || right == Type.DOUBLE_TYPE) return Type.DOUBLE_TYPE;
|
||||
if (left == Type.FLOAT_TYPE || right == Type.FLOAT_TYPE) return Type.FLOAT_TYPE;
|
||||
if (left == Type.LONG_TYPE || right == Type.LONG_TYPE) return Type.LONG_TYPE;
|
||||
return Type.INT_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
trait A : Comparable<A>
|
||||
|
||||
class B(val x: Int) : A {
|
||||
override fun compareTo(other: A) = x.compareTo((other as B).x)
|
||||
}
|
||||
|
||||
fun checkLess(x: A, y: A) = when {
|
||||
x >= y -> "Fail $x >= $y"
|
||||
!(x < y) -> "Fail !($x < $y)"
|
||||
!(x <= y) -> "Fail !($x <= $y)"
|
||||
x > y -> "Fail $x > $y"
|
||||
x.compareTo(y) >= 0 -> "Fail $x.compareTo($y) >= 0"
|
||||
else -> "OK"
|
||||
}
|
||||
|
||||
fun box() = checkLess(B(0), B(1))
|
||||
@@ -0,0 +1,10 @@
|
||||
fun checkLess(x: Double, y: Int) = when {
|
||||
x >= y -> "Fail $x >= $y"
|
||||
!(x < y) -> "Fail !($x < $y)"
|
||||
!(x <= y) -> "Fail !($x <= $y)"
|
||||
x > y -> "Fail $x > $y"
|
||||
x.compareTo(y) >= 0 -> "Fail $x.compareTo($y) >= 0"
|
||||
else -> "OK"
|
||||
}
|
||||
|
||||
fun box() = checkLess(0.5, 1)
|
||||
@@ -0,0 +1,10 @@
|
||||
fun checkLess(x: Double, y: Long) = when {
|
||||
x >= y -> "Fail $x >= $y"
|
||||
!(x < y) -> "Fail !($x < $y)"
|
||||
!(x <= y) -> "Fail !($x <= $y)"
|
||||
x > y -> "Fail $x > $y"
|
||||
x.compareTo(y) >= 0 -> "Fail $x.compareTo($y) >= 0"
|
||||
else -> "OK"
|
||||
}
|
||||
|
||||
fun box() = checkLess(0.5, 1.toLong())
|
||||
@@ -0,0 +1,16 @@
|
||||
fun checkLess(x: Array<Int>, y: Array<Int>) = when {
|
||||
x >= y -> "Fail $x >= $y"
|
||||
!(x < y) -> "Fail !($x < $y)"
|
||||
!(x <= y) -> "Fail !($x <= $y)"
|
||||
x > y -> "Fail $x > $y"
|
||||
x.compareTo(y) >= 0 -> "Fail $x.compareTo($y) >= 0"
|
||||
else -> "OK"
|
||||
}
|
||||
|
||||
fun Array<Int>.compareTo(other: Array<Int>) = size - other.size
|
||||
|
||||
fun box(): String {
|
||||
val a = Array<Int>(0, {0})
|
||||
val b = Array<Int>(1, {0})
|
||||
return checkLess(a, b)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
class A(val x: Int)
|
||||
|
||||
fun A.compareTo(other: A) = x.compareTo(other.x)
|
||||
|
||||
fun checkLess(x: A, y: A) = when {
|
||||
x >= y -> "Fail $x >= $y"
|
||||
!(x < y) -> "Fail !($x < $y)"
|
||||
!(x <= y) -> "Fail !($x <= $y)"
|
||||
x > y -> "Fail $x > $y"
|
||||
x.compareTo(y) >= 0 -> "Fail $x.compareTo($y) >= 0"
|
||||
else -> "OK"
|
||||
}
|
||||
|
||||
fun box() = checkLess(A(0), A(1))
|
||||
@@ -0,0 +1,10 @@
|
||||
fun checkLess(x: Int, y: Double) = when {
|
||||
x >= y -> "Fail $x >= $y"
|
||||
!(x < y) -> "Fail !($x < $y)"
|
||||
!(x <= y) -> "Fail !($x <= $y)"
|
||||
x > y -> "Fail $x > $y"
|
||||
x.compareTo(y) >= 0 -> "Fail $x.compareTo($y) >= 0"
|
||||
else -> "OK"
|
||||
}
|
||||
|
||||
fun box() = checkLess(0, 0.5)
|
||||
@@ -0,0 +1,10 @@
|
||||
fun checkLess(x: Int, y: Long) = when {
|
||||
x >= y -> "Fail $x >= $y"
|
||||
!(x < y) -> "Fail !($x < $y)"
|
||||
!(x <= y) -> "Fail !($x <= $y)"
|
||||
x > y -> "Fail $x > $y"
|
||||
x.compareTo(y) >= 0 -> "Fail $x.compareTo($y) >= 0"
|
||||
else -> "OK"
|
||||
}
|
||||
|
||||
fun box() = checkLess(0, 123456789123.toLong())
|
||||
@@ -0,0 +1,10 @@
|
||||
fun checkLess(x: Long, y: Double) = when {
|
||||
x >= y -> "Fail $x >= $y"
|
||||
!(x < y) -> "Fail !($x < $y)"
|
||||
!(x <= y) -> "Fail !($x <= $y)"
|
||||
x > y -> "Fail $x > $y"
|
||||
x.compareTo(y) >= 0 -> "Fail $x.compareTo($y) >= 0"
|
||||
else -> "OK"
|
||||
}
|
||||
|
||||
fun box() = checkLess(0.toLong(), 0.5)
|
||||
@@ -0,0 +1,10 @@
|
||||
fun checkLess(x: Long, y: Int) = when {
|
||||
x >= y -> "Fail $x >= $y"
|
||||
!(x < y) -> "Fail !($x < $y)"
|
||||
!(x <= y) -> "Fail !($x <= $y)"
|
||||
x > y -> "Fail $x > $y"
|
||||
x.compareTo(y) >= 0 -> "Fail $x.compareTo($y) >= 0"
|
||||
else -> "OK"
|
||||
}
|
||||
|
||||
fun box() = checkLess(-123456789123.toLong(), 0)
|
||||
@@ -0,0 +1,7 @@
|
||||
fun box(): String {
|
||||
if (1 >= 1.9) return "Fail #1"
|
||||
if (1.compareTo(1.1) >= 0) return "Fail #2"
|
||||
if (1.9 <= 1) return "Fail #3"
|
||||
if (1.1.compareTo(1) <= 0) return "Fail #4"
|
||||
return "OK"
|
||||
}
|
||||
@@ -460,4 +460,8 @@ public class PrimitiveTypesTest extends CodegenTestCase {
|
||||
public void testUnboxComparable() {
|
||||
blackBoxFile("primitiveTypes/unboxComparable.kt");
|
||||
}
|
||||
|
||||
public void testKt3078() {
|
||||
blackBoxFile("regressions/kt3078.kt");
|
||||
}
|
||||
}
|
||||
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2010-2012 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetbrains.jet.codegen.generated;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestSuite;
|
||||
|
||||
import java.io.File;
|
||||
import org.jetbrains.jet.JetTestUtils;
|
||||
import org.jetbrains.jet.test.InnerTestClasses;
|
||||
import org.jetbrains.jet.test.TestMetadata;
|
||||
|
||||
import org.jetbrains.jet.codegen.generated.AbstractCodegenTest;
|
||||
|
||||
/** This class is generated by {@link org.jetbrains.jet.generators.tests.GenerateTests}. DO NOT MODIFY MANUALLY */
|
||||
@TestMetadata("compiler/testData/codegen/operatorConventions")
|
||||
@InnerTestClasses({OperatorConventionsTestGenerated.CompareTo.class})
|
||||
public class OperatorConventionsTestGenerated extends AbstractCodegenTest {
|
||||
public void testAllFilesPresentInOperatorConventions() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("compiler/testData/codegen/operatorConventions"), "kt", true);
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/operatorConventions/compareTo")
|
||||
public static class CompareTo extends AbstractCodegenTest {
|
||||
public void testAllFilesPresentInCompareTo() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("compiler/testData/codegen/operatorConventions/compareTo"), "kt", true);
|
||||
}
|
||||
|
||||
@TestMetadata("comparable.kt")
|
||||
public void testComparable() throws Exception {
|
||||
doTest("compiler/testData/codegen/operatorConventions/compareTo/comparable.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("doubleInt.kt")
|
||||
public void testDoubleInt() throws Exception {
|
||||
doTest("compiler/testData/codegen/operatorConventions/compareTo/doubleInt.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("doubleLong.kt")
|
||||
public void testDoubleLong() throws Exception {
|
||||
doTest("compiler/testData/codegen/operatorConventions/compareTo/doubleLong.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("extensionArray.kt")
|
||||
public void testExtensionArray() throws Exception {
|
||||
doTest("compiler/testData/codegen/operatorConventions/compareTo/extensionArray.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("extensionObject.kt")
|
||||
public void testExtensionObject() throws Exception {
|
||||
doTest("compiler/testData/codegen/operatorConventions/compareTo/extensionObject.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("intDouble.kt")
|
||||
public void testIntDouble() throws Exception {
|
||||
doTest("compiler/testData/codegen/operatorConventions/compareTo/intDouble.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("intLong.kt")
|
||||
public void testIntLong() throws Exception {
|
||||
doTest("compiler/testData/codegen/operatorConventions/compareTo/intLong.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("longDouble.kt")
|
||||
public void testLongDouble() throws Exception {
|
||||
doTest("compiler/testData/codegen/operatorConventions/compareTo/longDouble.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("longInt.kt")
|
||||
public void testLongInt() throws Exception {
|
||||
doTest("compiler/testData/codegen/operatorConventions/compareTo/longInt.kt");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static Test suite() {
|
||||
TestSuite suite = new TestSuite("OperatorConventionsTestGenerated");
|
||||
suite.addTestSuite(OperatorConventionsTestGenerated.class);
|
||||
suite.addTestSuite(CompareTo.class);
|
||||
return suite;
|
||||
}
|
||||
}
|
||||
@@ -114,6 +114,13 @@ public class GenerateTests {
|
||||
testModel("compiler/testData/codegen/instructions")
|
||||
);
|
||||
|
||||
generateTest(
|
||||
"compiler/tests/",
|
||||
"OperatorConventionsTestGenerated",
|
||||
AbstractCodegenTest.class,
|
||||
testModel("compiler/testData/codegen/operatorConventions")
|
||||
);
|
||||
|
||||
generateTest(
|
||||
"compiler/tests/",
|
||||
"ExtensionPropertiesTestGenerated",
|
||||
|
||||
Reference in New Issue
Block a user