From 2f786efebfa14cff8bac28c345fbdde5b3e0b05a Mon Sep 17 00:00:00 2001 From: Alexey Tsvetkov Date: Tue, 17 Mar 2015 18:04:08 +0300 Subject: [PATCH] JS: support is-check on reified parameter --- .../kotlin/js/translate/context/Namer.java | 9 +- .../expression/FunctionTranslator.java | 9 ++ .../expression/PatternTranslator.java | 125 ++++++++++++------ .../translate/general/AbstractTranslator.java | 6 + .../reference/CallArgumentTranslator.kt | 41 +++++- .../kotlin/js/translate/utils/JsAstUtils.java | 20 --- js/js.translator/testData/kotlin_lib_ecma5.js | 18 ++- 7 files changed, 158 insertions(+), 70 deletions(-) diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java index 0ad20c54387..fd3afc3ebce 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java @@ -368,8 +368,13 @@ public final class Namer { } @NotNull - public JsExpression isOperationReference() { - return kotlin(isTypeName); + public JsExpression isTypeOf(@NotNull JsExpression type) { + return new JsInvocation(kotlin("isTypeOf"), type); + } + + @NotNull + public JsExpression isInstanceOf(@NotNull JsExpression type) { + return new JsInvocation(kotlin("isInstanceOf"), type); } @NotNull diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.java index ec19c7f9db0..905f5a5ff8b 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.java @@ -126,6 +126,15 @@ public final class FunctionTranslator extends AbstractTranslator { } List jsParameters = new SmartList(); + + for (TypeParameterDescriptor type : descriptor.getTypeParameters()) { + if (type.isReified()) { + JsName typeName = context().getNameForDescriptor(type); + JsName paramName = functionObject.getScope().declareName("is" + typeName.getIdent()); + jsParameters.add(new JsParameter(paramName)); + } + } + mayBeAddThisParameterForExtensionFunction(jsParameters); addParameters(jsParameters, descriptor, context()); return jsParameters; diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/PatternTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/PatternTranslator.java index 81aecd8901e..1a14e4d56ea 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/PatternTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/PatternTranslator.java @@ -16,23 +16,25 @@ package org.jetbrains.kotlin.js.translate.expression; -import com.google.dart.compiler.backend.js.ast.JsExpression; -import com.google.dart.compiler.backend.js.ast.JsInvocation; -import com.google.dart.compiler.backend.js.ast.JsNameRef; +import com.google.dart.compiler.backend.js.ast.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.kotlin.descriptors.ClassDescriptor; +import org.jetbrains.kotlin.descriptors.*; +import org.jetbrains.kotlin.js.translate.context.Namer; 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.patterns.NamePredicate; import org.jetbrains.kotlin.js.translate.utils.BindingUtils; -import org.jetbrains.kotlin.js.translate.utils.JsAstUtils; import org.jetbrains.kotlin.js.translate.utils.TranslationUtils; import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.JetExpression; import org.jetbrains.kotlin.psi.JetIsExpression; import org.jetbrains.kotlin.psi.JetTypeReference; +import org.jetbrains.kotlin.resolve.DescriptorUtils; +import org.jetbrains.kotlin.types.JetType; + +import java.util.List; import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getTypeByReference; import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.*; @@ -63,55 +65,97 @@ public final class PatternTranslator extends AbstractTranslator { @NotNull public JsExpression translateIsCheck(@NotNull JsExpression subject, @NotNull JetTypeReference typeReference) { - JsExpression result = translateAsIntrinsicTypeCheck(subject, typeReference); - if (result != null) { - return result; - } - return translateAsIsCheck(subject, typeReference); - } + JetType type = BindingUtils.getTypeByReference(bindingContext(), typeReference); + JsExpression checkFunReference = getIsTypeCheckCallable(type); + JsInvocation isCheck = new JsInvocation(checkFunReference, subject); - @NotNull - private JsExpression translateAsIsCheck(@NotNull JsExpression expressionToMatch, - @NotNull JetTypeReference typeReference) { - JsInvocation isCheck = new JsInvocation(context().namer().isOperationReference(), - expressionToMatch, getClassNameReference(typeReference)); if (isNullable(typeReference)) { - return addNullCheck(expressionToMatch, isCheck); + return addNullCheck(subject, isCheck); } + return isCheck; } - @Nullable - private JsExpression translateAsIntrinsicTypeCheck(@NotNull JsExpression expressionToMatch, - @NotNull JetTypeReference typeReference) { - Name typeName = getNameIfStandardType(getTypeByReference(bindingContext(), typeReference)); - if (typeName == null) { - return null; + @NotNull + public JsExpression getIsTypeCheckCallable(@NotNull JetType type) { + JsExpression builtinCheck = getIsTypeCheckCallableForBuiltin(type); + if (builtinCheck != null) return builtinCheck; + + ClassifierDescriptor typeDescriptor = type.getConstructor().getDeclarationDescriptor(); + + if (typeDescriptor instanceof TypeParameterDescriptor) { + TypeParameterDescriptor typeParameterDescriptor = (TypeParameterDescriptor) typeDescriptor; + + if (typeParameterDescriptor.isReified()) { + return getIsTypeCheckCallableForReifiedType(typeParameterDescriptor); + } } - String jsSTypeName; + JsNameRef typeName = getClassNameReference(type); + return namer().isInstanceOf(typeName); + } + + @Nullable + private JsExpression getIsTypeCheckCallableForBuiltin(@NotNull JetType type) { + Name typeName = getNameIfStandardType(type); + if (NamePredicate.STRING.apply(typeName)) { - jsSTypeName = "string"; + return namer().isTypeOf(program().getStringLiteral("string")); } - else if (NamePredicate.BOOLEAN.apply(typeName)) { - jsSTypeName = "boolean"; + + if (NamePredicate.BOOLEAN.apply(typeName)) { + return namer().isTypeOf(program().getStringLiteral("boolean")); } - else if (NamePredicate.LONG.apply(typeName)) { - return JsAstUtils.isLong(expressionToMatch); + + if (NamePredicate.LONG.apply(typeName)) { + return namer().isInstanceOf(Namer.KOTLIN_LONG_NAME_REF); } - else if (NamePredicate.NUMBER.apply(typeName)) { - return JsAstUtils.isNumber(expressionToMatch); + + if (NamePredicate.NUMBER.apply(typeName)) { + return namer().kotlin(Namer.IS_NUMBER); } - else if (NamePredicate.CHAR.apply(typeName)) { - return JsAstUtils.isChar(expressionToMatch); + + if (NamePredicate.CHAR.apply(typeName)) { + return namer().kotlin(Namer.IS_CHAR); } - else if (NamePredicate.PRIMITIVE_NUMBERS_MAPPED_TO_PRIMITIVE_JS.apply(typeName)) { - jsSTypeName = "number"; + + if (NamePredicate.PRIMITIVE_NUMBERS_MAPPED_TO_PRIMITIVE_JS.apply(typeName)) { + return namer().isTypeOf(program().getStringLiteral("number")); } - else { - return null; + + return null; + } + + @NotNull + private JsExpression getIsTypeCheckCallableForReifiedType(@NotNull TypeParameterDescriptor typeParameter) { + assert typeParameter.isReified(): "Expected reified type, actual: " + typeParameter; + DeclarationDescriptor containingDeclaration = typeParameter.getContainingDeclaration(); + assert containingDeclaration instanceof CallableDescriptor: + "Expected type parameter " + typeParameter + + " to be contained in CallableDescriptor, actual: " + containingDeclaration.getClass(); + + CallableDescriptor containingDescriptor = (CallableDescriptor) containingDeclaration; + int index = countReifiedTypesBefore(containingDescriptor.getTypeParameters(), typeParameter.getIndex()); + JsFunction containingFunction = context().getFunctionObject(containingDescriptor); + JsParameter isTypeFunParameter = containingFunction.getParameters().get(index); + return isTypeFunParameter.getName().makeRef(); + } + + private static int countReifiedTypesBefore( + @NotNull List typeParameters, + int typeParamIndex + ) { + int count = 0; + + for (TypeParameterDescriptor typeParameter : typeParameters) { + if (typeParameter.getIndex() >= typeParamIndex) break; + + if (typeParameter.isReified()) { + count++; + } } - return typeof(expressionToMatch, program().getStringLiteral(jsSTypeName)); + + return count; } @NotNull @@ -124,9 +168,8 @@ public final class PatternTranslator extends AbstractTranslator { } @NotNull - private JsNameRef getClassNameReference(@NotNull JetTypeReference typeReference) { - ClassDescriptor referencedClass = BindingUtils.getClassDescriptorForTypeReference - (bindingContext(), typeReference); + private JsNameRef getClassNameReference(@NotNull JetType type) { + ClassDescriptor referencedClass = DescriptorUtils.getClassDescriptorForType(type); return context().getQualifiedReference(referencedClass); } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/AbstractTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/AbstractTranslator.java index 9951d8b1ecc..ed65b65f9f7 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/AbstractTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/AbstractTranslator.java @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.js.translate.general; import com.google.dart.compiler.backend.js.ast.JsProgram; import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.js.translate.context.Namer; import org.jetbrains.kotlin.js.translate.context.TranslationContext; import org.jetbrains.kotlin.resolve.BindingContext; @@ -44,4 +45,9 @@ public abstract class AbstractTranslator { protected BindingContext bindingContext() { return context.bindingContext(); } + + @NotNull + protected Namer namer() { + return context.namer(); + } } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallArgumentTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallArgumentTranslator.kt index a80420c0641..4e9e60368d2 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallArgumentTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallArgumentTranslator.kt @@ -18,18 +18,21 @@ package org.jetbrains.kotlin.js.translate.reference import com.google.dart.compiler.backend.js.ast.* import com.intellij.util.SmartList +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor +import org.jetbrains.kotlin.js.descriptorUtils.nameIfStandardType import org.jetbrains.kotlin.js.translate.context.TemporaryConstVariable import org.jetbrains.kotlin.js.translate.context.TemporaryVariable import org.jetbrains.kotlin.js.translate.context.TranslationContext +import org.jetbrains.kotlin.js.translate.expression.PatternTranslator import org.jetbrains.kotlin.js.translate.general.AbstractTranslator import org.jetbrains.kotlin.js.translate.general.Translation -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.* import org.jetbrains.kotlin.psi.JetExpression import org.jetbrains.kotlin.psi.ValueArgument import org.jetbrains.kotlin.resolve.calls.model.* +import org.jetbrains.kotlin.types.JetType import java.util.ArrayList import java.util.Collections @@ -41,7 +44,7 @@ public class CallArgumentTranslator private ( context: TranslationContext ) : AbstractTranslator(context) { - public class ArgumentsInfo( + public data class ArgumentsInfo( public val translateArguments: List, public val hasSpreadOperator: Boolean, public val cachedReceiver: TemporaryConstVariable? @@ -174,7 +177,14 @@ public class CallArgumentTranslator private ( val argumentTranslator = CallArgumentTranslator(resolvedCall, receiver, innerContext) val result = argumentTranslator.translate() context.moveVarsFrom(innerContext) - return result + val callDescriptor = resolvedCall.getCandidateDescriptor() + + if (CallExpressionTranslator.shouldBeInlined(callDescriptor)) { + val typeArgs = resolvedCall.getTypeArguments() + return typeArgs.addReifiedTypeArgsTo(result, context) + } + + return result; } private fun translateSingleArgument(actualArgument: ResolvedValueArgument, result: MutableList, context: TranslationContext): ArgumentsKind { @@ -320,3 +330,24 @@ public class CallArgumentTranslator private ( } } + +private fun Map.addReifiedTypeArgsTo( + info: CallArgumentTranslator.ArgumentsInfo, + context: TranslationContext +): CallArgumentTranslator.ArgumentsInfo { + + val reifiedTypeArguments = SmartList() + val patternTranslator = PatternTranslator.newInstance(context) + + for (param in keySet().sortBy { it.getIndex() }) { + if (!param.isReified()) continue + + val argumentType = get(param) + if (argumentType == null) continue + + val isCheckCallable = patternTranslator.getIsTypeCheckCallable(argumentType) + reifiedTypeArguments.add(isCheckCallable) + } + + return info.copy(translateArguments = reifiedTypeArguments + info.translateArguments) +} diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsAstUtils.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsAstUtils.java index aa9ed46d8f6..f1d15ee3c9d 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsAstUtils.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsAstUtils.java @@ -168,16 +168,6 @@ public final class JsAstUtils { return invokeKotlinFunction(Namer.PRIMITIVE_COMPARE_TO, left, right); } - @NotNull - public static JsExpression isNumber(@NotNull JsExpression expression) { - return invokeKotlinFunction(Namer.IS_NUMBER, expression); - } - - @NotNull - public static JsExpression isChar(@NotNull JsExpression expression) { - return invokeKotlinFunction(Namer.IS_CHAR, expression); - } - @NotNull private static JsExpression rangeTo(@NotNull String rangeClassName, @NotNull JsExpression rangeStart, @NotNull JsExpression rangeEnd) { JsNameRef expr = new JsNameRef(rangeClassName, Namer.KOTLIN_NAME); @@ -196,11 +186,6 @@ public final class JsAstUtils { return rangeTo(Namer.CHAR_RANGE, rangeStart, rangeEnd); } - @NotNull - public static JsExpression isLong(@NotNull JsExpression expression) { - return instanceOf(expression, Namer.KOTLIN_LONG_NAME_REF); - } - public static JsExpression newLong(long value, @NotNull TranslationContext context) { if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) { int low = (int) value; @@ -259,11 +244,6 @@ 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) { diff --git a/js/js.translator/testData/kotlin_lib_ecma5.js b/js/js.translator/testData/kotlin_lib_ecma5.js index e465e0a5b53..fb93b455480 100644 --- a/js/js.translator/testData/kotlin_lib_ecma5.js +++ b/js/js.translator/testData/kotlin_lib_ecma5.js @@ -475,9 +475,23 @@ var Kotlin = {}; Object.defineProperty(Kotlin.modules, id, {value: declaration}); }; - Kotlin.defineInlineFunction = function(tag, fun) { + function defineInlineFunction(tag, fun) { return fun; - }; + } + + Kotlin.defineInlineFunction = defineInlineFunction; + + Kotlin.isTypeOf = defineInlineFunction('stdlib.kotlin.isTypeOf', function (type) { + return function (object) { + return typeof object === type; + } + }); + + Kotlin.isInstanceOf = defineInlineFunction('stdlib.kotlin.isInstanceOf', function (klass) { + return function (object) { + return Kotlin.isType(object, klass); + } + }); Kotlin.kotlinModuleMetadata = function (abiVersion, moduleName, data) { };