From cc9866483298f69cd80f892e35259857fe6525b4 Mon Sep 17 00:00:00 2001 From: Michael Nedzelsky Date: Tue, 22 Jul 2014 16:11:37 +0400 Subject: [PATCH] JS backend: support for reflection --- .../compiler/backend/js/ast/JsProgram.java | 2 + .../callTranslator/FunctionCallCases.kt | 13 +- .../k2js/translate/context/Namer.java | 54 ++++++ .../expression/ExpressionVisitor.java | 18 +- .../reference/CallableReferenceTranslator.kt | 158 ++++++++++++++++++ js/js.translator/testData/kotlin_lib_ecma5.js | 53 ++++++ 6 files changed, 286 insertions(+), 12 deletions(-) create mode 100644 js/js.translator/src/org/jetbrains/k2js/translate/reference/CallableReferenceTranslator.kt diff --git a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsProgram.java b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsProgram.java index 2954c33321d..6a388acf669 100644 --- a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsProgram.java +++ b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsProgram.java @@ -7,6 +7,7 @@ package com.google.dart.compiler.backend.js.ast; import gnu.trove.TDoubleObjectHashMap; import gnu.trove.THashMap; import gnu.trove.TIntObjectHashMap; +import org.jetbrains.annotations.NotNull; import java.util.Map; @@ -91,6 +92,7 @@ public final class JsProgram extends SourceInfoAwareJsNode { /** * Creates or retrieves a JsStringLiteral from an interned object pool. */ + @NotNull public JsStringLiteral getStringLiteral(String value) { JsStringLiteral literal = stringLiteralMap.get(value); if (literal == null) { diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/FunctionCallCases.kt b/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/FunctionCallCases.kt index ccebbf2389b..f9b19d28b78 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/FunctionCallCases.kt +++ b/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/FunctionCallCases.kt @@ -34,6 +34,8 @@ import org.jetbrains.k2js.translate.reference.CallArgumentTranslator import org.jetbrains.jet.lang.descriptors.CallableMemberDescriptor import org.jetbrains.jet.lang.descriptors.Visibilities import org.jetbrains.jet.lang.psi.Call.CallType +import org.jetbrains.k2js.translate.utils.JsDescriptorUtils +import org.jetbrains.jet.lang.resolve.DescriptorUtils public fun addReceiverToArgs(receiver: JsExpression, arguments: List): List { if (arguments.isEmpty()) @@ -143,10 +145,15 @@ object InvokeIntrinsic : FunctionCallCase { return false val parameterCount = callInfo.callableDescriptor.getValueParameters().size() val funDeclaration = callInfo.callableDescriptor.getContainingDeclaration() - return funDeclaration == ((if (callInfo.callableDescriptor.getReceiverParameter() == null) - KotlinBuiltIns.getInstance().getFunction(parameterCount) + + val reflectionTypes = callInfo.context.getReflectionTypes() + return if (callInfo.callableDescriptor.getReceiverParameter() == null) + funDeclaration == KotlinBuiltIns.getInstance().getFunction(parameterCount) || + funDeclaration == reflectionTypes.getKFunction(parameterCount) else - KotlinBuiltIns.getInstance().getExtensionFunction(parameterCount))) + funDeclaration == KotlinBuiltIns.getInstance().getExtensionFunction(parameterCount) || + funDeclaration == reflectionTypes.getKExtensionFunction(parameterCount) || + funDeclaration == reflectionTypes.getKMemberFunction(parameterCount) } override fun FunctionCallInfo.thisObject(): JsExpression { 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 580296c1489..c76614d4af6 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 @@ -43,6 +43,12 @@ public final class Namer { private static final String TRAIT_OBJECT_NAME = "createTrait"; private static final String OBJECT_OBJECT_NAME = "createObject"; private static final String ENUM_ENTRIES_NAME = "createEnumEntries"; + private static final String CALLABLE_REF_FOR_MEMBER_FUNCTION_NAME = "getCallableRefForMemberFunction"; + private static final String CALLABLE_REF_FOR_EXTENSION_FUNCTION_NAME = "getCallableRefForExtensionFunction"; + private static final String CALLABLE_REF_FOR_CONSTRUCTOR_NAME = "getCallableRefForConstructor"; + private static final String CALLABLE_REF_FOR_TOP_LEVEL_PROPERTY = "getCallableRefForTopLevelProperty"; + private static final String CALLABLE_REF_FOR_MEMBER_PROPERTY = "getCallableRefForMemberProperty"; + private static final String CALLABLE_REF_FOR_EXTENSION_PROPERTY = "getCallableRefForExtensionProperty"; private static final String SETTER_PREFIX = "set_"; private static final String GETTER_PREFIX = "get_"; @@ -177,6 +183,18 @@ public final class Namer { @NotNull private final JsName enumEntriesName; @NotNull + private final JsName callableRefForMemberFunctionName; + @NotNull + private final JsName callableRefForExtensionFunctionName; + @NotNull + private final JsName callableRefForConstructorName; + @NotNull + private final JsName callableRefForTopLevelProperty; + @NotNull + private final JsName callableRefForMemberProperty; + @NotNull + private final JsName callableRefForExtensionProperty; + @NotNull private final JsExpression undefinedExpression; @NotNull private final JsExpression callGetProperty; @@ -200,6 +218,12 @@ public final class Namer { className = kotlinScope.declareName(CLASS_OBJECT_NAME); enumEntriesName = kotlinScope.declareName(ENUM_ENTRIES_NAME); objectName = kotlinScope.declareName(OBJECT_OBJECT_NAME); + callableRefForMemberFunctionName = kotlinScope.declareName(CALLABLE_REF_FOR_MEMBER_FUNCTION_NAME); + callableRefForExtensionFunctionName = kotlinScope.declareName(CALLABLE_REF_FOR_EXTENSION_FUNCTION_NAME); + callableRefForConstructorName = kotlinScope.declareName(CALLABLE_REF_FOR_CONSTRUCTOR_NAME); + callableRefForTopLevelProperty = kotlinScope.declareName(CALLABLE_REF_FOR_TOP_LEVEL_PROPERTY); + callableRefForMemberProperty = kotlinScope.declareName(CALLABLE_REF_FOR_MEMBER_PROPERTY); + callableRefForExtensionProperty = kotlinScope.declareName(CALLABLE_REF_FOR_EXTENSION_PROPERTY); isTypeName = kotlinScope.declareName("isType"); @@ -236,6 +260,36 @@ public final class Namer { return kotlin(objectName); } + @NotNull + public JsExpression callableRefForMemberFunctionReference() { + return kotlin(callableRefForMemberFunctionName); + } + + @NotNull + public JsExpression callableRefForExtensionFunctionReference() { + return kotlin(callableRefForExtensionFunctionName); + } + + @NotNull + public JsExpression callableRefForConstructorReference() { + return kotlin(callableRefForConstructorName); + } + + @NotNull + public JsExpression callableRefForTopLevelPropertyReference() { + return kotlin(callableRefForTopLevelProperty); + } + + @NotNull + public JsExpression callableRefForMemberPropertyReference() { + return kotlin(callableRefForMemberProperty); + } + + @NotNull + public JsExpression callableRefForExtensionPropertyReference() { + return kotlin(callableRefForExtensionProperty); + } + @NotNull public JsExpression throwNPEFunctionRef() { return new JsNameRef(THROW_NPE_FUN_NAME, kotlinObject()); 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 bd37fa3bce9..13015b2a94d 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 @@ -19,9 +19,7 @@ package org.jetbrains.k2js.translate.expression; import com.google.dart.compiler.backend.js.ast.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; -import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; -import org.jetbrains.jet.lang.descriptors.VariableDescriptor; +import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.lang.resolve.BindingContextUtils; @@ -39,12 +37,8 @@ import org.jetbrains.k2js.translate.general.Translation; import org.jetbrains.k2js.translate.general.TranslatorVisitor; import org.jetbrains.k2js.translate.operation.BinaryOperationTranslator; import org.jetbrains.k2js.translate.operation.UnaryOperationTranslator; -import org.jetbrains.k2js.translate.reference.AccessTranslationUtils; -import org.jetbrains.k2js.translate.reference.CallExpressionTranslator; -import org.jetbrains.k2js.translate.reference.QualifiedExpressionTranslator; -import org.jetbrains.k2js.translate.reference.ReferenceTranslator; -import org.jetbrains.k2js.translate.utils.JsAstUtils; -import org.jetbrains.k2js.translate.utils.TranslationUtils; +import org.jetbrains.k2js.translate.reference.*; +import org.jetbrains.k2js.translate.utils.*; import org.jetbrains.k2js.translate.utils.mutator.AssignToExpressionMutator; import java.util.List; @@ -168,6 +162,12 @@ public final class ExpressionVisitor extends TranslatorVisitor { return newVar(name, initializer).source(expression); } + @Override + @NotNull + public JsNode visitCallableReferenceExpression(@NotNull JetCallableReferenceExpression expression, @NotNull TranslationContext context) { + return CallableReferenceTranslator.INSTANCE$.translate(expression, context); + } + @Override @NotNull public JsNode visitCallExpression(@NotNull JetCallExpression expression, diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallableReferenceTranslator.kt b/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallableReferenceTranslator.kt new file mode 100644 index 00000000000..0c70143ed08 --- /dev/null +++ b/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallableReferenceTranslator.kt @@ -0,0 +1,158 @@ +/* + * 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.reference + +import com.google.dart.compiler.backend.js.ast.JsExpression +import org.jetbrains.jet.lang.descriptors.CallableDescriptor +import org.jetbrains.k2js.translate.context.TranslationContext +import org.jetbrains.k2js.translate.utils.JsDescriptorUtils +import org.jetbrains.jet.lang.descriptors.ClassDescriptor +import com.google.dart.compiler.backend.js.ast.JsInvocation +import org.jetbrains.jet.lang.descriptors.Visibilities +import org.jetbrains.k2js.translate.utils.AnnotationsUtils +import org.jetbrains.jet.lang.psi.JetCallableReferenceExpression +import org.jetbrains.k2js.translate.utils.BindingUtils +import org.jetbrains.jet.lang.descriptors.ConstructorDescriptor +import org.jetbrains.jet.lang.descriptors.PropertyDescriptor +import org.jetbrains.jet.lang.descriptors.PackageFragmentDescriptor +import com.google.dart.compiler.backend.js.ast.JsLiteral +import java.util.ArrayList +import org.jetbrains.jet.lang.descriptors.FunctionDescriptor +import org.jetbrains.jet.lang.resolve.DescriptorUtils +import com.google.dart.compiler.backend.js.ast.JsNameRef +import org.jetbrains.k2js.translate.context.Namer +import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns + +object CallableReferenceTranslator { + + fun translate(expression: JetCallableReferenceExpression, context: TranslationContext): JsExpression { + val descriptor = BindingUtils.getDescriptorForReferenceExpression(context.bindingContext(), expression.getCallableReference()) + return when (descriptor) { + is PropertyDescriptor -> + translateForProperty(descriptor as PropertyDescriptor, context) + is FunctionDescriptor -> + translateForFunction(descriptor as FunctionDescriptor, context) + else -> + throw IllegalArgumentException("Expected property or function: ${descriptor}, expression=${expression.getText()}") + } + } + + private fun translateForFunction(descriptor: FunctionDescriptor, context: TranslationContext): JsExpression { + return when { + // TODO Support for callable reference to builtin functions and members + JsDescriptorUtils.isBuiltin(descriptor) -> + throw UnsupportedOperationException("callable references for builtin functions are not supported yet") + isConstructor(descriptor) -> + translateForConstructor(descriptor, context) + isExtension(descriptor) -> + translateForExtensionFunction(descriptor, context) + isMember(descriptor) -> + translateForMemberFunction(descriptor, context) + else -> + ReferenceTranslator.translateAsFQReference(descriptor, context) + } + } + + private fun translateForProperty(descriptor: PropertyDescriptor, context: TranslationContext): JsExpression { + return when { + // TODO Support for callable reference to builtin properties + JsDescriptorUtils.isBuiltin(descriptor) -> + throw UnsupportedOperationException("callable references for builtin properties are not supported yet") + isExtension(descriptor) -> + translateForExtensionProperty(descriptor, context) + isMember(descriptor) -> + translateForMemberProperty(descriptor, context) + else -> + translateForTopLevelProperty(descriptor, context) + } + } + + private fun isConstructor(descriptor: CallableDescriptor): Boolean = descriptor is ConstructorDescriptor + + private fun isExtension(descriptor: CallableDescriptor): Boolean = JsDescriptorUtils.isExtension(descriptor) + + private fun isMember(descriptor: CallableDescriptor): Boolean = JsDescriptorUtils.getContainingDeclaration(descriptor) is ClassDescriptor + + private fun isVar(descriptor: PropertyDescriptor): JsExpression = if (descriptor.isVar()) JsLiteral.TRUE else JsLiteral.FALSE + + private fun translateForTopLevelProperty(descriptor: PropertyDescriptor, context: TranslationContext): JsExpression { + val packageDescriptor = JsDescriptorUtils.getContainingDeclaration(descriptor) + assert(packageDescriptor is PackageFragmentDescriptor, "Expected PackageFragmentDescriptor: ${packageDescriptor}") + + val jsPackageNameRef = context.getQualifiedReference(packageDescriptor) + val jsPropertyName = context.getNameForDescriptor(descriptor) + val jsPropertyNameAsString = context.program().getStringLiteral(jsPropertyName.toString()) + + return JsInvocation(context.namer().callableRefForTopLevelPropertyReference(), jsPackageNameRef, jsPropertyNameAsString, isVar(descriptor)) + } + + private fun translateForMemberProperty(descriptor: PropertyDescriptor, context: TranslationContext): JsExpression { + val jsPropertyName = context.getNameForDescriptor(descriptor) + val jsPropertyNameAsString = context.program().getStringLiteral(jsPropertyName.toString()) + return JsInvocation(context.namer().callableRefForMemberPropertyReference(), jsPropertyNameAsString, isVar(descriptor)) + } + + private fun translateForExtensionProperty(descriptor: PropertyDescriptor, context: TranslationContext): JsExpression { + val jsGetterNameRef = context.getQualifiedReference(descriptor.getGetter()!!) + val propertyName = descriptor.getName() + val jsPropertyNameAsString = context.program().getStringLiteral(propertyName.asString()) + val argumentList = ArrayList(3) + argumentList.add(jsPropertyNameAsString) + argumentList.add(jsGetterNameRef) + if (descriptor.isVar()) { + val jsSetterNameRef = context.getQualifiedReference(descriptor.getSetter()!!) + argumentList.add(jsSetterNameRef) + } + if (AnnotationsUtils.isNativeObject(descriptor)) + return translateForMemberProperty(descriptor, context) + else + return JsInvocation(context.namer().callableRefForExtensionPropertyReference(), argumentList) + } + + private fun translateForConstructor(descriptor: FunctionDescriptor, context: TranslationContext): JsExpression { + val jsFunctionRef = ReferenceTranslator.translateAsFQReference(descriptor, context) + return JsInvocation(context.namer().callableRefForConstructorReference(), jsFunctionRef) + } + + private fun translateForExtensionFunction(descriptor: FunctionDescriptor, context: TranslationContext): JsExpression { + val receiverParameterDescriptor = descriptor.getReceiverParameter() + assert(receiverParameterDescriptor != null, "receiverParameter for extension should not be null") + + val jsFunctionRef = ReferenceTranslator.translateAsFQReference(descriptor, context) + if (descriptor.getVisibility() == Visibilities.LOCAL) + return jsFunctionRef + else if (AnnotationsUtils.isNativeObject(descriptor)) { + val jetType = receiverParameterDescriptor!!.getType() + val receiverClassDescriptor = DescriptorUtils.getClassDescriptorForType(jetType) + return translateAsMemberFunctionReference(descriptor, receiverClassDescriptor, context) + } + else + return JsInvocation(context.namer().callableRefForExtensionFunctionReference(), jsFunctionRef) + } + + private fun translateForMemberFunction(descriptor: FunctionDescriptor, context: TranslationContext): JsExpression { + val classDescriptor = JsDescriptorUtils.getContainingDeclaration(descriptor) as? ClassDescriptor ?: throw IllegalArgumentException("Expected ClassDescriptor: ${descriptor}") + return translateAsMemberFunctionReference(descriptor, classDescriptor, context) + } + + private fun translateAsMemberFunctionReference(descriptor: CallableDescriptor, classDescriptor: ClassDescriptor, context: TranslationContext): JsExpression { + val jsClassNameRef = context.getQualifiedReference(classDescriptor) + val funName = context.getNameForDescriptor(descriptor) + val funNameAsString = context.program().getStringLiteral(funName.toString()) + return JsInvocation(context.namer().callableRefForMemberFunctionReference(), jsClassNameRef, funNameAsString) + } +} \ No newline at end of file diff --git a/js/js.translator/testData/kotlin_lib_ecma5.js b/js/js.translator/testData/kotlin_lib_ecma5.js index 45df55ef93d..0954fb2689f 100644 --- a/js/js.translator/testData/kotlin_lib_ecma5.js +++ b/js/js.translator/testData/kotlin_lib_ecma5.js @@ -294,7 +294,60 @@ var Kotlin = {}; } }; + // TODO Store callable references for members in class + Kotlin.getCallableRefForMemberFunction = function (klass, memberName) { + return function () { + return this[memberName].apply(this, arguments); + }; + }; + // TODO Store callable references for extension functions in class + // extFun expected receiver as the first argument + Kotlin.getCallableRefForExtensionFunction = function (extFun) { + return function () { + var args = [this]; + Array.prototype.push.apply(args, arguments); + return extFun.apply(null, args); + }; + }; + + Kotlin.getCallableRefForConstructor = function (klass) { + return function () { + var obj = Object.create(klass.prototype); + klass.apply(obj, arguments); + return obj; + }; + }; + + Kotlin.getCallableRefForTopLevelProperty = function(packageName, name, isVar) { + var obj = {}; + obj.name = name; + obj.get = function() { return packageName[name]; }; + if (isVar) { + obj.set_za3rmp$ = function(value) { packageName[name] = value; }; + } + return obj; + }; + + Kotlin.getCallableRefForMemberProperty = function(name, isVar) { + var obj = {}; + obj.name = name; + obj.get_za3rmp$ = function(receiver) { return receiver[name]; }; + if (isVar) { + obj.set_wn2jw4$ = function(receiver, value) { receiver[name] = value; }; + } + return obj; + }; + + Kotlin.getCallableRefForExtensionProperty = function(name, getFun, setFun) { + var obj = {}; + obj.name = name; + obj.get_za3rmp$ = getFun; + if (setFun !== undefined) { + obj.set_wn2jw4$ = setFun; + } + return obj; + }; ////////////////////////////////// packages & modules ////////////////////////////// Kotlin.modules = {};