From cc4f84ffaefa14cbbc95fefa80a1e2bc03ff5b60 Mon Sep 17 00:00:00 2001 From: Michael Nedzelsky Date: Sat, 6 Sep 2014 15:40:49 +0400 Subject: [PATCH] JS backend: fix KT-5738 JS backend: result of toByte() should be in the range of bytes #KT-5738 Fixed --- .../k2js/test/semantics/NumberTest.java | 8 ++ .../factories/NumberConversionFIF.java | 92 ++++++++++++------- .../PrimitiveBinaryOperationFIF.java | 2 +- .../k2js/translate/utils/JsAstUtils.java | 10 ++ js/js.translator/testData/kotlin_lib.js | 8 ++ .../number/cases/conversionsWithTruncation.kt | 27 ++++++ .../cases/conversionsWithoutTruncation.kt | 55 +++++++++++ 7 files changed, 168 insertions(+), 34 deletions(-) create mode 100644 js/js.translator/testData/number/cases/conversionsWithTruncation.kt create mode 100644 js/js.translator/testData/number/cases/conversionsWithoutTruncation.kt diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/NumberTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/NumberTest.java index 88a1327432a..3b707b628b8 100644 --- a/js/js.tests/test/org/jetbrains/k2js/test/semantics/NumberTest.java +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/NumberTest.java @@ -69,4 +69,12 @@ public final class NumberTest extends SingleFileTranslationTest { public void testNumberCompareTo() throws Exception { checkFooBoxIsOk(); } + + public void testConversionsWithoutTruncation() throws Exception { + checkFooBoxIsOk(); + } + + public void testConversionsWithTruncation() throws Exception { + checkFooBoxIsOk(); + } } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/NumberConversionFIF.java b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/NumberConversionFIF.java index 7b55dea6d48..43048f76560 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/NumberConversionFIF.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/NumberConversionFIF.java @@ -16,47 +16,32 @@ package org.jetbrains.k2js.translate.intrinsic.functions.factories; -import com.google.common.collect.Sets; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; import com.google.dart.compiler.backend.js.ast.JsExpression; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jet.lang.resolve.name.Name; +import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; import org.jetbrains.k2js.translate.context.TranslationContext; import org.jetbrains.k2js.translate.intrinsic.functions.basic.FunctionIntrinsic; -import org.jetbrains.k2js.translate.intrinsic.functions.patterns.NamePredicate; import org.jetbrains.k2js.translate.utils.JsAstUtils; import java.util.List; -import java.util.Set; -import static org.jetbrains.jet.lang.types.expressions.OperatorConventions.*; import static org.jetbrains.k2js.translate.intrinsic.functions.patterns.PatternBuilder.pattern; +//TODO: support longs and chars public final class NumberConversionFIF extends CompositeFIF { - @NotNull - private static final NamePredicate SUPPORTED_CONVERSIONS; - - static { - Set supportedConversions = Sets.newHashSet(NUMBER_CONVERSIONS); - //TODO: support longs and chars - supportedConversions.remove(CHAR); - supportedConversions.remove(LONG); - SUPPORTED_CONVERSIONS = new NamePredicate(supportedConversions); - } - - @NotNull - private static final NamePredicate FLOATING_POINT_CONVERSIONS = new NamePredicate(FLOAT, DOUBLE); - - @NotNull - private static final NamePredicate INTEGER_CONVERSIONS = new NamePredicate(INT, SHORT, BYTE); @NotNull private static final FunctionIntrinsic RETURN_RECEIVER = new FunctionIntrinsic() { @NotNull @Override - public JsExpression apply(@Nullable JsExpression receiver, + public JsExpression apply( + @Nullable JsExpression receiver, @NotNull List arguments, - @NotNull TranslationContext context) { + @NotNull TranslationContext context + ) { assert receiver != null; assert arguments.isEmpty(); return receiver; @@ -65,27 +50,68 @@ public final class NumberConversionFIF extends CompositeFIF { @NotNull public static final String INTEGER_NUMBER_TYPES = "Int|Byte|Short"; - //NOTE: treat Number as if it is floating point type + @NotNull - private static final String FLOATING_POINT_NUMBER_TYPES = "Float|Double|Number"; - @NotNull - private static final FunctionIntrinsic GET_INTEGER_PART = new FunctionIntrinsic() { + private static final FunctionIntrinsic TO_INT32 = new FunctionIntrinsic() { @NotNull @Override - public JsExpression apply(@Nullable JsExpression receiver, + public JsExpression apply( + @Nullable JsExpression receiver, @NotNull List arguments, - @NotNull TranslationContext context) { + @NotNull TranslationContext context + ) { assert receiver != null; assert arguments.isEmpty(); return JsAstUtils.toInt32(receiver, context); } }; + + @NotNull + private static final FunctionIntrinsic TO_SHORT = new FunctionIntrinsic() { + @NotNull + @Override + public JsExpression apply( + @Nullable JsExpression receiver, + @NotNull List arguments, + @NotNull TranslationContext context + ) { + assert receiver != null; + assert arguments.isEmpty(); + return JsAstUtils.toShort(receiver); + } + }; + + @NotNull + private static final FunctionIntrinsic TO_BYTE = new FunctionIntrinsic() { + @NotNull + @Override + public JsExpression apply( + @Nullable JsExpression receiver, + @NotNull List arguments, + @NotNull TranslationContext context + ) { + assert receiver != null; + assert arguments.isEmpty(); + return JsAstUtils.toByte(receiver); + } + }; + + @NotNull + private static final Predicate returnReceiverPredicate = Predicates.or( + pattern("Int.toInt|toFloat|toDouble"), + pattern("Short.toShort|toInt|toFloat|toDouble"), + pattern("Byte.toByte|toShort|toInt|toFloat|toDouble"), + pattern("Float|Double|Number.toFloat|toDouble") + ); + @NotNull public static final FunctionIntrinsicFactory INSTANCE = new NumberConversionFIF(); + //NOTE: treat Number as if it is floating point type private NumberConversionFIF() { - add(pattern(INTEGER_NUMBER_TYPES, SUPPORTED_CONVERSIONS), RETURN_RECEIVER); - add(pattern(FLOATING_POINT_NUMBER_TYPES, INTEGER_CONVERSIONS), GET_INTEGER_PART); - add(pattern(FLOATING_POINT_NUMBER_TYPES, FLOATING_POINT_CONVERSIONS), RETURN_RECEIVER); + add(returnReceiverPredicate, RETURN_RECEIVER); + add(pattern("Float|Double|Number.toInt"), TO_INT32); + add(pattern("Int|Float|Double|Number.toShort"), TO_SHORT); + add(pattern("Short|Int|Float|Double|Number.toByte"), TO_BYTE); } -} +} \ No newline at end of file diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/PrimitiveBinaryOperationFIF.java b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/PrimitiveBinaryOperationFIF.java index 0785fb21a15..9325dc6bcfd 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/PrimitiveBinaryOperationFIF.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/PrimitiveBinaryOperationFIF.java @@ -87,7 +87,7 @@ public enum PrimitiveBinaryOperationFIF implements FunctionIntrinsicFactory { ) { assert receiver != null; assert arguments.size() == 1; - return JsAstUtils.compareTo(receiver, arguments.get(0), context); + return JsAstUtils.compareTo(receiver, arguments.get(0)); } }; diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsAstUtils.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsAstUtils.java index 3c6e84c126d..e46ea9a8e7f 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsAstUtils.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsAstUtils.java @@ -123,6 +123,16 @@ public final class JsAstUtils { return new JsBinaryOperation(JsBinaryOperator.BIT_OR, expression, context.program().getNumberLiteral(0)); } + @NotNull + public static JsExpression toShort(@NotNull JsExpression expression) { + return new JsInvocation(new JsNameRef(OperatorConventions.SHORT.getIdentifier(), Namer.KOTLIN_OBJECT_REF), expression); + } + + @NotNull + public static JsExpression toByte(@NotNull JsExpression expression) { + return new JsInvocation(new JsNameRef(OperatorConventions.BYTE.getIdentifier(), Namer.KOTLIN_OBJECT_REF), expression); + } + @NotNull public static JsExpression compareTo(@NotNull JsExpression left, @NotNull JsExpression right) { return new JsInvocation(new JsNameRef(OperatorConventions.COMPARE_TO.getIdentifier(), Namer.KOTLIN_OBJECT_REF), left, right); diff --git a/js/js.translator/testData/kotlin_lib.js b/js/js.translator/testData/kotlin_lib.js index 6b4ddc95761..db18e3f8478 100644 --- a/js/js.translator/testData/kotlin_lib.js +++ b/js/js.translator/testData/kotlin_lib.js @@ -89,6 +89,14 @@ return a < b ? -1 : a > b ? 1 : 0; }; + Kotlin.toShort = function(a) { + return (a & 0xFFFF) << 16 >> 16; + }; + + Kotlin.toByte = function(a) { + return (a & 0xFF) << 24 >> 24; + }; + Kotlin.intUpto = function (from, to) { return new Kotlin.NumberRange(from, to); }; diff --git a/js/js.translator/testData/number/cases/conversionsWithTruncation.kt b/js/js.translator/testData/number/cases/conversionsWithTruncation.kt new file mode 100644 index 00000000000..1fae0dbbf68 --- /dev/null +++ b/js/js.translator/testData/number/cases/conversionsWithTruncation.kt @@ -0,0 +1,27 @@ +package foo + +fun box(): String { + + assertEquals(65, 321.0.toByte()) + assertEquals(-56, 200.0.toByte()) + + assertEquals(65, 321.0f.toByte()) + assertEquals(-56, 200.0f.toByte()) + + assertEquals(65, 321.toByte()) + assertEquals(-56, 200.toByte()) + + assertEquals(65, (321: Short).toByte()) + assertEquals(-56, (200: Short).toByte()) + + assertEquals(-1, 65535.0.toShort()) + assertEquals(-1, 65535.0f.toShort()) + assertEquals(-1, 65535.toShort()) + + assertEquals(65535, 65535.2.toInt()) + assertEquals(23, 23.6f.toInt()) + assertEquals(-12, -12.4.toShort()) + assertEquals(-12, -12.4.toByte()) + + return "OK" +} diff --git a/js/js.translator/testData/number/cases/conversionsWithoutTruncation.kt b/js/js.translator/testData/number/cases/conversionsWithoutTruncation.kt new file mode 100644 index 00000000000..bdf7cbeafc3 --- /dev/null +++ b/js/js.translator/testData/number/cases/conversionsWithoutTruncation.kt @@ -0,0 +1,55 @@ +package foo + +fun testForNumber(numberX: Number) { + assertEquals(true, 65.0 == numberX.toDouble()) + assertEquals(true, 65.0f == numberX.toFloat()) + assertEquals(true, 65 == numberX.toInt()) + assertEquals(true, (65: Short) == numberX.toShort()) + assertEquals(true, (65: Byte) == numberX.toByte()) +} + +fun box(): String { + + testForNumber(65.0) + testForNumber(65.0f) + testForNumber(65) + testForNumber(65: Short) + testForNumber(65: Byte) + + var doubleX: Double = 65.0 + assertEquals(true, 65.0 == doubleX.toDouble()) + assertEquals(true, 65.0f == doubleX.toFloat()) + assertEquals(true, 65 == doubleX.toInt()) + assertEquals(true, (65: Short) == doubleX.toShort()) + assertEquals(true, (65: Byte) == doubleX.toByte()) + + var floatX: Float = 65.0f + assertEquals(true, 65.0 == floatX.toDouble()) + assertEquals(true, 65.0f == floatX.toFloat()) + assertEquals(true, 65 == floatX.toInt()) + assertEquals(true, (65: Short) == floatX.toShort()) + assertEquals(true, (65: Byte) == floatX.toByte()) + + val intX: Int = 65 + assertEquals(true, 65.0 == intX.toDouble()) + assertEquals(true, 65.0f == intX.toFloat()) + assertEquals(true, 65 == intX.toInt()) + assertEquals(true, (65: Short) == intX.toShort()) + assertEquals(true, (65: Byte) == intX.toByte()) + + val shortX: Short = 65: Short + assertEquals(true, 65.0 == shortX.toDouble()) + assertEquals(true, 65.0f == shortX.toFloat()) + assertEquals(true, 65 == shortX.toInt()) + assertEquals(true, (65: Short) == shortX.toShort()) + assertEquals(true, (65: Byte) == shortX.toByte()) + + val byteX: Byte = 65: Byte + assertEquals(true, 65.0 == byteX.toDouble()) + assertEquals(true, 65.0f == byteX.toFloat()) + assertEquals(true, 65 == byteX.toInt()) + assertEquals(true, (65: Short) == byteX.toShort()) + assertEquals(true, (65: Byte) == byteX.toByte()) + + return "OK" +} \ No newline at end of file