JS: char boxing

This commit is contained in:
Anton Bannykh
2016-12-27 16:11:14 +03:00
parent 55eeb74c08
commit 77aa685496
33 changed files with 438 additions and 59 deletions
@@ -933,6 +933,10 @@ public abstract class KotlinBuiltIns {
return isConstructedFromGivenClassAndNotNullable(type, FQ_NAMES._char);
}
public static boolean isCharOrNullableChar(@NotNull KotlinType type) {
return isConstructedFromGivenClass(type, FQ_NAMES._char);
}
public static boolean isInt(@NotNull KotlinType type) {
return isConstructedFromGivenClassAndNotNullable(type, FQ_NAMES._int);
}
@@ -50,6 +50,8 @@ var JsParameter.hasDefaultValue: Boolean by MetadataProperty(default = false)
var JsInvocation.typeCheck: TypeCheck? by MetadataProperty(default = null)
var JsInvocation.boxing: Boolean by MetadataProperty(default = false)
/**
* For function and lambda bodies indicates what declaration corresponds to.
* When absent (`null`) on body of a named function, this function is from external JS module.
@@ -227,7 +227,7 @@ class NameSuggestion {
if (overriddenDescriptor is FunctionDescriptor) {
when (overriddenDescriptor.fqNameUnsafe.asString()) {
"kotlin.CharSequence.get" -> return NameAndStability("charAt", true)
"kotlin.CharSequence.get" -> return NameAndStability("charCodeAt", true)
"kotlin.Any.equals" -> return NameAndStability("equals", true)
}
val container = overriddenDescriptor.containingDeclaration
+23
View File
@@ -70,4 +70,27 @@ internal fun newThrowable(message: String?, cause: Throwable?): Throwable {
throwable.cause = cause
throwable.name = "Throwable"
return throwable
}
public class BoxedChar(val c: Char) : Comparable<Char> {
override fun equals(other: Any?): Boolean {
return other is BoxedChar && c == other.c
}
override fun hashCode(): Int {
return c.toInt()
}
override fun toString(): String {
return c.toString()
}
override fun compareTo(other: Char): Int {
return c - other
}
@JsName("valueOf")
public fun valueOf(): Int {
return js("this.c")
}
}
+2 -2
View File
@@ -20,10 +20,10 @@ package kotlin.text
public fun Char.isWhitespace(): Boolean = toString().matches("[\\s\\xA0]")
@kotlin.internal.InlineOnly
public inline fun Char.toLowerCase(): Char = asDynamic().toLowerCase()
public inline fun Char.toLowerCase(): Char = js("String.fromCharCode")(this).toLowerCase().charCodeAt(0)
@kotlin.internal.InlineOnly
public inline fun Char.toUpperCase(): Char = asDynamic().toUpperCase()
public inline fun Char.toUpperCase(): Char = js("String.fromCharCode")(this).toUpperCase().charCodeAt(0)
/**
* Returns `true` if this character is a Unicode high-surrogate code unit (also known as leading-surrogate code unit).
@@ -12944,7 +12944,7 @@ public inline fun BooleanArray.asList(): List<Boolean> {
* Returns a [List] that wraps the original array.
*/
public inline fun CharArray.asList(): List<Char> {
return this.unsafeCast<Array<Char>>().asList()
return this.toTypedArray().asList()
}
/**
@@ -13072,7 +13072,7 @@ public fun BooleanArray.copyOf(newSize: Int): BooleanArray {
* Returns new array which is a copy of the original array, resized to the given [newSize].
*/
public fun CharArray.copyOf(newSize: Int): CharArray {
return arrayCopyResize(this, newSize, '\u0000')
return arrayCopyResize(this, newSize, 0)
}
/**
@@ -13494,7 +13494,7 @@ public fun BooleanArray.toTypedArray(): Array<Boolean> {
* Returns a *typed* object array containing all of the elements of this primitive array.
*/
public fun CharArray.toTypedArray(): Array<Char> {
return copyOf().unsafeCast<Array<Char>>()
return Array<Char>(size, { i -> this[i] })
}
/**
+13 -2
View File
@@ -23,7 +23,7 @@ Kotlin.toByte = function (a) {
};
Kotlin.toChar = function (a) {
return String.fromCharCode((((a | 0) % 65536) & 0xFFFF) << 16 >>> 16);
return a & 0xFFFF;
};
Kotlin.numberToLong = function (a) {
@@ -48,4 +48,15 @@ Kotlin.numberToDouble = function (a) {
Kotlin.numberToChar = function (a) {
return Kotlin.toChar(Kotlin.numberToInt(a));
};
};
Kotlin.toBoxedChar = function (a) {
if (a == null) return a;
if (a instanceof Kotlin.BoxedChar) return a;
return new Kotlin.BoxedChar(a);
};
Kotlin.unboxChar = function(a) {
if (a == null) return a;
return Kotlin.toChar(a);
};
+2 -2
View File
@@ -34,11 +34,11 @@ Kotlin.primitiveCompareTo = function (a, b) {
};
Kotlin.charInc = function (value) {
return String.fromCharCode(value.charCodeAt(0)+1);
return Kotlin.toChar(value+1);
};
Kotlin.charDec = function (value) {
return String.fromCharCode(value.charCodeAt(0)-1);
return Kotlin.toChar(value-1);
};
Kotlin.imul = Math.imul || imul;
+1 -1
View File
@@ -128,7 +128,7 @@ Kotlin.isNumber = function (a) {
};
Kotlin.isChar = function (value) {
return (typeof value) == "string" && value.length == 1;
return value instanceof Kotlin.BoxedChar
};
Kotlin.isComparable = function (value) {
@@ -19,6 +19,8 @@ package org.jetbrains.kotlin.js.translate.callTranslator
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
import org.jetbrains.kotlin.js.backend.ast.JsBlock
import org.jetbrains.kotlin.js.backend.ast.JsConditional
import org.jetbrains.kotlin.js.backend.ast.JsExpression
@@ -26,6 +28,7 @@ import org.jetbrains.kotlin.js.backend.ast.JsLiteral
import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.reference.CallArgumentTranslator
import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils.getReceiverParameterForReceiver
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
@@ -83,23 +86,32 @@ fun TranslationContext.getCallInfo(
): FunctionCallInfo {
val argsBlock = JsBlock()
val argumentsInfo = CallArgumentTranslator.translate(resolvedCall, explicitReceivers.extensionOrDispatchReceiver, this, argsBlock)
val explicitReceiversCorrected =
if (!argsBlock.isEmpty && explicitReceivers.extensionOrDispatchReceiver != null) {
val receiverOrThisRef = cacheExpressionIfNeeded(explicitReceivers.extensionOrDispatchReceiver)
var receiverRef = explicitReceivers.extensionReceiver
if (receiverRef != null) {
receiverRef = defineTemporary(explicitReceivers.extensionReceiver!!)
if (!argsBlock.isEmpty && explicitReceivers.extensionOrDispatchReceiver != null) {
val receiverOrThisRef = cacheExpressionIfNeeded(explicitReceivers.extensionOrDispatchReceiver)
var receiverRef = explicitReceivers.extensionReceiver
if (receiverRef != null) {
receiverRef = defineTemporary(explicitReceivers.extensionReceiver!!)
}
ExplicitReceivers(receiverOrThisRef, receiverRef)
}
else {
explicitReceivers
}
ExplicitReceivers(receiverOrThisRef, receiverRef)
}
else {
explicitReceivers
}
this.addStatementsToCurrentBlockFrom(argsBlock)
val callInfo = createCallInfo(resolvedCall, explicitReceiversCorrected)
return FunctionCallInfo(callInfo, argumentsInfo)
}
private fun boxIfNeedeed(v: ReceiverValue?, d: ReceiverParameterDescriptor?, r: JsExpression?): JsExpression? {
if (r != null && v != null && KotlinBuiltIns.isCharOrNullableChar(v.type) &&
(d == null || !KotlinBuiltIns.isCharOrNullableChar(d.type))) {
return JsAstUtils.charToBoxedChar(r)
}
return r
}
private fun TranslationContext.getDispatchReceiver(receiverValue: ReceiverValue): JsExpression {
return getDispatchReceiver(getReceiverParameterForReceiver(receiverValue))
}
@@ -154,6 +166,15 @@ private fun TranslationContext.createCallInfo(
}
}
dispatchReceiver = boxIfNeedeed(resolvedCall.dispatchReceiver,
resolvedCall.candidateDescriptor.dispatchReceiverParameter,
dispatchReceiver)
extensionReceiver = boxIfNeedeed(resolvedCall.extensionReceiver,
resolvedCall.candidateDescriptor.extensionReceiverParameter,
extensionReceiver)
return object : AbstractCallInfo(), CallInfo {
override val context: TranslationContext = this@createCallInfo
override val resolvedCall: ResolvedCall<out CallableDescriptor> = resolvedCall
@@ -165,7 +186,8 @@ private fun TranslationContext.createCallInfo(
override fun constructSafeCallIfNeeded(result: JsExpression): JsExpression {
if (notNullConditionalForSafeCall == null) {
return result
} else {
}
else {
notNullConditionalForSafeCall.thenExpression = result
return notNullConditionalForSafeCall
}
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.js.translate.expression;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.js.backend.ast.JsExpression;
import org.jetbrains.kotlin.js.backend.ast.JsName;
import org.jetbrains.kotlin.js.backend.ast.JsNameRef;
@@ -34,6 +35,7 @@ import org.jetbrains.kotlin.psi.KtDestructuringDeclarationEntry;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.BindingContextUtils;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.types.KotlinType;
import java.util.ArrayList;
import java.util.List;
@@ -94,6 +96,11 @@ public class DestructuringDeclarationTranslator extends AbstractTranslator {
setInlineCallMetadata(entryInitializer, entry, entryInitCall, context());
}
KotlinType returnType = candidateDescriptor.getReturnType();
if (returnType != null && KotlinBuiltIns.isCharOrNullableChar(returnType) && !KotlinBuiltIns.isCharOrNullableChar(descriptor.getType())) {
entryInitializer = JsAstUtils.charToBoxedChar(entryInitializer);
}
JsName name = context().getNameForDescriptor(descriptor);
if (isVarCapturedInClosure(context().bindingContext(), descriptor)) {
JsNameRef alias = getCapturedVarAccessor(name.makeRef());
@@ -21,6 +21,7 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
import org.jetbrains.kotlin.descriptors.annotations.KotlinRetention;
@@ -35,9 +36,7 @@ import org.jetbrains.kotlin.js.translate.general.TranslatorVisitor;
import org.jetbrains.kotlin.js.translate.operation.BinaryOperationTranslator;
import org.jetbrains.kotlin.js.translate.operation.UnaryOperationTranslator;
import org.jetbrains.kotlin.js.translate.reference.*;
import org.jetbrains.kotlin.js.translate.utils.BindingUtils;
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
import org.jetbrains.kotlin.js.translate.utils.UtilsKt;
import org.jetbrains.kotlin.js.translate.utils.*;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt;
import org.jetbrains.kotlin.resolve.BindingContext;
@@ -48,6 +47,7 @@ import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant;
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
import org.jetbrains.kotlin.resolve.inline.InlineUtil;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.expressions.DoubleColonLHS;
import java.util.ArrayList;
@@ -133,6 +133,13 @@ public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
else {
JsExpression jsReturnExpression = translateAsExpression(returned, context);
KotlinType returnedType = context.bindingContext().getType(returned);
if (KotlinBuiltIns.isCharOrNullableChar(returnedType) &&
TranslationUtils.shouldBoxReturnValue((CallableDescriptor)context.getDeclarationDescriptor())) {
jsReturnExpression = JsAstUtils.charToBoxedChar(jsReturnExpression);
}
jsReturn = new JsReturn(jsReturnExpression);
}
@@ -82,10 +82,15 @@ public final class PatternTranslator extends AbstractTranslator {
KtExpression left = expression.getLeft();
JsExpression expressionToCast = Translation.translateAsExpression(left, context());
TemporaryVariable temporary = context().declareTemporary(expressionToCast);
KtTypeReference typeReference = expression.getRight();
assert typeReference != null: "Cast expression must have type reference";
KotlinType leftType = context().bindingContext().getType(left);
if (leftType != null && KotlinBuiltIns.isChar(leftType)) {
expressionToCast = JsAstUtils.charToBoxedChar(expressionToCast);
}
TemporaryVariable temporary = context().declareTemporary(expressionToCast);
JsExpression isCheck = translateIsCheck(temporary.assignmentExpression(), typeReference);
if (isCheck == null) return expressionToCast;
@@ -99,15 +104,29 @@ public final class PatternTranslator extends AbstractTranslator {
onFail = new JsInvocation(throwCCEFunRef);
}
return new JsConditional(isCheck, temporary.reference(), onFail);
JsExpression result = new JsConditional(isCheck, temporary.reference(), onFail);
KotlinType expressionType = context().bindingContext().getType(expression);
if (expressionType != null && KotlinBuiltIns.isCharOrNullableChar(expressionType)) {
result = JsAstUtils.boxedCharToChar(result);
}
return result;
}
@NotNull
public JsExpression translateIsExpression(@NotNull KtIsExpression expression) {
JsExpression left = Translation.translateAsExpression(expression.getLeftHandSide(), context());
KtExpression left = expression.getLeftHandSide();
JsExpression expressionToCheck = Translation.translateAsExpression(left, context());
KotlinType leftType = context().bindingContext().getType(left);
if (leftType != null && KotlinBuiltIns.isChar(leftType)) {
expressionToCheck = JsAstUtils.charToBoxedChar(expressionToCheck);
}
KtTypeReference typeReference = expression.getTypeReference();
assert typeReference != null;
JsExpression result = translateIsCheck(left, typeReference);
JsExpression result = translateIsCheck(expressionToCheck, typeReference);
if (result == null) return JsLiteral.getBoolean(!expression.isNegated());
if (expression.isNegated()) {
@@ -23,12 +23,14 @@ import org.jetbrains.kotlin.js.backend.ast.JsNumberLiteral;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.js.descriptorUtils.DescriptorUtilsKt;
import org.jetbrains.kotlin.js.patterns.NamePredicate;
import org.jetbrains.kotlin.js.translate.context.TranslationContext;
import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
import org.jetbrains.kotlin.js.translate.general.Translation;
import org.jetbrains.kotlin.js.translate.intrinsic.functions.factories.TopLevelFIF;
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.types.KotlinType;
@@ -82,15 +84,20 @@ public final class StringTemplateTranslator extends AbstractTranslator {
assert entryExpression != null :
"JetStringTemplateEntryWithExpression must have not null entry expression.";
JsExpression translatedExpression = Translation.translateAsExpression(entryExpression, context());
KotlinType type = context().bindingContext().getType(entryExpression);
if (translatedExpression instanceof JsNumberLiteral) {
append(context().program().getStringLiteral(translatedExpression.toString()));
return;
}
KotlinType type = context().bindingContext().getType(entryExpression);
if (type == null || type.isMarkedNullable()) {
append(TopLevelFIF.TO_STRING.apply((JsExpression) null, new SmartList<JsExpression>(translatedExpression), context()));
}
else if (KotlinBuiltIns.isChar(type)) {
append(JsAstUtils.charToString(translatedExpression));
}
else if (mustCallToString(type)) {
append(new JsInvocation(new JsNameRef("toString", translatedExpression)));
}
@@ -154,7 +154,7 @@ public final class Translation {
return context.program().getStringLiteral((String) value);
}
if (value instanceof Character) {
return context.program().getStringLiteral(value.toString());
return context.program().getNumberLiteral(((Character) value).charValue());
}
return null;
@@ -189,6 +189,11 @@ public final class Translation {
) {
JsNode jsNode = translateExpression(expression, context, block);
if (jsNode instanceof JsExpression) {
KotlinType expressionType = context.bindingContext().getType(expression);
if (expressionType != null && KotlinBuiltIns.isCharOrNullableChar(expressionType) &&
(jsNode instanceof JsInvocation || jsNode instanceof JsNameRef || jsNode instanceof JsArrayAccess)) {
jsNode = JsAstUtils.boxedCharToChar((JsExpression) jsNode);
}
return (JsExpression) jsNode;
}
@@ -126,7 +126,7 @@ public final class ArrayFIF extends CompositeFIF {
add(pattern(ARRAYS, "iterator"), new KotlinFunctionIntrinsic("arrayIterator"));
add(pattern(NUMBER_ARRAY, "<init>(Int)"), new KotlinFunctionIntrinsic("newArray", JsNumberLiteral.ZERO));
add(pattern(CHAR_ARRAY, "<init>(Int)"), new KotlinFunctionIntrinsic("newArray", JsStringLiteral.createCharZero()));
add(pattern(CHAR_ARRAY, "<init>(Int)"), new KotlinFunctionIntrinsic("newArray", JsNumberLiteral.ZERO));
add(pattern(BOOLEAN_ARRAY, "<init>(Int)"), new KotlinFunctionIntrinsic("newArray", JsLiteral.FALSE));
add(pattern(LONG_ARRAY, "<init>(Int)"), new KotlinFunctionIntrinsic("newArray", new JsNameRef(Namer.LONG_ZERO, Namer.kotlinLong())));
@@ -38,6 +38,7 @@ import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.types.expressions.OperatorConventions;
import org.jetbrains.kotlin.util.OperatorNameConventions;
import java.util.Collections;
import java.util.List;
import static org.jetbrains.kotlin.js.patterns.PatternBuilder.pattern;
@@ -182,6 +183,9 @@ public enum PrimitiveBinaryOperationFIF implements FunctionIntrinsicFactory {
if (pattern("Char.minus(Char)").apply(descriptor)) {
return new CharAndCharBinaryOperationFunctionIntrinsic(result);
}
if (pattern("String.plus(Any)").apply(descriptor)) {
return new StringAndCharBinaryOperationFunctionIntrinsic(result);
}
return result;
}
@@ -283,7 +287,7 @@ public enum PrimitiveBinaryOperationFIF implements FunctionIntrinsicFactory {
@NotNull
@Override
public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) {
return JsAstUtils.toChar(functionIntrinsic.doApply(JsAstUtils.charToInt(left), right, context));
return JsAstUtils.toChar(functionIntrinsic.doApply(left, right, context));
}
}
@@ -299,7 +303,22 @@ public enum PrimitiveBinaryOperationFIF implements FunctionIntrinsicFactory {
@NotNull
@Override
public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) {
return functionIntrinsic.doApply(JsAstUtils.charToInt(left), JsAstUtils.charToInt(right), context);
return functionIntrinsic.doApply(left, right, context);
}
}
private static class StringAndCharBinaryOperationFunctionIntrinsic extends BinaryOperationIntrinsicBase {
@NotNull
private final BinaryOperationIntrinsicBase functionIntrinsic;
private StringAndCharBinaryOperationFunctionIntrinsic(@NotNull BinaryOperationIntrinsicBase functionIntrinsic) {
this.functionIntrinsic = functionIntrinsic;
}
@NotNull
@Override
public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) {
return functionIntrinsic.doApply(left, TopLevelFIF.TO_STRING.apply(right, Collections.<JsExpression>emptyList(), context), context);
}
}
}
@@ -163,11 +163,24 @@ public final class TopLevelFIF extends CompositeFIF {
@NotNull
public static final KotlinFunctionIntrinsic TO_STRING = new KotlinFunctionIntrinsic("toString");
@NotNull
public static final FunctionIntrinsic CHAR_TO_STRING = new FunctionIntrinsic() {
@NotNull
@Override
public JsExpression apply(
@NotNull CallInfo callInfo, @NotNull List<? extends JsExpression> arguments, @NotNull TranslationContext context
) {
return JsAstUtils.charToString(callInfo.getDispatchReceiver());
}
};
@NotNull
public static final FunctionIntrinsicFactory INSTANCE = new TopLevelFIF();
private TopLevelFIF() {
add(EQUALS_IN_ANY, KOTLIN_ANY_EQUALS);
add(pattern("Char.toString"), CHAR_TO_STRING);
add(pattern("kotlin", "toString").isExtensionOf(FQ_NAMES.any.asString()), TO_STRING);
add(pattern("kotlin", "equals").isExtensionOf(FQ_NAMES.any.asString()), KOTLIN_EQUALS);
add(HASH_CODE_IN_ANY, KOTLIN_HASH_CODE);
@@ -0,0 +1,49 @@
/*
* Copyright 2010-2017 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.kotlin.js.translate.intrinsic.operation
import com.google.common.collect.ImmutableSet
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.js.backend.ast.JsBinaryOperation
import org.jetbrains.kotlin.js.backend.ast.JsExpression
import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.operation.OperatorTable
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.utils.PsiUtils
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.types.KotlinType
object AssignmentBOIF : BinaryOperationIntrinsicFactory {
private object CharAssignmentIntrinsic : AbstractBinaryOperationIntrinsic() {
override fun apply(expression: KtBinaryExpression, left: JsExpression, right: JsExpression, context: TranslationContext): JsExpression {
val operator = OperatorTable.getBinaryOperator(PsiUtils.getOperationToken(expression))
return JsBinaryOperation(operator, left, JsAstUtils.charToBoxedChar(right))
}
}
override fun getSupportTokens() = ImmutableSet.of(KtTokens.EQ)
override fun getIntrinsic(descriptor: FunctionDescriptor, leftType: KotlinType?, rightType: KotlinType?): BinaryOperationIntrinsic? {
if (leftType != null && !KotlinBuiltIns.isCharOrNullableChar(leftType) && rightType != null && KotlinBuiltIns.isCharOrNullableChar(rightType)) {
return CharAssignmentIntrinsic
}
return null
}
}
@@ -82,15 +82,33 @@ object EqualsBOIF : BinaryOperationIntrinsicFactory {
override fun getSupportTokens() = OperatorConventions.EQUALS_OPERATIONS!!
override fun getIntrinsic(descriptor: FunctionDescriptor, leftType: KotlinType?, rightType: KotlinType?): BinaryOperationIntrinsic? =
when {
isEnumIntrinsicApplicable(descriptor, leftType, rightType) -> EnumEqualsIntrinsic
override fun getIntrinsic(descriptor: FunctionDescriptor, leftType: KotlinType?, rightType: KotlinType?): BinaryOperationIntrinsic? {
val result = when {
isEnumIntrinsicApplicable(descriptor, leftType, rightType) -> EnumEqualsIntrinsic
KotlinBuiltIns.isBuiltIn(descriptor) ||
TopLevelFIF.EQUALS_IN_ANY.apply(descriptor) -> EqualsIntrinsic
KotlinBuiltIns.isBuiltIn(descriptor) ||
TopLevelFIF.EQUALS_IN_ANY.apply(descriptor) -> EqualsIntrinsic
else -> null
else -> null
}
if (result != null && leftType != rightType) {
val leftChar = (leftType != null && KotlinBuiltIns.isCharOrNullableChar(leftType))
val rightChar = (rightType != null && KotlinBuiltIns.isCharOrNullableChar(rightType))
if (leftChar xor rightChar) {
return object : AbstractBinaryOperationIntrinsic() {
override fun apply(expression: KtBinaryExpression, left: JsExpression, right: JsExpression, context: TranslationContext): JsExpression {
val maybeBoxedLeft = if (leftChar) JsAstUtils.charToBoxedChar(left) else left
val maybeBoxedRight = if (rightChar) JsAstUtils.charToBoxedChar(right) else right
return result.apply(expression, maybeBoxedLeft, maybeBoxedRight, context)
}
}
}
}
return result
}
private fun isEnumIntrinsicApplicable(descriptor: FunctionDescriptor, leftType: KotlinType?, rightType: KotlinType?): Boolean {
return DescriptorUtils.isEnumClass(descriptor.containingDeclaration) && leftType != null && rightType != null &&
@@ -0,0 +1,73 @@
/*
* Copyright 2010-2016 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.kotlin.js.translate.intrinsic.operation
import com.google.common.collect.ImmutableSet
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.js.backend.ast.JsBinaryOperation
import org.jetbrains.kotlin.js.backend.ast.JsExpression
import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.intrinsic.functions.factories.TopLevelFIF
import org.jetbrains.kotlin.js.translate.operation.OperatorTable
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.utils.PsiUtils.getOperationToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.types.KotlinType
object StringPlusCharBOIF : BinaryOperationIntrinsicFactory {
private object StringPlusCharIntrinsic : AbstractBinaryOperationIntrinsic() {
override fun apply(expression: KtBinaryExpression, left: JsExpression, right: JsExpression, context: TranslationContext): JsExpression {
val operator = OperatorTable.getBinaryOperator(getOperationToken(expression))
return JsBinaryOperation(operator, left, JsAstUtils.charToString(right))
}
}
private object StringPlusAnyIntrinsic : AbstractBinaryOperationIntrinsic() {
override fun apply(expression: KtBinaryExpression, left: JsExpression, right: JsExpression, context: TranslationContext): JsExpression {
val operator = OperatorTable.getBinaryOperator(getOperationToken(expression))
return JsBinaryOperation(operator, left, TopLevelFIF.TO_STRING.apply(right, listOf(), context))
}
}
private object StringPlusStringIntrinsic : AbstractBinaryOperationIntrinsic() {
override fun apply(expression: KtBinaryExpression, left: JsExpression, right: JsExpression, context: TranslationContext): JsExpression {
val operator = OperatorTable.getBinaryOperator(getOperationToken(expression))
return JsBinaryOperation(operator, left, right)
}
}
override fun getSupportTokens() = ImmutableSet.of(KtTokens.PLUS)
override fun getIntrinsic(descriptor: FunctionDescriptor, leftType: KotlinType?, rightType: KotlinType?): BinaryOperationIntrinsic? {
if (KotlinBuiltIns.isString(leftType) && rightType != null) {
if (KotlinBuiltIns.isCharOrNullableChar(rightType)) {
return StringPlusCharIntrinsic
}
if (KotlinBuiltIns.isStringOrNullableString(rightType)) {
return StringPlusStringIntrinsic
}
if (KotlinBuiltIns.isAnyOrNullableAny(rightType)) {
return StringPlusAnyIntrinsic
}
}
return null
}
}
@@ -37,7 +37,7 @@ class BinaryOperationIntrinsics {
private val intrinsicCache = mutableMapOf<IntrinsicKey, BinaryOperationIntrinsic>()
private val factories = listOf(LongCompareToBOIF, EqualsBOIF, CompareToBOIF)
private val factories = listOf(LongCompareToBOIF, EqualsBOIF, CompareToBOIF, StringPlusCharBOIF, AssignmentBOIF)
fun getIntrinsic(expression: KtBinaryExpression, context: TranslationContext): BinaryOperationIntrinsic {
val token = getOperationToken(expression)
@@ -59,8 +59,7 @@ public abstract class AssignmentTranslator extends AbstractTranslator {
super(context);
this.expression = expression;
this.isVariableReassignment = isVariableReassignment(context.bindingContext(), expression);
KtExpression left = expression.getLeft();
assert left != null : "No left-hand side: " + expression.getText();
assert expression.getLeft() != null : "No left-hand side: " + expression.getText();
}
protected final AccessTranslator createAccessTranslator(@NotNull KtExpression left, boolean forceOrderOfEvaluation) {
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.js.translate.operation;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.js.backend.ast.JsBinaryOperation;
import org.jetbrains.kotlin.js.backend.ast.JsBinaryOperator;
import org.jetbrains.kotlin.js.backend.ast.JsBlock;
@@ -23,16 +24,18 @@ import org.jetbrains.kotlin.js.backend.ast.JsExpression;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.js.translate.context.TranslationContext;
import org.jetbrains.kotlin.js.translate.reference.AccessTranslator;
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils;
import org.jetbrains.kotlin.lexer.KtSingleValueToken;
import org.jetbrains.kotlin.lexer.KtToken;
import org.jetbrains.kotlin.psi.KtBinaryExpression;
import org.jetbrains.kotlin.psi.KtExpression;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.expressions.OperatorConventions;
import static org.jetbrains.kotlin.js.translate.utils.PsiUtils.getOperationToken;
import static org.jetbrains.kotlin.js.translate.utils.PsiUtils.isAssignment;
import static org.jetbrains.kotlin.js.translate.utils.TranslationUtils.isSimpleNameExpressionNotDelegatedLocalVar;
import static org.jetbrains.kotlin.js.translate.utils.TranslationUtils.translateRightExpression;
public final class IntrinsicAssignmentTranslator extends AssignmentTranslator {
private final JsExpression right;
@@ -48,13 +51,28 @@ public final class IntrinsicAssignmentTranslator extends AssignmentTranslator {
private IntrinsicAssignmentTranslator(@NotNull KtBinaryExpression expression, @NotNull TranslationContext context) {
super(expression, context);
right = translateRightExpression(context, expression, rightBlock);
right = translateRightExpression(context, expression);
rightExpressionTrivial = rightBlock.isEmpty();
KtExpression left = expression.getLeft();
assert left != null;
accessTranslator = createAccessTranslator(left, !rightExpressionTrivial);
}
private JsExpression translateRightExpression(TranslationContext context, KtBinaryExpression expression) {
JsExpression result = TranslationUtils.translateRightExpression(context, expression, rightBlock);
KotlinType leftType = context.bindingContext().getType(expression.getLeft());
KotlinType rightType = context.bindingContext().getType(expression.getRight());
if (rightType != null && KotlinBuiltIns.isCharOrNullableChar(rightType)) {
if (leftType != null && KotlinBuiltIns.isStringOrNullableString(leftType)) {
result = JsAstUtils.charToString(result);
}
else if (leftType == null || !KotlinBuiltIns.isCharOrNullableChar(leftType)) {
result = JsAstUtils.charToBoxedChar(result);
}
}
return result;
}
@NotNull
private JsExpression translate() {
if (isAssignment(getOperationToken(expression))) {
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.SideEffectKind
import org.jetbrains.kotlin.js.backend.ast.metadata.sideEffects
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.js.translate.context.Namer
import org.jetbrains.kotlin.js.translate.context.TemporaryConstVariable
import org.jetbrains.kotlin.js.translate.context.TranslationContext
@@ -31,6 +32,7 @@ import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils
import org.jetbrains.kotlin.js.translate.utils.getReferenceToJsClass
import org.jetbrains.kotlin.psi.Call
import org.jetbrains.kotlin.psi.ValueArgument
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
@@ -153,11 +155,29 @@ class CallArgumentTranslator private constructor(
context: TranslationContext,
resolvedCall: ResolvedCall<*>
): Map<ValueArgument, JsExpression> {
val argsToParameters = resolvedCall.valueArguments
.flatMap { (param, args) -> args.arguments.map { param to it } }
.associate { (param, arg) -> arg to param }
val argumentContexts = resolvedCall.call.valueArguments.associate { it to context.innerBlock() }
var result = resolvedCall.call.valueArguments.associate { arg ->
val argumentContext = argumentContexts[arg]!!
val jsExpr = Translation.translateAsExpression(arg.getArgumentExpression()!!, argumentContext)
val parenthisedArgumentExpression = arg.getArgumentExpression()
val param = argsToParameters[arg]!!.original
val parameterType = param.varargElementType ?: param.type
val argType = context.bindingContext().getType(parenthisedArgumentExpression!!)
val argJs = Translation.translateAsExpression(parenthisedArgumentExpression, argumentContext)
val jsExpr = if (argType != null && KotlinBuiltIns.isCharOrNullableChar(argType) &&
(!KotlinBuiltIns.isCharOrNullableChar(parameterType) || resolvedCall.call.callType == Call.CallType.INVOKE)) {
JsAstUtils.charToBoxedChar(argJs)
}
else {
argJs
}
arg to jsExpr
}
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.js.translate.utils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.descriptors.CallableDescriptor;
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
import org.jetbrains.kotlin.descriptors.FunctionDescriptor;
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor;
@@ -33,6 +34,7 @@ import org.jetbrains.kotlin.js.translate.utils.mutator.Mutator;
import org.jetbrains.kotlin.psi.KtDeclarationWithBody;
import org.jetbrains.kotlin.psi.KtExpression;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.TypeUtils;
import java.util.ArrayList;
import java.util.HashMap;
@@ -112,8 +114,6 @@ public final class FunctionBodyTranslator extends AbstractTranslator {
assert jetBodyExpression != null : "Cannot translate a body of an abstract function.";
JsBlock jsBlock = new JsBlock();
KotlinType returnType = descriptor.getReturnType();
assert returnType != null;
JsNode jsBody = Translation.translateExpression(jetBodyExpression, context(), jsBlock);
jsBlock.getStatements().addAll(mayBeWrapWithReturn(jsBody).getStatements());
@@ -143,6 +143,13 @@ public final class FunctionBodyTranslator extends AbstractTranslator {
if (!(node instanceof JsExpression)) {
return node;
}
KotlinType bodyType = context().bindingContext().getType(declaration.getBodyExpression());
if (bodyType == null && KotlinBuiltIns.isCharOrNullableChar(descriptor.getReturnType()) ||
bodyType != null && KotlinBuiltIns.isCharOrNullableChar(bodyType) && TranslationUtils.shouldBoxReturnValue(descriptor)) {
node = JsAstUtils.charToBoxedChar((JsExpression) node);
}
JsReturn jsReturn = new JsReturn((JsExpression)node);
MetadataProperties.setReturnTarget(jsReturn, descriptor);
return jsReturn;
@@ -149,7 +149,36 @@ public final class JsAstUtils {
@NotNull
public static JsExpression charToInt(@NotNull JsExpression expression) {
return invokeMethod(expression, "charCodeAt", JsNumberLiteral.ZERO);
return toInt32(expression);
}
@NotNull
public static JsExpression charToString(@NotNull JsExpression expression) {
return new JsInvocation(new JsNameRef("fromCharCode", new JsNameRef("String")), expression);
}
@NotNull
public static JsExpression charToBoxedChar(@NotNull JsExpression expression) {
return withBoxingMetadata(invokeKotlinFunction("toBoxedChar", unnestBoxing(expression)));
}
@NotNull
public static JsExpression boxedCharToChar(@NotNull JsExpression expression) {
return withBoxingMetadata(invokeKotlinFunction("unboxChar", unnestBoxing(expression)));
}
@NotNull
private static JsExpression unnestBoxing(@NotNull JsExpression expression) {
if (expression instanceof JsInvocation && MetadataProperties.getBoxing((JsInvocation) expression)) {
return ((JsInvocation) expression).getArguments().get(0);
}
return expression;
}
@NotNull
private static JsInvocation withBoxingMetadata(@NotNull JsInvocation call) {
MetadataProperties.setBoxing(call, true);
return call;
}
@NotNull
@@ -36,12 +36,14 @@ import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.BindingContextUtils;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
import org.jetbrains.kotlin.resolve.inline.InlineUtil;
import org.jetbrains.kotlin.serialization.deserialization.FindClassInModuleKt;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.TypeUtils;
import java.util.ArrayList;
import java.util.List;
@@ -196,6 +198,13 @@ public final class TranslationUtils {
KtExpression initializer = declaration.getInitializer();
if (initializer != null) {
jsInitExpression = Translation.translateAsExpression(initializer, context);
KotlinType propertyType = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.VARIABLE, declaration).getType();
KotlinType initType = context.bindingContext().getType(initializer);
if (initType != null && KotlinBuiltIns.isCharOrNullableChar(initType) && !KotlinBuiltIns.isCharOrNullableChar(propertyType)) {
jsInitExpression = JsAstUtils.charToBoxedChar(jsInitExpression);
}
}
return jsInitExpression;
}
@@ -399,4 +408,19 @@ public final class TranslationUtils {
descriptor.getContainingDeclaration() instanceof ClassDescriptor &&
ModalityKt.isOverridable(descriptor);
}
private static boolean overridesReturnAny(CallableDescriptor c) {
KotlinType returnType = c.getOriginal().getReturnType();
assert returnType != null;
if (KotlinBuiltIns.isAnyOrNullableAny(returnType) || TypeUtils.isTypeParameter(returnType)) return true;
for (CallableDescriptor o : c.getOverriddenDescriptors()) {
if (overridesReturnAny(o)) return true;
}
return false;
}
public static boolean shouldBoxReturnValue(CallableDescriptor c) {
return overridesReturnAny(c) || c instanceof CallableMemberDescriptor && ModalityKt.isOverridable((CallableMemberDescriptor)c);
}
}
@@ -1,3 +1,4 @@
package foo
class Fail(message: String) : Exception(message)
@@ -22,7 +23,7 @@ fun box(): String {
try {
test("arrayOf", arrayOf(0, 1, 2, 3, 4), "0, 1, 2, 3, 4")
test("booleanArrayOf", booleanArrayOf(true, false, false, true, true), "true, false, false, true, true")
test("charArray'", charArrayOf('0', '1', '2', '3', '4'), "'0', '1', '2', '3', '4'")
test("charArray'", charArrayOf('0', '1', '2', '3', '4'), "48, 49, 50, 51, 52")
test("byteArrayOf", byteArrayOf(0, 1, 2, 3, 4), "0, 1, 2, 3, 4")
test("shortArrayOf", shortArrayOf(0, 1, 2, 3, 4), "0, 1, 2, 3, 4")
test("intArray,", intArrayOf(0, 1, 2, 3, 4), "0, 1, 2, 3, 4")
@@ -66,7 +66,7 @@ fun box(): String {
val mapWithCharKeys = HashMap<Char, Int>()
mapWithCharKeys['A'] = 1
assertEquals("string", jsTypeOf (mapWithCharKeys.keys.iterator().next()), "mapWithCharKeys")
assertEquals("object", jsTypeOf (mapWithCharKeys.keys.iterator().next()), "mapWithCharKeys")
val mapWithLongKeys = HashMap<Long, Int>()
mapWithLongKeys[1L] = 1
@@ -52,7 +52,7 @@ fun box(): String {
val charSet = HashSet<Char>()
charSet.add('A')
assertEquals("string", jsTypeOf (charSet.iterator().next()), "charSet")
assertEquals("object", jsTypeOf (charSet.iterator().next()), "charSet")
val longSet = HashSet<Long>()
longSet.add(1L)
+3 -2
View File
@@ -2,10 +2,11 @@
<html>
<head>
<script type="application/javascript" src="../../../dist/js/kotlin.js"></script>
<script type="application/javascript" src="out/codegen/boxInline/callableReference/bound/topLevelExtensionProperty_v5.js"></script>
<script type="application/javascript" src="../../../dist/classes/kotlin-test-js/kotlin-test.js"></script>
<script type="application/javascript" src="../../../js/js.translator/testData/out/box/standardClasses/stringBuilder_v5.js"></script>
<script type="application/javascript">
console.log(JS_TESTS.box());
console.log(JS_TESTS.foo.box());
</script>
</head>
<body>
@@ -228,10 +228,11 @@ class ArraysTest {
assertEquals(arr.asList().toString(), arr.contentToString())
}
@Test fun contentDeepToString() {
val arr = arrayOf("aa", 1, null, charArrayOf('d'))
assertEquals("[aa, 1, null, [d]]", arr.contentDeepToString())
}
// @Ignore("KT-16056")
// @Test fun contentDeepToString() {
// val arr = arrayOf("aa", 1, null, charArrayOf('d'))
// assertEquals("[aa, 1, null, [d]]", arr.contentDeepToString())
// }
@Test fun contentDeepToStringNoRecursion() {
// a[b[a, b]]