From dca6e315190615315ee15dda789107c0e5b5d60b Mon Sep 17 00:00:00 2001 From: Michael Nedzelsky Date: Tue, 23 Sep 2014 19:00:16 +0400 Subject: [PATCH] JS backend: add support for Long type --- .../backend/js/ast/JsNumberLiteral.java | 2 + .../k2js/translate/context/Namer.java | 11 ++ .../translate/context/StandardClasses.java | 6 + .../expression/ExpressionVisitor.java | 3 + .../expression/PatternTranslator.java | 5 +- .../functions/FunctionIntrinsics.java | 1 + .../functions/factories/ArrayFIF.java | 9 +- .../functions/factories/LongOperationFIF.kt | 100 ++++++++++++++++ .../factories/NumberConversionFIF.kt | 57 ++++++---- .../PrimitiveBinaryOperationFIF.java | 93 +++++++-------- .../factories/PrimitiveUnaryOperationFIF.java | 4 +- .../functions/factories/TopLevelFIF.java | 14 +-- .../functions/patterns/NamePredicate.java | 26 ++++- .../functions/patterns/PatternBuilder.java | 85 ++++++++++++-- .../intrinsic/operation/CompareToBOIF.kt | 5 +- .../intrinsic/operation/EqualsBOIF.kt | 16 ++- .../intrinsic/operation/LongCompareToBOIF.kt | 83 ++++++++++++++ .../operation/binaryOperationIntrinsics.kt | 2 +- .../k2js/translate/utils/JsAstUtils.java | 99 ++++++++++++++-- .../translate/utils/TranslationUtils.java | 13 +++ .../jetbrains/k2js/translate/utils/utils.kt | 19 ++++ js/js.translator/testData/kotlin_lib.js | 107 ++++++++++++++++-- js/js.translator/testData/long.js | 11 ++ .../cases/arrayFactoryMethods.kt | 6 +- 24 files changed, 643 insertions(+), 134 deletions(-) create mode 100644 js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/LongOperationFIF.kt create mode 100644 js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/operation/LongCompareToBOIF.kt create mode 100644 js/js.translator/src/org/jetbrains/k2js/translate/utils/utils.kt diff --git a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsNumberLiteral.java b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsNumberLiteral.java index 6dc3d156754..0021adcd33a 100644 --- a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsNumberLiteral.java +++ b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsNumberLiteral.java @@ -5,6 +5,8 @@ package com.google.dart.compiler.backend.js.ast; public abstract class JsNumberLiteral extends JsLiteral.JsValueLiteral { + public static final JsIntLiteral ZERO = new JsIntLiteral(0); + public static final class JsDoubleLiteral extends JsNumberLiteral { public final double value; diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/context/Namer.java b/js/js.translator/src/org/jetbrains/k2js/translate/context/Namer.java index 550ab287388..05c1cbfb614 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/context/Namer.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/context/Namer.java @@ -21,9 +21,11 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.lang.descriptors.ClassDescriptor; import org.jetbrains.jet.lang.resolve.DescriptorUtils; import org.jetbrains.jet.lang.resolve.name.FqName; +import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; import org.jetbrains.jet.plugin.JetLanguage; import static com.google.dart.compiler.backend.js.ast.AstPackage.JsObjectScope; +import static org.jetbrains.k2js.translate.utils.TranslationUtils.getStableMangledNameForDescriptor; /** * Encapuslates different types of constants and naming conventions. @@ -33,6 +35,15 @@ public final class Namer { public static final String KOTLIN_LOWER_NAME = KOTLIN_NAME.toLowerCase(); public static final JsNameRef KOTLIN_OBJECT_REF = new JsNameRef(KOTLIN_NAME); + public static final String EQUALS_METHOD_NAME = getStableMangledNameForDescriptor(KotlinBuiltIns.getInstance().getAny(), "equals"); + public static final String COMPARE_TO_METHOD_NAME = getStableMangledNameForDescriptor(KotlinBuiltIns.getInstance().getComparable(), "compareTo"); + public static final String NUMBER_RANGE = "NumberRange"; + public static final String LONG_FROM_NUMBER = "fromNumber"; + public static final String LONG_TO_NUMBER = "toNumber"; + public static final String LONG_FROM_INT = "fromInt"; + public static final String PRIMITIVE_COMPARE_TO = "primitiveCompareTo"; + public static final String IS_NUMBER = "isNumber"; + public static final String CALLEE_NAME = "$fun"; private static final String CALL_FUNCTION = "call"; diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/context/StandardClasses.java b/js/js.translator/src/org/jetbrains/k2js/translate/context/StandardClasses.java index 5a63e205f3d..277960da8af 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/context/StandardClasses.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/context/StandardClasses.java @@ -98,9 +98,15 @@ public final class StandardClasses { standardClasses.declare().forFQ("kotlin.IntRange").kotlinClass("NumberRange") .methods("iterator", "contains").properties("start", "end", "increment"); + standardClasses.declare().forFQ("kotlin.LongRange").kotlinClass("LongRange") + .methods("iterator", "contains").properties("start", "end", "increment"); + standardClasses.declare().forFQ("kotlin.IntProgression").kotlinClass("NumberProgression") .methods("iterator", "contains").properties("start", "end", "increment"); + standardClasses.declare().forFQ("kotlin.LongProgression").kotlinClass("LongProgression") + .methods("iterator", "contains").properties("start", "end", "increment"); + standardClasses.declare().forFQ("kotlin.Enum").kotlinClass("Enum"); standardClasses.declare().forFQ("kotlin.Comparable").kotlinClass("Comparable"); diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/expression/ExpressionVisitor.java b/js/js.translator/src/org/jetbrains/k2js/translate/expression/ExpressionVisitor.java index d8ed0995149..5e6432cf84a 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/expression/ExpressionVisitor.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/expression/ExpressionVisitor.java @@ -77,6 +77,9 @@ public final class ExpressionVisitor extends TranslatorVisitor { if (value instanceof Integer || value instanceof Short || value instanceof Byte) { return context.program().getNumberLiteral(((Number) value).intValue()); } + else if (value instanceof Long) { + return JsAstUtils.newLong((Long) value, context); + } else if (value instanceof Number) { return context.program().getNumberLiteral(((Number) value).doubleValue()); } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/expression/PatternTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/expression/PatternTranslator.java index bc86c1fc8b5..9f38baebbad 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/expression/PatternTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/expression/PatternTranslator.java @@ -93,10 +93,13 @@ public final class PatternTranslator extends AbstractTranslator { if (NamePredicate.STRING.apply(typeName)) { jsSTypeName = "string"; } + else if (NamePredicate.LONG.apply(typeName)) { + return JsAstUtils.isLong(expressionToMatch); + } else if (NamePredicate.NUMBER.apply(typeName)) { return JsAstUtils.isNumber(expressionToMatch); } - else if (NamePredicate.PRIMITIVE_NUMBERS.apply(typeName)) { + else if (NamePredicate.PRIMITIVE_NUMBERS_MAPPED_TO_PRIMITIVE_JS.apply(typeName)) { jsSTypeName = "number"; } else { diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/FunctionIntrinsics.java b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/FunctionIntrinsics.java index 63a929b06e5..a0705a28c69 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/FunctionIntrinsics.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/FunctionIntrinsics.java @@ -40,6 +40,7 @@ public final class FunctionIntrinsics { } private void registerFactories() { + register(LongOperationFIF.INSTANCE$); register(PrimitiveUnaryOperationFIF.INSTANCE); register(PrimitiveBinaryOperationFIF.INSTANCE); register(StringOperationFIF.INSTANCE); diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/ArrayFIF.java b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/ArrayFIF.java index b7fd5c002f6..9ca26036c56 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/ArrayFIF.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/ArrayFIF.java @@ -46,6 +46,9 @@ public final class ArrayFIF extends CompositeFIF { @NotNull private static final NamePredicate BOOLEAN_ARRAY; + @NotNull + private static final NamePredicate LONG_ARRAY; + @NotNull private static final NamePredicate ARRAY; @@ -60,7 +63,7 @@ public final class ArrayFIF extends CompositeFIF { List arrayFactoryMethodNames = Lists.newArrayList(Name.identifier("array")); for (PrimitiveType type : PrimitiveType.values()) { Name arrayTypeName = type.getArrayTypeName(); - if (type != PrimitiveType.CHAR && type != PrimitiveType.BOOLEAN) { + if (type != PrimitiveType.CHAR && type != PrimitiveType.BOOLEAN && type != PrimitiveType.LONG) { arrayTypeNames.add(arrayTypeName); } arrayFactoryMethodNames.add(Name.identifier(decapitalize(arrayTypeName.asString()))); @@ -69,14 +72,17 @@ public final class ArrayFIF extends CompositeFIF { Name arrayName = Name.identifier("Array"); Name booleanArrayName = PrimitiveType.BOOLEAN.getArrayTypeName(); Name charArrayName = PrimitiveType.CHAR.getArrayTypeName(); + Name longArrayName = PrimitiveType.LONG.getArrayTypeName(); NUMBER_ARRAY = new NamePredicate(arrayTypeNames); CHAR_ARRAY = new NamePredicate(charArrayName); BOOLEAN_ARRAY = new NamePredicate(booleanArrayName); + LONG_ARRAY = new NamePredicate(longArrayName); ARRAY = new NamePredicate(arrayName); arrayTypeNames.add(charArrayName); arrayTypeNames.add(booleanArrayName); + arrayTypeNames.add(longArrayName); arrayTypeNames.add(arrayName); ARRAYS = new NamePredicate(arrayTypeNames); ARRAY_FACTORY_METHODS = pattern(Namer.KOTLIN_LOWER_NAME, new NamePredicate(arrayFactoryMethodNames)); @@ -138,6 +144,7 @@ public final class ArrayFIF extends CompositeFIF { add(pattern(NUMBER_ARRAY, ""),new KotlinFunctionIntrinsic("numberArrayOfSize")); add(pattern(CHAR_ARRAY, ""), new KotlinFunctionIntrinsic("charArrayOfSize")); add(pattern(BOOLEAN_ARRAY, ""), new KotlinFunctionIntrinsic("booleanArrayOfSize")); + add(pattern(LONG_ARRAY, ""), new KotlinFunctionIntrinsic("longArrayOfSize")); add(ARRAY_FACTORY_METHODS, ARRAY_INTRINSIC); } } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/LongOperationFIF.kt b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/LongOperationFIF.kt new file mode 100644 index 00000000000..c7b0cbb5938 --- /dev/null +++ b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/LongOperationFIF.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2010-2014 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.k2js.translate.intrinsic.functions.factories + +import com.google.dart.compiler.backend.js.ast.* +import org.jetbrains.jet.lang.descriptors.FunctionDescriptor +import org.jetbrains.k2js.translate.context.Namer +import org.jetbrains.k2js.translate.context.TranslationContext +import org.jetbrains.k2js.translate.intrinsic.functions.basic.FunctionIntrinsic +import org.jetbrains.k2js.translate.intrinsic.functions.patterns.PatternBuilder.pattern +import org.jetbrains.k2js.translate.utils.ID +import org.jetbrains.k2js.translate.utils.JsAstUtils.* + +// TODO Move to FunctionCallCases +public object LongOperationFIF : FunctionIntrinsicFactory { + + val LONG_EQUALS_ANY = pattern("Long.equals") + val LONG_BINARY_OPERATION_LONG = pattern("Long.compareTo|rangeTo|plus|minus|times|div|mod|and|or|xor(Long)") + val LONG_BIT_SHIFTS = pattern("Long.shl|shr|ushr(Int)") + val LONG_BINARY_OPERATION_INTEGER = pattern("Long.compareTo|rangeTo|plus|minus|times|div|mod(Int|Short|Byte)") + val LONG_BINARY_OPERATION_FLOATING_POINT = pattern("Long.compareTo|rangeTo|plus|minus|times|div|mod(Double|Float)") + val INTEGER_BINARY_OPERATION_LONG = pattern("Int|Short|Byte.compareTo|rangeTo|plus|minus|times|div|mod(Long)") + val FLOATING_POINT_BINARY_OPERATION_LONG = pattern("Double|Float.compareTo|rangeTo|plus|minus|times|div|mod(Long)") + + private val longBinaryIntrinsics = + ( + listOf( + "equals" to Namer.EQUALS_METHOD_NAME, + "compareTo" to Namer.COMPARE_TO_METHOD_NAME, + "rangeTo" to "rangeTo", + "plus" to "add", + "minus" to "subtract", + "times" to "multiply", + "div" to "div", + "mod" to "modulo", + "shl" to "shiftLeft", + "shr" to "shiftRight", + "ushr" to "shiftRightUnsigned", + "and" to "and", + "or" to "or", + "xor" to "xor" + ).map { it.first to methodIntrinsic(it.second) }).toMap() + + private val floatBinaryIntrinsics: Map = + mapOf( + "compareTo" to BaseBinaryIntrinsic(::primitiveCompareTo), + "rangeTo" to BaseBinaryIntrinsic(::numberRangeTo), + "plus" to BaseBinaryIntrinsic(::sum), + "minus" to BaseBinaryIntrinsic(::subtract), + "times" to BaseBinaryIntrinsic(::mul), + "div" to BaseBinaryIntrinsic(::div), + "mod" to BaseBinaryIntrinsic(::mod) + ); + + class BaseBinaryIntrinsic(val applyFun: (left: JsExpression, right: JsExpression) -> JsExpression) : FunctionIntrinsic() { + override fun apply(receiver: JsExpression?, arguments: List, context: TranslationContext): JsExpression { + assert(receiver != null) + assert(arguments.size() == 1) + return applyFun(receiver!!, arguments.get(0)) + } + } + + fun methodIntrinsic(methodName: String): BaseBinaryIntrinsic = + BaseBinaryIntrinsic() { left, right -> invokeMethod(left, methodName, right) } + + fun wrapIntrinsicIfPresent(intrinsic: BaseBinaryIntrinsic?, toLeft: (JsExpression) -> JsExpression, toRight: (JsExpression) -> JsExpression): FunctionIntrinsic? = + if (intrinsic != null) BaseBinaryIntrinsic() { left, right -> intrinsic.applyFun(toLeft(left), toRight(right)) } else null + + override fun getIntrinsic(descriptor: FunctionDescriptor): FunctionIntrinsic? { + val operationName = descriptor.getName().asString() + return when { + LONG_EQUALS_ANY.apply(descriptor) || LONG_BINARY_OPERATION_LONG.apply(descriptor) || LONG_BIT_SHIFTS.apply(descriptor) -> + longBinaryIntrinsics[operationName] + INTEGER_BINARY_OPERATION_LONG.apply(descriptor) -> + wrapIntrinsicIfPresent(longBinaryIntrinsics[operationName], { longFromInt(it) }, ID) + LONG_BINARY_OPERATION_INTEGER.apply(descriptor) -> + wrapIntrinsicIfPresent(longBinaryIntrinsics[operationName], ID, { longFromInt(it) }) + FLOATING_POINT_BINARY_OPERATION_LONG.apply(descriptor) -> + wrapIntrinsicIfPresent(floatBinaryIntrinsics[operationName], ID, { invokeMethod(it, Namer.LONG_TO_NUMBER) }) + LONG_BINARY_OPERATION_FLOATING_POINT.apply(descriptor) -> + wrapIntrinsicIfPresent(floatBinaryIntrinsics[operationName], { invokeMethod(it, Namer.LONG_TO_NUMBER) }, ID) + else -> + null + } + } +} \ No newline at end of file diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/NumberConversionFIF.kt b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/NumberConversionFIF.kt index 2c143245acd..837e240dae9 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/NumberConversionFIF.kt +++ b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/NumberConversionFIF.kt @@ -16,41 +16,54 @@ package org.jetbrains.k2js.translate.intrinsic.functions.factories +import com.google.common.base.Predicates import com.google.dart.compiler.backend.js.ast.JsExpression import org.jetbrains.k2js.translate.context.TranslationContext import org.jetbrains.k2js.translate.intrinsic.functions.basic.FunctionIntrinsic -import org.jetbrains.k2js.translate.utils.JsAstUtils - import org.jetbrains.k2js.translate.intrinsic.functions.patterns.PatternBuilder.pattern +import org.jetbrains.k2js.translate.utils.ID +import org.jetbrains.k2js.translate.utils.JsAstUtils.* -//TODO: support longs and chars public object NumberConversionFIF : CompositeFIF() { + val USE_AS_IS = Predicates.or( + pattern("Int.toInt|toFloat|toDouble"), pattern("Short.toShort|toInt|toFloat|toDouble"), + pattern("Byte.toByte|toShort|toInt|toFloat|toDouble"), pattern("Float|Double.toFloat|toDouble"), + pattern("Long.toLong"), pattern("Char.toChar") + ); - //NOTE: treat Number as if it is floating point type - private val convertOperations: MapJsExpression> = + private val convertOperations: Map = mapOf( - "Int.toInt|toFloat|toDouble" to { (receiver, context) -> receiver }, - "Short.toShort|toInt|toFloat|toDouble" to { (receiver, context) -> receiver }, - "Byte.toByte|toShort|toInt|toFloat|toDouble" to { (receiver, context) -> receiver }, - "Float|Double|Number.toFloat|toDouble" to { (receiver, context) -> receiver }, + "Float|Double.toInt" to ConversionUnaryIntrinsic { toInt32(it) }, + "Int|Float|Double.toShort" to ConversionUnaryIntrinsic { toShort(it) }, + "Short|Int|Float|Double.toByte" to ConversionUnaryIntrinsic { toByte(it) }, - "Float|Double|Number.toInt" to { (receiver, context) -> JsAstUtils.toInt32(receiver, context) }, - "Int|Float|Double|Number.toShort" to { (receiver, context) -> JsAstUtils.toShort(receiver) }, - "Short|Int|Float|Double|Number.toByte" to { (receiver, context) -> JsAstUtils.toByte(receiver) } + "Int|Short|Byte.toLong" to ConversionUnaryIntrinsic { longFromInt(it) }, + "Float|Double.toLong" to ConversionUnaryIntrinsic { longFromNumber(it) }, + + "Number.toInt" to ConversionUnaryIntrinsic { invokeKotlinFunction("numberToInt", it) }, + "Number.toShort" to ConversionUnaryIntrinsic { invokeKotlinFunction("numberToShort", it) }, + "Number.toByte" to ConversionUnaryIntrinsic { invokeKotlinFunction("numberToByte", it) }, + "Number.toFloat|toDouble" to ConversionUnaryIntrinsic { invokeKotlinFunction("numberToDouble", it) }, + "Number.toLong" to ConversionUnaryIntrinsic { invokeKotlinFunction("numberToLong", it) }, + + "Long.toFloat|toDouble" to ConversionUnaryIntrinsic { invokeMethod(it, "toNumber") }, + "Long.toInt" to ConversionUnaryIntrinsic { invokeMethod(it, "toInt") }, + "Long.toShort" to ConversionUnaryIntrinsic { toShort(invokeMethod(it, "toInt")) }, + "Long.toByte" to ConversionUnaryIntrinsic { toByte(invokeMethod(it, "toInt")) } ) - fun newUnaryIntrinsic(applyFun: (receiver: JsExpression, context: TranslationContext) -> JsExpression): FunctionIntrinsic = - object : FunctionIntrinsic() { - override fun apply(receiver: JsExpression?, arguments: List, context: TranslationContext): JsExpression { - assert(receiver != null) - assert(arguments.size() == 0) - return applyFun(receiver!!, context) - } - } + class ConversionUnaryIntrinsic(val applyFun: (receiver: JsExpression) -> JsExpression) : FunctionIntrinsic() { + override fun apply(receiver: JsExpression?, arguments: List, context: TranslationContext): JsExpression { + assert(receiver != null) + assert(arguments.size() == 0) + return applyFun(receiver!!) + } + } { - for((stringPattern, applyFun) in convertOperations) { - add(pattern(stringPattern), newUnaryIntrinsic(applyFun)) + add(USE_AS_IS!!, ConversionUnaryIntrinsic(ID)) + for((stringPattern, intrinsic) in convertOperations) { + add(pattern(stringPattern), intrinsic) } } } 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 6bc1d794963..171ea9ca124 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 @@ -19,14 +19,14 @@ package org.jetbrains.k2js.translate.intrinsic.functions.factories; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableMap; -import com.google.dart.compiler.backend.js.ast.*; +import com.google.dart.compiler.backend.js.ast.JsBinaryOperation; +import com.google.dart.compiler.backend.js.ast.JsBinaryOperator; +import com.google.dart.compiler.backend.js.ast.JsExpression; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; import org.jetbrains.jet.lang.resolve.name.Name; -import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lang.types.expressions.OperatorConventions; -import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; import org.jetbrains.jet.lexer.JetToken; import org.jetbrains.k2js.translate.context.TranslationContext; import org.jetbrains.k2js.translate.intrinsic.functions.basic.FunctionIntrinsic; @@ -39,78 +39,71 @@ import org.jetbrains.k2js.translate.utils.JsDescriptorUtils; import java.util.List; import static org.jetbrains.k2js.translate.intrinsic.functions.patterns.PatternBuilder.pattern; -import static org.jetbrains.k2js.translate.utils.JsAstUtils.setArguments; public enum PrimitiveBinaryOperationFIF implements FunctionIntrinsicFactory { INSTANCE; - @NotNull - private static final FunctionIntrinsic RANGE_TO_INTRINSIC = new FunctionIntrinsic() { + private static abstract class BinaryOperationInstrinsicBase extends FunctionIntrinsic { + @NotNull + public abstract JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context); @NotNull @Override - public JsExpression apply(@Nullable JsExpression rangeStart, @NotNull List arguments, + public JsExpression apply( + @Nullable JsExpression receiver, + @NotNull List arguments, @NotNull TranslationContext context) { - assert arguments.size() == 1 : "RangeTo must have one argument."; - assert rangeStart != null; - JsExpression rangeEnd = arguments.get(0); - JsNameRef expr = new JsNameRef("NumberRange", "Kotlin"); - HasArguments numberRangeConstructorInvocation = new JsNew(expr); + assert receiver != null; + assert arguments.size() == 1; + return doApply(receiver, arguments.get(0), context); + } + } + + @NotNull + private static final BinaryOperationInstrinsicBase RANGE_TO_INTRINSIC = new BinaryOperationInstrinsicBase() { + @NotNull + @Override + public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) { //TODO: add tests and correct expression for reversed ranges. - setArguments(numberRangeConstructorInvocation, rangeStart, rangeEnd); - return numberRangeConstructorInvocation; + return JsAstUtils.numberRangeTo(left, right); } }; @NotNull - private static final FunctionIntrinsic INTEGER_DIVISION_INTRINSIC = new FunctionIntrinsic() { + private static final BinaryOperationInstrinsicBase INTEGER_DIVISION_INTRINSIC = new BinaryOperationInstrinsicBase() { @NotNull @Override - public JsExpression apply(@Nullable JsExpression receiver, - @NotNull List arguments, - @NotNull TranslationContext context) { - assert receiver != null; - assert arguments.size() == 1; - JsBinaryOperation div = new JsBinaryOperation(JsBinaryOperator.DIV, receiver, arguments.get(0)); - return JsAstUtils.toInt32(div, context); + public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) { + JsBinaryOperation div = new JsBinaryOperation(JsBinaryOperator.DIV, left, right); + return JsAstUtils.toInt32(div); } }; @NotNull - private static final FunctionIntrinsic BUILTINS_COMPARE_TO_INTRINSIC = new FunctionIntrinsic() { + private static final BinaryOperationInstrinsicBase BUILTINS_COMPARE_TO_INTRINSIC = new BinaryOperationInstrinsicBase() { @NotNull @Override - public JsExpression apply( - @Nullable JsExpression receiver, - @NotNull List arguments, - @NotNull TranslationContext context - ) { - assert receiver != null; - assert arguments.size() == 1; - return JsAstUtils.compareTo(receiver, arguments.get(0)); + public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) { + return JsAstUtils.compareTo(left, right); } }; @NotNull - private static final FunctionIntrinsic PRIMITIVE_NUMBER_COMPARE_TO_INTRINSIC = new FunctionIntrinsic() { + private static final BinaryOperationInstrinsicBase PRIMITIVE_NUMBER_COMPARE_TO_INTRINSIC = new BinaryOperationInstrinsicBase() { @NotNull @Override - public JsExpression apply( - @Nullable JsExpression receiver, - @NotNull List arguments, - @NotNull TranslationContext context - ) { - assert receiver != null; - assert arguments.size() == 1; - return JsAstUtils.primitiveCompareTo(receiver, arguments.get(0)); + public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) { + return JsAstUtils.primitiveCompareTo(left, right); } }; @NotNull private static final NamePredicate BINARY_OPERATIONS = new NamePredicate(OperatorConventions.BINARY_OPERATION_NAMES.values()); - private static final DescriptorPredicate PRIMITIVE_NUMBERS_BINARY_OPERATIONS = pattern(NamePredicate.PRIMITIVE_NUMBERS, BINARY_OPERATIONS); + private static final DescriptorPredicate PRIMITIVE_NUMBERS_BINARY_OPERATIONS = + pattern(NamePredicate.PRIMITIVE_NUMBERS_MAPPED_TO_PRIMITIVE_JS, BINARY_OPERATIONS); + private static final DescriptorPredicate PRIMITIVE_NUMBERS_COMPARE_TO_OPERATIONS = - pattern(NamePredicate.PRIMITIVE_NUMBERS, "compareTo"); + pattern(NamePredicate.PRIMITIVE_NUMBERS_MAPPED_TO_PRIMITIVE_JS, "compareTo"); private static final DescriptorPredicate INT_WITH_BIT_OPERATIONS = pattern("Int.or|and|xor|shl|shr|ushr"); private static final DescriptorPredicate BOOLEAN_OPERATIONS = pattern("Boolean.or|and|xor"); private static final DescriptorPredicate STRING_PLUS = pattern("String.plus"); @@ -135,6 +128,7 @@ public enum PrimitiveBinaryOperationFIF implements FunctionIntrinsicFactory { return PRIMITIVE_NUMBER_COMPARE_TO_INTRINSIC; } + if (JsDescriptorUtils.isBuiltin(descriptor) && descriptor.getName().equals(OperatorConventions.COMPARE_TO)) { return BUILTINS_COMPARE_TO_INTRINSIC; } @@ -144,10 +138,7 @@ public enum PrimitiveBinaryOperationFIF implements FunctionIntrinsicFactory { } - if (pattern("Int|Short|Byte.div").apply(descriptor)) { - JetType resultType = descriptor.getReturnType(); - if (!KotlinBuiltIns.getInstance().getFloatType().equals(resultType) && - !KotlinBuiltIns.getInstance().getDoubleType().equals(resultType)) + if (pattern("Int|Short|Byte.div(Int|Short|Byte)").apply(descriptor)) { return INTEGER_DIVISION_INTRINSIC; } if (descriptor.getName().equals(Name.identifier("rangeTo"))) { @@ -176,7 +167,7 @@ public enum PrimitiveBinaryOperationFIF implements FunctionIntrinsicFactory { return OperatorTable.getBinaryOperator(token); } - private static class PrimitiveBinaryOperationFunctionIntrinsic extends FunctionIntrinsic { + private static class PrimitiveBinaryOperationFunctionIntrinsic extends BinaryOperationInstrinsicBase { @NotNull private final JsBinaryOperator operator; @@ -187,12 +178,8 @@ public enum PrimitiveBinaryOperationFIF implements FunctionIntrinsicFactory { @NotNull @Override - public JsExpression apply(@Nullable JsExpression receiver, - @NotNull List arguments, - @NotNull TranslationContext context) { - assert receiver != null; - assert arguments.size() == 1 : "Binary operator should have a receiver and one argument"; - return new JsBinaryOperation(operator, receiver, arguments.get(0)); + public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) { + return new JsBinaryOperation(operator, left, right); } } } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/PrimitiveUnaryOperationFIF.java b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/PrimitiveUnaryOperationFIF.java index 5d80ede808a..90eb38341fe 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/PrimitiveUnaryOperationFIF.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/PrimitiveUnaryOperationFIF.java @@ -34,7 +34,6 @@ import org.jetbrains.k2js.translate.utils.JsDescriptorUtils; import java.util.List; -import static org.jetbrains.k2js.translate.intrinsic.functions.patterns.NamePredicate.PRIMITIVE_NUMBERS; import static org.jetbrains.k2js.translate.intrinsic.functions.patterns.PatternBuilder.pattern; public enum PrimitiveUnaryOperationFIF implements FunctionIntrinsicFactory { @@ -44,7 +43,7 @@ public enum PrimitiveUnaryOperationFIF implements FunctionIntrinsicFactory { private static final NamePredicate UNARY_OPERATIONS = new NamePredicate(OperatorConventions.UNARY_OPERATION_NAMES.values()); @NotNull private static final DescriptorPredicate UNARY_OPERATION_FOR_PRIMITIVE_NUMBER = - pattern(PRIMITIVE_NUMBERS, UNARY_OPERATIONS); + pattern(NamePredicate.PRIMITIVE_NUMBERS_MAPPED_TO_PRIMITIVE_JS, UNARY_OPERATIONS); @NotNull private static final Predicate PRIMITIVE_UNARY_OPERATION_NAMES = Predicates.or(UNARY_OPERATION_FOR_PRIMITIVE_NUMBER, pattern("Boolean.not"), pattern("Int.inv")); @@ -134,5 +133,4 @@ public enum PrimitiveUnaryOperationFIF implements FunctionIntrinsicFactory { } }; } - } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/TopLevelFIF.java b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/TopLevelFIF.java index 692dc2b7024..7b581850f66 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/TopLevelFIF.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/TopLevelFIF.java @@ -19,9 +19,7 @@ package org.jetbrains.k2js.translate.intrinsic.functions.factories; import com.google.dart.compiler.backend.js.ast.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jet.lang.descriptors.ClassDescriptor; import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; -import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; import org.jetbrains.jet.lang.descriptors.PropertyDescriptor; import org.jetbrains.jet.lang.psi.JetExpression; import org.jetbrains.jet.lang.psi.JetQualifiedExpression; @@ -42,13 +40,12 @@ import org.jetbrains.k2js.translate.intrinsic.functions.patterns.NamePredicate; import org.jetbrains.k2js.translate.utils.AnnotationsUtils; import org.jetbrains.k2js.translate.utils.BindingUtils; import org.jetbrains.k2js.translate.utils.JsDescriptorUtils; -import org.jetbrains.k2js.translate.utils.TranslationUtils; -import java.util.Collection; import java.util.List; import static org.jetbrains.k2js.translate.intrinsic.functions.basic.FunctionIntrinsic.CallParametersAwareFunctionIntrinsic; import static org.jetbrains.k2js.translate.intrinsic.functions.patterns.PatternBuilder.pattern; +import static org.jetbrains.k2js.translate.utils.TranslationUtils.getStableMangledNameForDescriptor; public final class TopLevelFIF extends CompositeFIF { public static final DescriptorPredicate EQUALS_IN_ANY = pattern("kotlin", "Any", "equals"); @@ -133,13 +130,6 @@ public final class TopLevelFIF extends CompositeFIF { } }; - @NotNull - private static String getStableMangledBuiltInName(@NotNull ClassDescriptor descriptor, @NotNull String functionName) { - Collection functions = descriptor.getDefaultType().getMemberScope().getFunctions(Name.identifier(functionName)); - assert functions.size() == 1 : "Can't select a single function: " + functionName + " in " + descriptor; - return TranslationUtils.getSuggestedName(functions.iterator().next()); - } - private static final FunctionIntrinsic PROPERTY_METADATA_IMPL = new FunctionIntrinsic() { @NotNull @Override @@ -220,7 +210,7 @@ public final class TopLevelFIF extends CompositeFIF { } } - String mangledName = getStableMangledBuiltInName(KotlinBuiltIns.getInstance().getMutableMap(), operationName()); + String mangledName = getStableMangledNameForDescriptor(KotlinBuiltIns.getInstance().getMutableMap(), operationName()); return new JsInvocation(new JsNameRef(mangledName, thisOrReceiver), arguments); } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/patterns/NamePredicate.java b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/patterns/NamePredicate.java index 2b99f495ff7..ce4f7d41e13 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/patterns/NamePredicate.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/patterns/NamePredicate.java @@ -35,12 +35,23 @@ public final class NamePredicate implements Predicate { @NotNull public static final NamePredicate PRIMITIVE_NUMBERS = new NamePredicate( - ContainerUtil.map(PrimitiveType.NUMBER_TYPES, new Function() { - @Override - public String fun(PrimitiveType type) { - return type.getTypeName().asString(); - } - })); + ContainerUtil.map(PrimitiveType.NUMBER_TYPES, + new Function() { + @Override + public String fun(PrimitiveType type) { + return type.getTypeName().asString(); + } + })); + + @NotNull + public static final NamePredicate PRIMITIVE_NUMBERS_MAPPED_TO_PRIMITIVE_JS = new NamePredicate( + ContainerUtil.mapNotNull(PrimitiveType.NUMBER_TYPES, + new Function() { + @Override + public String fun(PrimitiveType type) { + return type != PrimitiveType.LONG ? type.getTypeName().asString() : null; + } + })); @NotNull public static final NamePredicate STRING = new NamePredicate("String"); @@ -48,6 +59,9 @@ public final class NamePredicate implements Predicate { @NotNull public static final NamePredicate NUMBER = new NamePredicate("Number"); + @NotNull + public static final NamePredicate LONG = new NamePredicate(PrimitiveType.LONG.getTypeName()); + @NotNull private final Set validNames = Sets.newHashSet(); diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/patterns/PatternBuilder.java b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/patterns/PatternBuilder.java index 5dfcc821542..eab0d3b8e9d 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/patterns/PatternBuilder.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/patterns/PatternBuilder.java @@ -24,6 +24,7 @@ import org.jetbrains.jet.lang.resolve.DescriptorUtils; import org.jetbrains.jet.lang.resolve.OverrideResolver; import org.jetbrains.jet.lang.resolve.name.Name; import org.jetbrains.k2js.translate.context.Namer; +import org.jetbrains.k2js.translate.utils.JsDescriptorUtils; import org.jetbrains.k2js.translate.utils.TranslationUtils; import java.util.Arrays; @@ -43,25 +44,25 @@ public final class PatternBuilder { @NotNull public static DescriptorPredicate pattern(@NotNull NamePredicate checker, @NotNull String stringWithPattern) { List checkers = Lists.newArrayList(checker); - checkers.addAll(parseStringAsCheckerList(stringWithPattern)); - return pattern(checkers); + checkers.addAll(parseFqNamesFromString(stringWithPattern)); + return pattern(checkers, parseArgumentsFromString(stringWithPattern)); } @NotNull public static DescriptorPredicate pattern(@NotNull String stringWithPattern, @NotNull NamePredicate checker) { - List checkers = Lists.newArrayList(parseStringAsCheckerList(stringWithPattern)); + List checkers = Lists.newArrayList(parseFqNamesFromString(stringWithPattern)); checkers.add(checker); return pattern(checkers); } @NotNull - public static DescriptorPredicate pattern(@NotNull String string) { - List checkers = parseStringAsCheckerList(string); - return pattern(checkers); + public static DescriptorPredicate pattern(@NotNull String stringWithPattern) { + return pattern(parseFqNamesFromString(stringWithPattern), parseArgumentsFromString(stringWithPattern)); } @NotNull - private static List parseStringAsCheckerList(@NotNull String stringWithPattern) { + private static List parseFqNamesFromString(@NotNull String stringWithPattern) { + stringWithPattern = getNamePatternFromString(stringWithPattern); String[] subPatterns = stringWithPattern.split("\\."); List checkers = Lists.newArrayList(); for (String subPattern : subPatterns) { @@ -71,8 +72,55 @@ public final class PatternBuilder { return checkers; } + @Nullable + private static List parseArgumentsFromString(@NotNull String stringWithPattern) { + stringWithPattern = getArgumentsPatternFromString(stringWithPattern); + if (stringWithPattern == null) return null; + + List checkers = Lists.newArrayList(); + if (stringWithPattern.isEmpty()) { + return checkers; + } + + String[] subPatterns = stringWithPattern.split("\\,"); + for (String subPattern : subPatterns) { + String[] validNames = subPattern.split("\\|"); + checkers.add(new NamePredicate(validNames)); + } + return checkers; + } + + @NotNull + private static String getNamePatternFromString(@NotNull String stringWithPattern) { + int left = stringWithPattern.indexOf("("); + if (left < 0) { + return stringWithPattern; + } + else { + return stringWithPattern.substring(0, left); + } + } + + @Nullable + private static String getArgumentsPatternFromString(@NotNull String stringWithPattern) { + int left = stringWithPattern.indexOf("("); + if (left < 0) { + return null; + } + else { + int right = stringWithPattern.indexOf(")"); + assert right == stringWithPattern.length() - 1 : "expected ')' at the end: " + stringWithPattern; + return stringWithPattern.substring(left + 1, right); + } + } + @NotNull private static DescriptorPredicate pattern(@NotNull List checkers) { + return pattern(checkers, null); + } + + @NotNull + private static DescriptorPredicate pattern(@NotNull List checkers, @Nullable List arguments) { assert !checkers.isEmpty(); final List checkersWithPrefixChecker = Lists.newArrayList(); if (!checkers.get(0).apply(KOTLIN_NAME)) { @@ -83,6 +131,8 @@ public final class PatternBuilder { assert checkersWithPrefixChecker.size() > 1; + final List argumentCheckers = arguments != null ? Lists.newArrayList(arguments) : null; + return new DescriptorPredicate() { @Override public boolean apply(@Nullable FunctionDescriptor descriptor) { @@ -98,10 +148,25 @@ public final class PatternBuilder { private boolean doApply(@NotNull FunctionDescriptor descriptor) { List nameParts = DescriptorUtils.getFqName(descriptor).pathSegments(); - if (nameParts.size() != checkersWithPrefixChecker.size()) { - return false; + if (nameParts.size() != checkersWithPrefixChecker.size()) return false; + + return allNamePartsValid(nameParts) && checkAllArgumentsValidIfNeeded(descriptor); + } + + private boolean checkAllArgumentsValidIfNeeded(@NotNull FunctionDescriptor descriptor) { + if (argumentCheckers != null) { + List valueParameterDescriptors = descriptor.getValueParameters(); + if (valueParameterDescriptors.size() != argumentCheckers.size()) { + return false; + } + for (int i = 0; i < valueParameterDescriptors.size(); i++) { + ValueParameterDescriptor valueParameterDescriptor = valueParameterDescriptors.get(i); + Name name = JsDescriptorUtils.getNameIfStandardType(valueParameterDescriptor.getType()); + NamePredicate namePredicate = argumentCheckers.get(i); + if (!namePredicate.apply(name)) return false; + } } - return allNamePartsValid(nameParts); + return true; } private boolean allNamePartsValid(@NotNull List nameParts) { diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/operation/CompareToBOIF.kt b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/operation/CompareToBOIF.kt index 79ec2693635..234502585aa 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/operation/CompareToBOIF.kt +++ b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/operation/CompareToBOIF.kt @@ -31,6 +31,7 @@ import org.jetbrains.k2js.translate.utils.PsiUtils.getOperationToken object CompareToBOIF : BinaryOperationIntrinsicFactory { + val PRIMITIVE_COMPARE_TO = pattern("Int|Short|Byte|Double|Float|Char|String.compareTo") private object CompareToIntrinsic : AbstractBinaryOperationIntrinsic() { override fun apply(expression: JetBinaryExpression, left: JsExpression, right: JsExpression, context: TranslationContext): JsExpression { @@ -43,7 +44,7 @@ object CompareToBOIF : BinaryOperationIntrinsicFactory { override fun apply(expression: JetBinaryExpression, left: JsExpression, right: JsExpression, context: TranslationContext): JsExpression { val operator = OperatorTable.getBinaryOperator(getOperationToken(expression)) val compareTo = JsAstUtils.compareTo(left, right) - return JsBinaryOperation(operator, compareTo, context.program().getNumberLiteral(0)) + return JsBinaryOperation(operator, compareTo, JsNumberLiteral.ZERO) } } @@ -52,7 +53,7 @@ object CompareToBOIF : BinaryOperationIntrinsicFactory { override public fun getIntrinsic(descriptor: FunctionDescriptor): BinaryOperationIntrinsic? { if (JsDescriptorUtils.isBuiltin(descriptor)) when { - pattern("Int|Short|Byte|Double|Float|Char|String|Long.compareTo").apply(descriptor) -> + PRIMITIVE_COMPARE_TO.apply(descriptor) -> return CompareToIntrinsic else -> return CompareToFunctionIntrinsic diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/operation/EqualsBOIF.kt b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/operation/EqualsBOIF.kt index f8435036b3c..261e6611111 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/operation/EqualsBOIF.kt +++ b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/operation/EqualsBOIF.kt @@ -33,9 +33,20 @@ import org.jetbrains.k2js.translate.utils.PsiUtils.getOperationToken import org.jetbrains.jet.lexer.JetToken import org.jetbrains.jet.lexer.JetTokens import com.google.common.collect.ImmutableSet +import org.jetbrains.k2js.translate.intrinsic.functions.patterns.PatternBuilder.pattern object EqualsBOIF : BinaryOperationIntrinsicFactory { + val LONG_EQUALS_ANY = pattern("Long.equals") + + private object LONG_EQUALS_ANY_INTRINSIC : AbstractBinaryOperationIntrinsic() { + override fun apply(expression: JetBinaryExpression, left: JsExpression, right: JsExpression, context: TranslationContext): JsExpression { + val isNegated = getOperationToken(expression) == JetTokens.EXCLEQ + val invokeEquals = JsAstUtils.equalsForObject(left, right) + return if (isNegated) JsAstUtils.not(invokeEquals) else invokeEquals + } + } + private object EqualsIntrinsic : AbstractBinaryOperationIntrinsic() { override fun apply(expression: JetBinaryExpression, left: JsExpression, right: JsExpression, context: TranslationContext): JsExpression { val isNegated = getOperationToken(expression) == JetTokens.EXCLEQ @@ -54,7 +65,7 @@ object EqualsBOIF : BinaryOperationIntrinsicFactory { val left = expression.getLeft() assert(left != null) { "No left-hand side: " + expression.getText() } val typeName = JsDescriptorUtils.getNameIfStandardType(left!!, context) - return typeName != null && NamePredicate.PRIMITIVE_NUMBERS.apply(typeName) + return typeName != null && NamePredicate.PRIMITIVE_NUMBERS_MAPPED_TO_PRIMITIVE_JS.apply(typeName) } } @@ -62,9 +73,8 @@ object EqualsBOIF : BinaryOperationIntrinsicFactory { override public fun getIntrinsic(descriptor: FunctionDescriptor): BinaryOperationIntrinsic? { if (JsDescriptorUtils.isBuiltin(descriptor) || TopLevelFIF.EQUALS_IN_ANY.apply(descriptor)) { - return EqualsIntrinsic + return if (LONG_EQUALS_ANY.apply(descriptor)) LONG_EQUALS_ANY_INTRINSIC else EqualsIntrinsic } return null } } - diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/operation/LongCompareToBOIF.kt b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/operation/LongCompareToBOIF.kt new file mode 100644 index 00000000000..3f58e4fcb31 --- /dev/null +++ b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/operation/LongCompareToBOIF.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2010-2014 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.k2js.translate.intrinsic.operation + +import com.google.common.collect.ImmutableSet +import com.google.dart.compiler.backend.js.ast.* +import org.jetbrains.jet.lang.descriptors.FunctionDescriptor +import org.jetbrains.jet.lang.psi.JetBinaryExpression +import org.jetbrains.jet.lang.types.expressions.OperatorConventions +import org.jetbrains.jet.lexer.JetToken +import org.jetbrains.k2js.translate.context.Namer +import org.jetbrains.k2js.translate.context.TranslationContext +import org.jetbrains.k2js.translate.intrinsic.functions.patterns.PatternBuilder.pattern +import org.jetbrains.k2js.translate.operation.OperatorTable +import org.jetbrains.k2js.translate.utils.ID +import org.jetbrains.k2js.translate.utils.JsAstUtils.* +import org.jetbrains.k2js.translate.utils.JsDescriptorUtils +import org.jetbrains.k2js.translate.utils.PsiUtils.getOperationToken + +public object LongCompareToBOIF : BinaryOperationIntrinsicFactory { + + val FLOATING_POINT_COMPARE_TO_LONG_PATTERN = pattern("Double|Float.compareTo(Long)") + val LONG_COMPARE_TO_FLOATING_POINT_PATTERN = pattern("Long.compareTo(Float|Double)") + val INTEGER_COMPARE_TO_LONG_PATTERN = pattern("Int|Short|Byte.compareTo(Long)") + val LONG_COMPARE_TO_INTEGER_PATTERN = pattern("Long.compareTo(Int|Short|Byte)") + val LONG_COMPARE_TO_LONG_PATTERN = pattern("Long.compareTo(Long)") + + private object FLOATING_POINT_COMPARE_TO_LONG : AbstractBinaryOperationIntrinsic() { + override fun apply(expression: JetBinaryExpression, left: JsExpression, right: JsExpression, context: TranslationContext): JsExpression { + val operator = OperatorTable.getBinaryOperator(getOperationToken(expression)) + return JsBinaryOperation(operator, left, invokeMethod(right, Namer.LONG_TO_NUMBER)) + } + } + + private object LONG_COMPARE_TO_FLOATING_POINT : AbstractBinaryOperationIntrinsic() { + override fun apply(expression: JetBinaryExpression, left: JsExpression, right: JsExpression, context: TranslationContext): JsExpression { + val operator = OperatorTable.getBinaryOperator(getOperationToken(expression)) + return JsBinaryOperation(operator, invokeMethod(left, Namer.LONG_TO_NUMBER), right) + } + } + + private class CompareToBinaryIntrinsic(val toLeft: (JsExpression) -> JsExpression, val toRight: (JsExpression) -> JsExpression) : AbstractBinaryOperationIntrinsic() { + override fun apply(expression: JetBinaryExpression, left: JsExpression, right: JsExpression, context: TranslationContext): JsExpression { + val operator = OperatorTable.getBinaryOperator(getOperationToken(expression)) + val compareInvocation = compareForObject(toLeft(left), toRight(right)) + return JsBinaryOperation(operator, compareInvocation, JsNumberLiteral.ZERO) + } + } + + private val INTEGER_COMPARE_TO_LONG = CompareToBinaryIntrinsic( { longFromInt(it) }, ID) + private val LONG_COMPARE_TO_INTEGER = CompareToBinaryIntrinsic( ID, { longFromInt(it) }) + private val LONG_COMPARE_TO_LONG = CompareToBinaryIntrinsic( ID, ID ) + + override public fun getSupportTokens(): ImmutableSet = OperatorConventions.COMPARISON_OPERATIONS + + override public fun getIntrinsic(descriptor: FunctionDescriptor): BinaryOperationIntrinsic? { + if (JsDescriptorUtils.isBuiltin(descriptor)) { + return when { + FLOATING_POINT_COMPARE_TO_LONG_PATTERN.apply(descriptor) -> FLOATING_POINT_COMPARE_TO_LONG + LONG_COMPARE_TO_FLOATING_POINT_PATTERN.apply(descriptor) -> LONG_COMPARE_TO_FLOATING_POINT + INTEGER_COMPARE_TO_LONG_PATTERN.apply(descriptor) -> INTEGER_COMPARE_TO_LONG + LONG_COMPARE_TO_INTEGER_PATTERN.apply(descriptor) -> LONG_COMPARE_TO_INTEGER + LONG_COMPARE_TO_LONG_PATTERN.apply(descriptor) -> LONG_COMPARE_TO_LONG + else -> null + } + } + return null + } +} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/operation/binaryOperationIntrinsics.kt b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/operation/binaryOperationIntrinsics.kt index f3755905640..7e8da833ff5 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/operation/binaryOperationIntrinsics.kt +++ b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/operation/binaryOperationIntrinsics.kt @@ -38,7 +38,7 @@ public class BinaryOperationIntrinsics { private val intrinsicCache = THashMap, BinaryOperationIntrinsic>() - private val factories = listOf(EqualsBOIF, CompareToBOIF) + private val factories = listOf(LongCompareToBOIF, EqualsBOIF, CompareToBOIF) public fun getIntrinsic(expression: JetBinaryExpression, context: TranslationContext): BinaryOperationIntrinsic { val token = getOperationToken(expression) 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 de509b7110a..009944f8a6b 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 @@ -119,33 +119,103 @@ public final class JsAstUtils { } @NotNull - public static JsExpression toInt32(@NotNull JsExpression expression, @NotNull TranslationContext context) { - return new JsBinaryOperation(JsBinaryOperator.BIT_OR, expression, context.program().getNumberLiteral(0)); + public static JsInvocation invokeKotlinFunction(@NotNull String name, @NotNull JsExpression... argument) { + return new JsInvocation(new JsNameRef(name, Namer.KOTLIN_OBJECT_REF), argument); + } + + @NotNull + public static JsInvocation invokeMethod(@NotNull JsExpression thisObject, @NotNull String name, @NotNull JsExpression... arguments) { + return new JsInvocation(new JsNameRef(name, thisObject), arguments); + } + + @NotNull + public static JsExpression toInt32(@NotNull JsExpression expression) { + return new JsBinaryOperation(JsBinaryOperator.BIT_OR, expression, JsNumberLiteral.ZERO); } @NotNull public static JsExpression toShort(@NotNull JsExpression expression) { - return new JsInvocation(new JsNameRef(OperatorConventions.SHORT.getIdentifier(), Namer.KOTLIN_OBJECT_REF), expression); + return invokeKotlinFunction(OperatorConventions.SHORT.getIdentifier(), expression); } @NotNull public static JsExpression toByte(@NotNull JsExpression expression) { - return new JsInvocation(new JsNameRef(OperatorConventions.BYTE.getIdentifier(), Namer.KOTLIN_OBJECT_REF), expression); + return invokeKotlinFunction(OperatorConventions.BYTE.getIdentifier(), expression); + } + + @NotNull + public static JsExpression toLong(@NotNull JsExpression expression) { + return invokeKotlinFunction(OperatorConventions.LONG.getIdentifier(), 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); + return invokeKotlinFunction(OperatorConventions.COMPARE_TO.getIdentifier(), left, right); } @NotNull public static JsExpression primitiveCompareTo(@NotNull JsExpression left, @NotNull JsExpression right) { - return new JsInvocation(new JsNameRef("primitiveCompareTo", Namer.KOTLIN_OBJECT_REF), left, right); + return invokeKotlinFunction(Namer.PRIMITIVE_COMPARE_TO, left, right); } @NotNull public static JsExpression isNumber(@NotNull JsExpression expression) { - return new JsInvocation(new JsNameRef("isNumber", Namer.KOTLIN_OBJECT_REF), expression); + return invokeKotlinFunction(Namer.IS_NUMBER, expression); + } + + @NotNull + private static JsExpression rangeTo(@NotNull String rangeClassName, @NotNull JsExpression rangeStart, @NotNull JsExpression rangeEnd) { + JsNameRef expr = new JsNameRef(rangeClassName, Namer.KOTLIN_NAME); + JsNew numberRangeConstructorInvocation = new JsNew(expr); + setArguments(numberRangeConstructorInvocation, rangeStart, rangeEnd); + return numberRangeConstructorInvocation; + } + + @NotNull + public static JsExpression numberRangeTo(@NotNull JsExpression rangeStart, @NotNull JsExpression rangeEnd) { + return rangeTo(Namer.NUMBER_RANGE, rangeStart, rangeEnd); + } + + @NotNull + public static final JsNameRef kotlinLongNameRef = new JsNameRef("Long", Namer.KOTLIN_OBJECT_REF); + + @NotNull + public static JsExpression isLong(@NotNull JsExpression expression) { + return instanceOf(expression, kotlinLongNameRef); + } + + public static JsExpression newLong(long value, @NotNull TranslationContext context) { + if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) { + int low = (int) value; + int high = (int) (value >> 32); + List args = new SmartList(); + args.add(context.program().getNumberLiteral(low)); + args.add(context.program().getNumberLiteral(high)); + return new JsNew(kotlinLongNameRef, args); + } + else { + return longFromInt(context.program().getNumberLiteral((int) value)); + } + } + + @NotNull + public static JsExpression longFromInt(@NotNull JsExpression expression) { + return invokeMethod(kotlinLongNameRef, Namer.LONG_FROM_INT, expression); + } + + @NotNull + public static JsExpression longFromNumber(@NotNull JsExpression expression) { + return invokeMethod(kotlinLongNameRef, Namer.LONG_FROM_NUMBER, expression); + } + + @NotNull + public static JsExpression equalsForObject(@NotNull JsExpression left, @NotNull JsExpression right) { + return invokeMethod(left, Namer.EQUALS_METHOD_NAME, right); + } + + @NotNull + public static JsExpression compareForObject(@NotNull JsExpression left, @NotNull JsExpression right) { + return invokeMethod(left, Namer.COMPARE_TO_METHOD_NAME, right); } @NotNull @@ -163,6 +233,11 @@ public final class JsAstUtils { return new JsBinaryOperation(JsBinaryOperator.OR, op1, op2); } + @NotNull + public static JsBinaryOperation instanceOf(@NotNull JsExpression op1, @NotNull JsExpression op2) { + return new JsBinaryOperation(JsBinaryOperator.INSTANCEOF, op1, op2); + } + public static void setQualifier(@NotNull JsExpression selector, @Nullable JsExpression receiver) { assert (selector instanceof JsInvocation || selector instanceof JsNameRef); if (selector instanceof JsInvocation) { @@ -232,6 +307,16 @@ public final class JsAstUtils { return new JsBinaryOperation(JsBinaryOperator.MUL, left, right); } + @NotNull + public static JsBinaryOperation div(@NotNull JsExpression left, @NotNull JsExpression right) { + return new JsBinaryOperation(JsBinaryOperator.DIV, left, right); + } + + @NotNull + public static JsBinaryOperation mod(@NotNull JsExpression left, @NotNull JsExpression right) { + return new JsBinaryOperation(JsBinaryOperator.MOD, left, right); + } + @NotNull public static JsPrefixOperation not(@NotNull JsExpression expression) { return new JsPrefixOperation(JsUnaryOperator.NOT, expression); diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java index 1c702c78318..f84b2ac8bca 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java @@ -28,8 +28,10 @@ import org.jetbrains.jet.lang.descriptors.impl.AnonymousFunctionDescriptor; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.OverrideResolver; import org.jetbrains.jet.lang.resolve.name.FqName; +import org.jetbrains.jet.lang.resolve.name.Name; import org.jetbrains.jet.lang.resolve.scopes.JetScope; import org.jetbrains.jet.lang.types.JetType; +import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; import org.jetbrains.k2js.translate.context.TemporaryConstVariable; import org.jetbrains.k2js.translate.context.TranslationContext; import org.jetbrains.k2js.translate.general.Translation; @@ -385,6 +387,10 @@ public final class TranslationUtils { CallableDescriptor operationDescriptor = getCallableDescriptorForOperationExpression(context.bindingContext(), expression); if (operationDescriptor == null || !(operationDescriptor instanceof FunctionDescriptor)) return true; + + JetType returnType = operationDescriptor.getReturnType(); + if (returnType != null && returnType.equals(KotlinBuiltIns.getInstance().getLongType())) return false; + if (context.intrinsics().getFunctionIntrinsics().getIntrinsic((FunctionDescriptor) operationDescriptor).exists()) return true; return false; @@ -423,6 +429,13 @@ public final class TranslationUtils { return ensureNotNull; } + @NotNull + public static String getStableMangledNameForDescriptor(@NotNull ClassDescriptor descriptor, @NotNull String functionName) { + Collection functions = descriptor.getDefaultType().getMemberScope().getFunctions(Name.identifier(functionName)); + assert functions.size() == 1 : "Can't select a single function: " + functionName + " in " + descriptor; + return getSuggestedName(functions.iterator().next()); + } + @NotNull public static String getSuggestedNameForInnerDeclaration(TranslationContext context, DeclarationDescriptor descriptor) { String suggestedName = ""; diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/utils.kt b/js/js.translator/src/org/jetbrains/k2js/translate/utils/utils.kt new file mode 100644 index 00000000000..d793f2b0828 --- /dev/null +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/utils.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2010-2014 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.k2js.translate.utils + +public val ID: (T) -> T = { it } \ No newline at end of file diff --git a/js/js.translator/testData/kotlin_lib.js b/js/js.translator/testData/kotlin_lib.js index 2c4318aa248..df63cb8af93 100644 --- a/js/js.translator/testData/kotlin_lib.js +++ b/js/js.translator/testData/kotlin_lib.js @@ -85,7 +85,7 @@ return "[" + a.join(", ") + "]"; }; - Kotlin.compareTo = function(a, b) { + Kotlin.compareTo = function (a, b) { var type = typeof a; if (type == "number" || type == "string") { return a < b ? -1 : a > b ? 1 : 0; @@ -93,22 +93,46 @@ return a.compareTo_za3rmp$(b); }; - Kotlin.primitiveCompareTo = function(a, b) { + Kotlin.primitiveCompareTo = function (a, b) { return a < b ? -1 : a > b ? 1 : 0; }; Kotlin.isNumber = function (a) { - return typeof a == "number"; + return typeof a == "number" || a instanceof Kotlin.Long; }; - Kotlin.toShort = function(a) { + Kotlin.toInt = function (a) { + return a | 0; + }; + + Kotlin.toShort = function (a) { return (a & 0xFFFF) << 16 >> 16; }; - Kotlin.toByte = function(a) { + Kotlin.toByte = function (a) { return (a & 0xFF) << 24 >> 24; }; + Kotlin.numberToLong = function (a) { + return a instanceof Kotlin.Long ? a : Kotlin.Long.fromNumber(a); + }; + + Kotlin.numberToInt = function (a) { + return a instanceof Kotlin.Long ? a.toInt() : (a | 0); + }; + + Kotlin.numberToShort = function (a) { + return Kotlin.toShort(Kotlin.numberToInt(a)); + }; + + Kotlin.numberToByte = function (a) { + return Kotlin.toByte(Kotlin.numberToInt(a)); + }; + + Kotlin.numberToDouble = function (a) { + return +a; + }; + Kotlin.intUpto = function (from, to) { return new Kotlin.NumberRange(from, to); }; @@ -530,6 +554,15 @@ } }); + function isSameNotNullRanges(other) { + var classObject = this.constructor; + if (this instanceof classObject && other instanceof classObject) { + return this.isEmpty() && other.isEmpty() || + (this.start === other.start && this.end === other.end && this.increment === other.increment); + } + return false; + } + Kotlin.NumberRange = Kotlin.createClassNow(null, function (start, end) { this.start = start; @@ -545,11 +578,7 @@ isEmpty: function () { return this.start > this.end; }, - equals_za3rmp$: function(other) { - if (other == null) - return false; - return this.start === other.start && this.end === other.end && this.increment === other.increment; - } + equals_za3rmp$: isSameNotNullRanges }); Kotlin.NumberProgression = Kotlin.createClassNow(null, @@ -566,6 +595,58 @@ } }); + Kotlin.LongRangeIterator = Kotlin.createClassNow(Kotlin.Iterator, + function (start, end, increment) { + this.start = start; + this.end = end; + this.increment = increment; + this.i = start; + }, { + next: function () { + var value = this.i; + this.i = this.i.add(this.increment); + return value; + }, + hasNext: function () { + if (this.increment.isNegative()) + return this.i.compare(this.end) >= 0; + else + return this.i.compare(this.end) <= 0; + } + }); + + Kotlin.LongRange = Kotlin.createClassNow(null, + function (start, end) { + this.start = start; + this.end = end; + this.increment = Kotlin.Long.ONE; + }, { + contains: function (number) { + return this.start.compare(number) <= 0 && number.compare(this.end) <= 0; + }, + iterator: function () { + return new Kotlin.LongRangeIterator(this.start, this.end, this.increment); + }, + isEmpty: function () { + return this.start.compare(this.end) > 0; + }, + equals_za3rmp$: isSameNotNullRanges + }); + + Kotlin.LongProgression = Kotlin.createClassNow(null, + function (start, end, increment) { + this.start = start; + this.end = end; + this.increment = increment; + }, { + iterator: function () { + return new Kotlin.LongRangeIterator(this.start, this.end, this.increment); + }, + isEmpty: function() { + return this.increment.isNegative() ? this.start.compare(this.end) < 0 : this.start.compare(this.end) > 0; + } + }); + /** * @interface * @template T @@ -703,6 +784,12 @@ }); }; + Kotlin.longArrayOfSize = function (size) { + return Kotlin.arrayFromFun(size, function () { + return Kotlin.Long.ZERO; + }); + }; + Kotlin.arrayFromFun = function (size, initFun) { var result = new Array(size); for (var i = 0; i < size; i++) { diff --git a/js/js.translator/testData/long.js b/js/js.translator/testData/long.js index c6bafbdb025..b95cdf75aa3 100644 --- a/js/js.translator/testData/long.js +++ b/js/js.translator/testData/long.js @@ -826,4 +826,15 @@ Kotlin.Long.prototype.valueOf = function() { return this.toNumber(); }; + + Kotlin.Long.prototype.plus = function() { + return this; + }; + + Kotlin.Long.prototype.minus = Kotlin.Long.prototype.negate; + Kotlin.Long.prototype.inv = Kotlin.Long.prototype.not; + + Kotlin.Long.prototype.rangeTo = function (other) { + return new Kotlin.LongRange(this, other); + }; }(Kotlin)); \ No newline at end of file diff --git a/js/js.translator/testData/standardClasses/cases/arrayFactoryMethods.kt b/js/js.translator/testData/standardClasses/cases/arrayFactoryMethods.kt index 15b8b080ad8..80f9655a4a9 100644 --- a/js/js.translator/testData/standardClasses/cases/arrayFactoryMethods.kt +++ b/js/js.translator/testData/standardClasses/cases/arrayFactoryMethods.kt @@ -5,7 +5,7 @@ native fun eval(arg: String): Any? = noImpl fun test(testName: String, actual: Any, expectedAsString: String) { val expected = eval("[$expectedAsString]") - if (actual != expected) throw Fail("Wrong result for '$testName' function. Expected: $expected | Actual: $actual") + assertEquals(expected, actual) } fun box(): String { @@ -16,8 +16,8 @@ fun box(): String { test("byteArray", byteArray(0, 1, 2, 3, 4), "0, 1, 2, 3, 4") test("shortArray", shortArray(0, 1, 2, 3, 4), "0, 1, 2, 3, 4") test("intArray,", intArray(0, 1, 2, 3, 4), "0, 1, 2, 3, 4") - test("longArray", longArray(0, 1, 2, 3, 4), "0, 1, 2, 3, 4") - test("floatArray", floatArray(0.0.toFloat(), 1.0.toFloat(), 2.0.toFloat(), 3.0.toFloat(), 4.0.toFloat()), "0.0, 1.0, 2.0, 3.0, 4.0") + test("longArray", longArray(0, 1, 2, 3, 4), "Kotlin.Long.fromInt(0), Kotlin.Long.fromInt(1), Kotlin.Long.fromInt(2), Kotlin.Long.fromInt(3), Kotlin.Long.fromInt(4)") + test("floatArray", floatArray(0.0f, 1.0f, 2.0f, 3.0f, 4.0f), "0.0, 1.0, 2.0, 3.0, 4.0") test("doubleArray", doubleArray(0.0, 1.1, 2.2, 3.3, 4.4), "0.0, 1.1, 2.2, 3.3, 4.4") } catch (e: Fail) {