From a72ea641428dc5fd07e273f4185dba004508c9de Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Fri, 28 Oct 2016 16:37:27 +0300 Subject: [PATCH] JS: fix translation of lambdas in public inline functions; fix AMD emulation for Ant test --- .../amd.js | 10 ++++- .../kotlin/js/naming/NameSuggestion.kt | 26 ++++++++++-- .../kotlin/js/inline/util/invocationUtils.kt | 41 ++++++------------- .../js/test/semantics/BoxJsTestGenerated.java | 6 +++ .../expression/LiteralFunctionTranslator.kt | 31 ++++++++++---- .../testData/box/crossModuleRef/lambda.kt | 17 ++++++++ 6 files changed, 90 insertions(+), 41 deletions(-) create mode 100644 js/js.translator/testData/box/crossModuleRef/lambda.kt diff --git a/compiler/testData/integration/ant/js/simpleWithStdlibAndJsFileAsAnotherLibModuleKind/amd.js b/compiler/testData/integration/ant/js/simpleWithStdlibAndJsFileAsAnotherLibModuleKind/amd.js index 4eff8adf66b..4c0dc3b8768 100644 --- a/compiler/testData/integration/ant/js/simpleWithStdlibAndJsFileAsAnotherLibModuleKind/amd.js +++ b/compiler/testData/integration/ant/js/simpleWithStdlibAndJsFileAsAnotherLibModuleKind/amd.js @@ -4,10 +4,16 @@ function define(name, dependencies, body) { var resolvedDependencies = []; + var currentModule = {}; + modules[name] = currentModule; for (var i = 0; i < dependencies.length; ++i) { - resolvedDependencies[i] = modules[dependencies[i]]; + var dependencyName = dependencies[i]; + resolvedDependencies[i] = dependencyName === 'exports' ? currentModule : modules[dependencyName]; + } + currentModule = body.apply(body, resolvedDependencies); + if (currentModule) { + modules[name] = currentModule; } - modules[name] = body.apply(body, resolvedDependencies); } define.amd = {}; diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/naming/NameSuggestion.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/naming/NameSuggestion.kt index 5fa5c4e7e22..6ed8728c315 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/naming/NameSuggestion.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/naming/NameSuggestion.kt @@ -19,7 +19,6 @@ package org.jetbrains.kotlin.js.naming import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName -import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.getNameForAnnotatedObject import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.isNativeObject import org.jetbrains.kotlin.resolve.DescriptorUtils @@ -105,8 +104,29 @@ class NameSuggestion { // Local functions and variables are always private with their own names as suggested names is CallableDescriptor -> if (DescriptorUtils.isDescriptorWithLocalVisibility(descriptor)) { - val name = getNameForAnnotatedObject(descriptor) ?: getSuggestedName(descriptor) - return SuggestedName(listOf(name), false, descriptor, descriptor.containingDeclaration) + val ownName = getNameForAnnotatedObject(descriptor) ?: getSuggestedName(descriptor) + var name = ownName + var scope = descriptor.containingDeclaration + + // Local functions always lifted to the closest class or package when they are contained inside public inline function + if (descriptor is FunctionDescriptor) { + var container = descriptor.containingDeclaration + var liftedName = ownName + var hasInline = false + while (container is FunctionDescriptor) { + if (container.isInline && container.visibility.isPublicAPI) { + hasInline = true + } + liftedName = getSuggestedName(container) + "$" + liftedName + container = container.containingDeclaration + } + if (hasInline) { + scope = container + name = liftedName + } + } + + return SuggestedName(listOf(name), false, descriptor, scope) } } diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/invocationUtils.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/invocationUtils.kt index 7f3ccdb51d7..e75b1c46a01 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/invocationUtils.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/invocationUtils.kt @@ -16,15 +16,9 @@ package org.jetbrains.kotlin.js.inline.util -import com.google.dart.compiler.backend.js.ast.JsInvocation -import com.google.dart.compiler.backend.js.ast.JsName -import com.google.dart.compiler.backend.js.ast.JsExpression -import com.google.dart.compiler.backend.js.ast.JsNameRef -import com.google.dart.compiler.backend.js.ast.JsFunction -import org.jetbrains.kotlin.js.translate.context.Namer -import com.google.dart.compiler.backend.js.ast.HasName -import com.google.dart.compiler.backend.js.ast.JsNode +import com.google.dart.compiler.backend.js.ast.* import com.google.dart.compiler.backend.js.ast.metadata.staticRef +import org.jetbrains.kotlin.js.translate.context.Namer /** * Gets invocation qualifier name. @@ -62,19 +56,6 @@ fun getSimpleIdent(call: JsInvocation): String? { return null } -/** - * Checks if JsInvocation is function creator call. - * - * Function creator is a function, that creates closure. - */ -fun isFunctionCreatorInvocation(invocation: JsInvocation): Boolean { - val staticRef = getStaticRef(invocation) - return when (staticRef) { - is JsFunction -> isFunctionCreator(staticRef) - else -> false - } -} - /** * Tests if invocation is JavaScript call function * @@ -116,12 +97,6 @@ private fun getCallerQualifierImpl(invocation: JsInvocation): JsExpression? { return (invocation.qualifier as? JsNameRef)?.qualifier } -private fun getStaticRef(invocation: JsInvocation): JsNode? { - val qualifier = invocation.qualifier - val qualifierName = (qualifier as? HasName)?.name - return qualifierName?.staticRef -} - /** * If an expression A references to another expression B, which in turn references to C, or a corresponding expression * at the end of a chain of references. They are usually JsNameRef expressions with JsFunction at the very end. @@ -131,8 +106,16 @@ private fun getStaticRef(invocation: JsInvocation): JsNode? { val JsExpression.transitiveStaticRef: JsExpression get() { var qualifier = this - while (qualifier is JsNameRef) { - qualifier = qualifier.name?.staticRef as? JsExpression ?: break + loop@while (true) { + qualifier = when (qualifier) { + is JsNameRef -> { + qualifier.name?.staticRef as? JsExpression ?: break@loop + } + is JsInvocation -> { + getSimpleName(qualifier)?.staticRef as? JsExpression ?: break@loop + } + else -> break@loop + } } return qualifier } \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index e7e961e95ed..f407b9f3b70 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -803,6 +803,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { doTest(fileName); } + @TestMetadata("lambda.kt") + public void testLambda() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/crossModuleRef/lambda.kt"); + doTest(fileName); + } + @TestMetadata("object.kt") public void testObject() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/crossModuleRef/object.kt"); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt index 1b300df82de..f1d34632f07 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt @@ -62,25 +62,25 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato tracker.getNameForCapturedDescriptor(descriptor) } else { - context().getInnerNameForDescriptor(descriptor) + invokingContext.getInnerNameForDescriptor(descriptor) } if (tracker.hasCapturedExceptContaining()) { val lambdaCreator = simpleReturnFunction(invokingContext.scope(), lambda) - lambdaCreator.name = context().getInnerNameForDescriptor(descriptor) + lambdaCreator.name = invokingContext.getInnerNameForDescriptor(descriptor) lambdaCreator.isLocal = true if (!isRecursive) { lambda.name = null } lambdaCreator.name.staticRef = lambdaCreator - return lambdaCreator.withCapturedParameters(functionContext, invokingContext) + return lambdaCreator.withCapturedParameters(descriptor, functionContext, invokingContext) } lambda.isLocal = true - context().addDeclarationStatement(lambda.makeStmt()) + invokingContext.addDeclarationStatement(lambda.makeStmt()) lambda.name.staticRef = lambda - return lambda.name.makeRef().apply { sideEffects = SideEffectKind.PURE } + return getReferenceToLambda(invokingContext, descriptor, lambda.name) } fun ValueParameterDescriptorImpl.WithDestructuringDeclaration.translate(context: TranslationContext): JsVars { @@ -93,9 +93,13 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato } } -fun JsFunction.withCapturedParameters(context: TranslationContext, invokingContext: TranslationContext): JsExpression { +fun JsFunction.withCapturedParameters( + descriptor: CallableMemberDescriptor, + context: TranslationContext, + invokingContext: TranslationContext +): JsExpression { context.addDeclarationStatement(makeStmt()) - val ref = name.makeRef().apply { sideEffects = SideEffectKind.PURE } + val ref = getReferenceToLambda(invokingContext, descriptor, name) val invocation = JsInvocation(ref).apply { sideEffects = SideEffectKind.PURE } val invocationArguments = invocation.arguments @@ -128,6 +132,19 @@ fun JsFunction.withCapturedParameters(context: TranslationContext, invokingConte return invocation } +private fun getReferenceToLambda(context: TranslationContext, descriptor: CallableMemberDescriptor, name: JsName): JsExpression { + return if (context.isPublicInlineFunction) { + val fqn = context.getQualifiedReference(descriptor) + if (fqn is JsNameRef) { + fqn.name?.let { it.staticRef = name.staticRef } + } + fqn + } + else { + JsAstUtils.pureFqn(name, null) + } +} + private data class CapturedArgsParams(val arguments: List = listOf(), val parameters: List = listOf()) /** diff --git a/js/js.translator/testData/box/crossModuleRef/lambda.kt b/js/js.translator/testData/box/crossModuleRef/lambda.kt new file mode 100644 index 00000000000..577ac75c58d --- /dev/null +++ b/js/js.translator/testData/box/crossModuleRef/lambda.kt @@ -0,0 +1,17 @@ +// MODULE: lib +// FILE: lib.kt +package lib + +fun bar(f: () -> String) = f() + +inline fun foo(): String { + return bar { "OK" } +} + +// MODULE: main(lib) +// FILE: main.kt +package main + +import lib.* + +fun box() = foo() \ No newline at end of file