JS: refactor generation of callable references to functions

This commit is contained in:
Alexey Andreev
2017-01-16 16:44:42 +03:00
parent 1b0648a926
commit 7a0f75f164
18 changed files with 87 additions and 268 deletions
@@ -1,5 +1,3 @@
// Enable when callable references to builtin members is supported
// IGNORE_BACKEND: JS
fun box(): String {
var state = 0
val name = (state++)::toString.name
@@ -1,5 +1,3 @@
// Enable when callable references to builtin members is supported
// IGNORE_BACKEND: JS
fun <T> get(t: T): () -> String {
return t::toString
}
@@ -1,5 +1,4 @@
//WITH_RUNTIME
// IGNORE_BACKEND: JS
fun box(): String {
val a = intArrayOf(1, 2)
val b = arrayOf("OK")
@@ -1,5 +1,3 @@
// Enable when callable references to builtin members is supported
// IGNORE_BACKEND: JS
fun box(): String {
val f = "KOTLIN"::get
return "${f(1)}${f(0)}"
@@ -1,6 +1,3 @@
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
fun box(): String {
if ((Boolean::not)(true) != false) return "Fail 1"
if ((Boolean::not)(false) != true) return "Fail 2"
@@ -1,6 +1,3 @@
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
enum class E {
ENTRY
}
@@ -1,6 +1,3 @@
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
class A
fun box() = if ((A::equals)(A(), A())) "Fail" else "OK"
@@ -1,6 +1,3 @@
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
fun box(): String {
fun OK() {}
@@ -1,6 +1,3 @@
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
private fun <T> upcast(value: T): T = value
fun box(): String {
@@ -1,6 +1,3 @@
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
// See KT-12337 Reference to property with invisible setter should not be a KMutableProperty
import kotlin.reflect.KProperty1
-3
View File
@@ -1,6 +1,3 @@
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
// WITH_RUNTIME
fun box(): String =
-3
View File
@@ -1,6 +1,3 @@
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
// WITH_RUNTIME
fun box(): String =
@@ -1,5 +1,3 @@
// Enable when callable references to builtin members is supported
// IGNORE_BACKEND: JS
// FILE: 1.kt
package test
@@ -14,43 +14,9 @@
* limitations under the License.
*/
// TODO Store callable references for members in class
Kotlin.getCallableRefForMemberFunction = function (memberName) {
return function () {
var args = [].slice.call(arguments);
var instance = args.shift();
return instance[memberName].apply(instance, args);
};
};
Kotlin.getBoundCallableRefForMemberFunction = function (receiver, memberName) {
return function () {
return receiver[memberName].apply(receiver, arguments);
};
};
// TODO Store callable references for extension functions in class
// extFun expected receiver as the first argument
Kotlin.getCallableRefForExtensionFunction = function (extFun) {
return function () {
return extFun.apply(null, arguments);
};
};
Kotlin.getBoundCallableRefForExtensionFunction = function (receiver, extFun) {
return function () {
var args = [].slice.call(arguments);
args.unshift(receiver);
return extFun.apply(null, args);
};
};
Kotlin.getCallableRefForConstructor = function (klass) {
return function () {
var obj = Object.create(klass.prototype);
klass.apply(obj, arguments);
return obj;
};
Kotlin.getCallableRef = function(name, f) {
f.callableName = name;
return f;
};
Kotlin.getCallableRefZeroArg = function(name, getter, setter) {
@@ -143,13 +143,7 @@ public class CallableReferenceInlineTestsGenerated extends AbstractCallableRefer
@TestMetadata("intrinsic.kt")
public void testIntrinsic() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/callableReference/bound/intrinsic.kt");
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
doTest(fileName);
}
@TestMetadata("map.kt")
@@ -1865,37 +1865,19 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
@TestMetadata("kCallableNameIntrinsic.kt")
public void testKCallableNameIntrinsic() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/bound/kCallableNameIntrinsic.kt");
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
doTest(fileName);
}
@TestMetadata("kt12738.kt")
public void testKt12738() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/bound/kt12738.kt");
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
doTest(fileName);
}
@TestMetadata("kt15446.kt")
public void testKt15446() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/bound/kt15446.kt");
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
doTest(fileName);
}
@TestMetadata("multiCase.kt")
@@ -1925,13 +1907,7 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
@TestMetadata("simpleFunction.kt")
public void testSimpleFunction() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/bound/simpleFunction.kt");
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
doTest(fileName);
}
@TestMetadata("simpleProperty.kt")
@@ -2036,13 +2012,7 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
@TestMetadata("booleanNotIntrinsic.kt")
public void testBooleanNotIntrinsic() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/function/booleanNotIntrinsic.kt");
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
doTest(fileName);
}
@TestMetadata("classMemberFromClass.kt")
@@ -2096,25 +2066,13 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
@TestMetadata("enumValueOfMethod.kt")
public void testEnumValueOfMethod() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/function/enumValueOfMethod.kt");
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
doTest(fileName);
}
@TestMetadata("equalsIntrinsic.kt")
public void testEqualsIntrinsic() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/function/equalsIntrinsic.kt");
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
doTest(fileName);
}
@TestMetadata("extensionFromClass.kt")
@@ -2270,13 +2228,7 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
@TestMetadata("newArray.kt")
public void testNewArray() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/function/newArray.kt");
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
doTest(fileName);
}
@TestMetadata("overloadedFun.kt")
@@ -2446,13 +2398,7 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
@TestMetadata("localFunctionName.kt")
public void testLocalFunctionName() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/function/local/localFunctionName.kt");
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
doTest(fileName);
}
@TestMetadata("localLocal.kt")
@@ -9975,25 +9921,13 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
@TestMetadata("kt10131.kt")
public void testKt10131() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/intrinsics/kt10131.kt");
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
doTest(fileName);
}
@TestMetadata("kt10131a.kt")
public void testKt10131a() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/intrinsics/kt10131a.kt");
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
doTest(fileName);
}
@TestMetadata("kt12125.kt")
@@ -75,14 +75,6 @@ public final class Namer {
public static final String OUTER_FIELD_NAME = "$outer";
private static final String CALLABLE_REF_FOR_MEMBER_FUNCTION_NAME = "getCallableRefForMemberFunction";
private static final String BOUND_CALLABLE_REF_FOR_MEMBER_FUNCTION_NAME = "getBoundCallableRefForMemberFunction";
private static final String CALLABLE_REF_FOR_EXTENSION_FUNCTION_NAME = "getCallableRefForExtensionFunction";
private static final String BOUND_CALLABLE_REF_FOR_EXTENSION_FUNCTION_NAME = "getBoundCallableRefForExtensionFunction";
private static final String CALLABLE_REF_FOR_LOCAL_EXTENSION_FUNCTION_NAME = "getCallableRefForLocalExtensionFunction";
private static final String BOUND_CALLABLE_REF_FOR_LOCAL_EXTENSION_FUNCTION_NAME = "getBoundCallableRefForLocalExtensionFunction";
private static final String CALLABLE_REF_FOR_CONSTRUCTOR_NAME = "getCallableRefForConstructor";
private static final String DELEGATE = "$delegate";
private static final String ROOT_PACKAGE = "_";
@@ -198,15 +190,7 @@ public final class Namer {
@NotNull
private final JsObjectScope kotlinScope;
@NotNull
private final JsName callableRefForMemberFunctionName;
@NotNull
private final JsName boundCallableRefForMemberFunctionName;
@NotNull
private final JsName callableRefForExtensionFunctionName;
@NotNull
private final JsName boundCallableRefForExtensionFunctionName;
@NotNull
private final JsName callableRefForConstructorName;
public static final String FUNCTION_CALLABLE_REF = "getCallableRef";
@NotNull
public static final String PROPERTY_CALLABLE_REF_ZERO_ARG = "getCallableRefZeroArg";
@NotNull
@@ -225,12 +209,6 @@ public final class Namer {
callGetProperty = kotlin("callGetter");
callSetProperty = kotlin("callSetter");
callableRefForMemberFunctionName = kotlinScope.declareName(CALLABLE_REF_FOR_MEMBER_FUNCTION_NAME);
boundCallableRefForMemberFunctionName = kotlinScope.declareName(BOUND_CALLABLE_REF_FOR_MEMBER_FUNCTION_NAME);
callableRefForExtensionFunctionName = kotlinScope.declareName(CALLABLE_REF_FOR_EXTENSION_FUNCTION_NAME);
boundCallableRefForExtensionFunctionName = kotlinScope.declareName(BOUND_CALLABLE_REF_FOR_EXTENSION_FUNCTION_NAME);
callableRefForConstructorName = kotlinScope.declareName(CALLABLE_REF_FOR_CONSTRUCTOR_NAME);
isTypeName = kotlinScope.declareName("isType");
}
@@ -245,31 +223,6 @@ public final class Namer {
return suggested.getNames().get(0);
}
@NotNull
public JsExpression callableRefForMemberFunctionReference() {
return kotlin(callableRefForMemberFunctionName);
}
@NotNull
public JsExpression boundCallableRefForMemberFunctionReference() {
return kotlin(boundCallableRefForMemberFunctionName);
}
@NotNull
public JsExpression callableRefForExtensionFunctionReference() {
return kotlin(callableRefForExtensionFunctionName);
}
@NotNull
public JsExpression boundCallableRefForExtensionFunctionReference() {
return kotlin(boundCallableRefForExtensionFunctionName);
}
@NotNull
public JsExpression callableRefForConstructorReference() {
return kotlin(callableRefForConstructorName);
}
@NotNull
public static JsExpression throwNPEFunctionRef() {
return new JsNameRef(THROW_NPE_FUN_NAME, kotlinObject());
@@ -16,23 +16,30 @@
package org.jetbrains.kotlin.js.translate.reference
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.resolve.diagnostics.ErrorsJs
import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator
import org.jetbrains.kotlin.js.translate.context.Namer
import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.general.Translation
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
import org.jetbrains.kotlin.js.translate.utils.BindingUtils
import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtCallableReferenceExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.PropertyImportedFromObject
import org.jetbrains.kotlin.resolve.calls.callUtil.getFunctionResolvedCallWithAssert
import org.jetbrains.kotlin.resolve.calls.callUtil.getPropertyResolvedCallWithAssert
import org.jetbrains.kotlin.resolve.calls.model.DelegatingResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument
import org.jetbrains.kotlin.resolve.calls.util.CallMaker
import org.jetbrains.kotlin.types.expressions.DoubleColonLHS
object CallableReferenceTranslator {
@@ -66,30 +73,56 @@ object CallableReferenceTranslator {
}
}
private fun reportNotSupported(context: TranslationContext, expression: KtCallableReferenceExpression): JsExpression {
context.bindingTrace().report(ErrorsJs.REFERENCE_TO_BUILTIN_MEMBERS_NOT_SUPPORTED.on(expression, expression))
return JsLiteral.NULL
}
private fun translateForFunction(
descriptor: FunctionDescriptor,
context: TranslationContext,
expression: KtCallableReferenceExpression,
receiver: JsExpression?
): JsExpression {
return when {
// TODO Support for callable reference to builtin functions and members
KotlinBuiltIns.isBuiltIn(descriptor) ->
reportNotSupported(context, expression)
isConstructor(descriptor) ->
translateForConstructor(descriptor, context)
isExtension(descriptor) ->
translateForExtensionFunction(descriptor, context, receiver)
isMember(descriptor) ->
translateForMemberFunction(descriptor, context, receiver)
else ->
ReferenceTranslator.translateAsValueReference(descriptor, context)
val realResolvedCall = expression.callableReference.getFunctionResolvedCallWithAssert(context.bindingContext())
val fakeExpression = constructFakeFunctionCall(expression.project, descriptor)
val fakeCall = CallMaker.makeCall(fakeExpression, null, null, fakeExpression, fakeExpression.valueArguments)
val fakeResolvedCall = object : DelegatingResolvedCall<FunctionDescriptor>(realResolvedCall) {
val valueArgumentList = fakeCall.valueArguments.map(::ExpressionValueArgument)
val valueArgumentMap = valueArgumentList.withIndex().associate { (index, arg) -> descriptor.valueParameters[index] to arg }
override fun getCall() = fakeCall
override fun getValueArgumentsByIndex(): List<ResolvedValueArgument> = valueArgumentList
override fun getValueArguments(): Map<ValueParameterDescriptor, ResolvedValueArgument> = valueArgumentMap
}
val function = JsFunction(context.scope(), JsBlock(), "")
val receiverParam = if (descriptor.dispatchReceiverParameter != null || descriptor.extensionReceiverParameter != null) {
val paramName = function.scope.declareTemporaryName(Namer.getReceiverParameterName())
function.parameters += JsParameter(paramName)
paramName.makeRef()
}
else {
null
}
val aliases = mutableMapOf<KtExpression, JsExpression>()
for ((index, valueArg) in fakeCall.valueArguments.withIndex()) {
val paramName = function.scope.declareTemporaryName(descriptor.valueParameters[index].name.asString())
function.parameters += JsParameter(paramName)
aliases[valueArg.getArgumentExpression()!!] = paramName.makeRef()
}
val functionContext = context.innerBlock(function.body).innerContextWithAliasesForExpressions(aliases)
val invocation = CallTranslator.translate(functionContext, fakeResolvedCall, receiverParam)
function.body.statements += JsReturn(invocation)
val rawCallableRef = bindIfNecessary(function, receiver)
return wrapFunctionCallableRef(context, expression.callableReference.getReferencedName(), rawCallableRef)
}
private fun constructFakeFunctionCall(project: Project, referencedFunction: FunctionDescriptor): KtCallExpression {
val fakeFunctionCall = StringBuilder("callableReferenceFakeCall(")
fakeFunctionCall.append(referencedFunction.valueParameters.map { "p${it.index}" }.joinToString(", "))
fakeFunctionCall.append(")")
return KtPsiFactory(project).createExpression(fakeFunctionCall.toString()) as KtCallExpression
}
private fun translateForProperty(
@@ -111,7 +144,7 @@ object CallableReferenceTranslator {
null
}
return wrapPropertyCallableRef(context, receiver, descriptor, descriptor.name.identifier, getter, setter)
return wrapPropertyCallableRef(context, receiver, descriptor, expression.callableReference.getReferencedName(), getter, setter)
}
private fun isSetterVisible(descriptor: PropertyDescriptor, context: TranslationContext): Boolean {
@@ -151,11 +184,15 @@ object CallableReferenceTranslator {
val accessorResult = translator(accessorContext, call, valueParam, receiverParam)
accessorFunction.body.statements += if (isSetter) accessorResult.makeStmt() else JsReturn(accessorResult)
return bindIfNecessary(accessorFunction, receiver)
}
private fun bindIfNecessary(function: JsFunction, receiver: JsExpression?): JsExpression {
return if (receiver != null) {
JsInvocation(JsNameRef("bind", accessorFunction), JsLiteral.NULL, receiver)
JsInvocation(JsNameRef("bind", function), JsLiteral.NULL, receiver)
}
else {
accessorFunction
function
}
}
@@ -181,47 +218,15 @@ object CallableReferenceTranslator {
return invocation
}
private fun isConstructor(descriptor: CallableDescriptor) = descriptor is ConstructorDescriptor
private fun isExtension(descriptor: CallableDescriptor) = DescriptorUtils.isExtension(descriptor)
private fun isMember(descriptor: CallableDescriptor) = JsDescriptorUtils.getContainingDeclaration(descriptor) is ClassDescriptor
private fun translateForConstructor(descriptor: FunctionDescriptor, context: TranslationContext): JsExpression {
val jsFunctionRef = ReferenceTranslator.translateAsValueReference(descriptor, context)
return JsInvocation(context.namer().callableRefForConstructorReference(), jsFunctionRef)
}
private fun translateForExtensionFunction(descriptor: FunctionDescriptor,
context: TranslationContext,
receiver: JsExpression?
): JsExpression {
val jsFunctionRef = ReferenceTranslator.translateAsValueReference(descriptor, context)
if (AnnotationsUtils.isNativeObject(descriptor)) {
return translateForMemberFunction(descriptor, context, receiver)
}
else {
if (receiver == null) {
return JsInvocation(context.namer().callableRefForExtensionFunctionReference(), jsFunctionRef)
}
else {
return JsInvocation(context.namer().boundCallableRefForExtensionFunctionReference(), receiver, jsFunctionRef)
}
}
}
private fun translateForMemberFunction(
descriptor: CallableDescriptor,
private fun wrapFunctionCallableRef(
context: TranslationContext,
receiver: JsExpression?
name: String,
function: JsExpression
): JsExpression {
val funName = context.getNameForDescriptor(descriptor)
val funNameAsString = context.program().getStringLiteral(funName.toString())
if (receiver == null) {
return JsInvocation(context.namer().callableRefForMemberFunctionReference(), funNameAsString)
}
else {
return JsInvocation(context.namer().boundCallableRefForMemberFunctionReference(), receiver, funNameAsString)
}
val nameLiteral = context.program().getStringLiteral(name)
val invokeName = Namer.FUNCTION_CALLABLE_REF
val invokeFun = JsNameRef(invokeName, Namer.kotlinObject())
val invocation = JsInvocation(invokeFun, nameLiteral, function)
return invocation
}
}