From d63a72747405d56a488c1070c7e13985e8a2ceb2 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 5 Jul 2016 11:45:33 +0300 Subject: [PATCH] KT-12877: support plain reference to declaration in UMD wrapper, support plain reference to package --- .../js/test/semantics/BoxJsTestGenerated.java | 12 ++ .../kotlin/js/facade/K2JSTranslator.java | 6 +- .../js/translate/context/StaticContext.java | 120 +++++++++++++++--- .../translate/context/TranslationContext.java | 2 +- .../general/ModuleWrapperTranslation.kt | 33 +++-- .../js/translate/general/Translation.java | 9 +- .../jsModule/externalFunctionUmdFallback.js | 5 +- .../jsModule/externalFunctionUmdFallback.kt | 8 +- .../box/jsModule/externalPackagePlain.js | 29 +++++ .../box/jsModule/externalPackagePlain.kt | 36 ++++++ .../jsModule/externalPackageUmdFallback.js | 29 +++++ .../jsModule/externalPackageUmdFallback.kt | 38 ++++++ 12 files changed, 285 insertions(+), 42 deletions(-) create mode 100644 js/js.translator/testData/box/jsModule/externalPackagePlain.js create mode 100644 js/js.translator/testData/box/jsModule/externalPackagePlain.kt create mode 100644 js/js.translator/testData/box/jsModule/externalPackageUmdFallback.js create mode 100644 js/js.translator/testData/box/jsModule/externalPackageUmdFallback.kt 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 5980a879a6d..e40f77b68b9 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 @@ -5030,6 +5030,18 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { doTest(fileName); } + @TestMetadata("externalPackagePlain.kt") + public void testExternalPackagePlain() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/jsModule/externalPackagePlain.kt"); + doTest(fileName); + } + + @TestMetadata("externalPackageUmdFallback.kt") + public void testExternalPackageUmdFallback() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/jsModule/externalPackageUmdFallback.kt"); + doTest(fileName); + } + @TestMetadata("externalProperty.kt") public void testExternalProperty() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/jsModule/externalProperty.kt"); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.java index 74b0eccb1bf..cae7ae88b45 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.java @@ -26,6 +26,7 @@ import org.jetbrains.kotlin.js.config.JsConfig; import org.jetbrains.kotlin.js.coroutine.CoroutineTransformer; import org.jetbrains.kotlin.js.facade.exceptions.TranslationException; import org.jetbrains.kotlin.js.inline.JsInliner; +import org.jetbrains.kotlin.js.translate.context.StaticContext; import org.jetbrains.kotlin.js.inline.clean.RemoveUnusedImportsKt; import org.jetbrains.kotlin.js.translate.context.TranslationContext; import org.jetbrains.kotlin.js.translate.general.Translation; @@ -96,7 +97,10 @@ public final class K2JSTranslator { expandIsCalls(program, context); ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); - List importedModules = new ArrayList(context.getImportedModules().keySet()); + List importedModules = new ArrayList(); + for (StaticContext.ImportedModule module : context.getImportedModules()) { + importedModules.add(module.getExternalName()); + } return new TranslationResult.Success(config, files, program, diagnostics, importedModules, moduleDescriptor, bindingTrace.getBindingContext()); } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java index 86fc5618520..33b86426342 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java @@ -121,9 +121,9 @@ public final class StaticContext { private final Map fqnCache = new HashMap(); @NotNull - private final Map importedModules = new LinkedHashMap(); + private final Map importedModules = new LinkedHashMap(); - private Map readOnlyImportedModules; + private Collection readOnlyImportedModules; @NotNull private final JsScope rootPackageScope; @@ -166,6 +166,10 @@ public final class StaticContext { this.currentModule = moduleDescriptor; this.rootFunction = rootFunction; rootPackageScope = new JsObjectScope(rootScope, "", "root-package"); + + JsName kotlinName = rootScope.declareName(Namer.KOTLIN_NAME); + importedModules.put(new ImportedModuleKey(Namer.KOTLIN_LOWER_NAME, null), + new ImportedModule(Namer.KOTLIN_LOWER_NAME, kotlinName, null)); } @NotNull @@ -194,9 +198,9 @@ public final class StaticContext { } @NotNull - public Map getImportedModules() { + public Collection getImportedModules() { if (readOnlyImportedModules == null) { - readOnlyImportedModules = Collections.unmodifiableMap(importedModules); + readOnlyImportedModules = Collections.unmodifiableCollection(importedModules.values()); } return readOnlyImportedModules; } @@ -253,14 +257,7 @@ public final class StaticContext { if (config.getModuleKind() != ModuleKind.PLAIN) { String moduleName = AnnotationsUtils.getModuleName(suggested.getDescriptor()); if (moduleName != null) { - return JsAstUtils.pureFqn(getModuleInternalName(moduleName), null); - } - - if (isNativeObject(suggested.getDescriptor())) { - String fileModuleName = AnnotationsUtils.getFileModuleName(getBindingContext(), suggested.getDescriptor()); - if (fileModuleName != null) { - return pureFqn(getNameForDescriptor(suggested.getDescriptor()), pureFqn(getModuleInternalName(fileModuleName), null)); - } + return JsAstUtils.pureFqn(getImportedModule(moduleName, suggested.getDescriptor()).internalName, null); } } @@ -279,6 +276,14 @@ public final class StaticContext { expression = getQualifiedExpression(suggested.getScope()); } + if (isNativeObject(suggested.getDescriptor())) { + String fileModuleName = AnnotationsUtils.getFileModuleName(getBindingContext(), suggested.getDescriptor()); + if (fileModuleName != null) { + JsName moduleJsName = getImportedModule(fileModuleName, null).internalName; + expression = pureFqn(moduleJsName, null); + } + } + for (JsName partName : partNames) { expression = new JsNameRef(partName, expression); applySideEffects(expression, suggested.getDescriptor()); @@ -611,18 +616,28 @@ public final class StaticContext { if (UNKNOWN_EXTERNAL_MODULE_NAME.equals(moduleName)) return null; - return getModuleInternalName(moduleName); + return getImportedModule(moduleName, null).getInternalName(); } @NotNull - private JsName getModuleInternalName(@NotNull String baseName) { - JsName moduleId = baseName.equals(Namer.KOTLIN_LOWER_NAME) ? rootScope.declareName(Namer.KOTLIN_NAME) : - importedModules.get(baseName); - if (moduleId == null) { - moduleId = rootScope.declareFreshName(Namer.LOCAL_MODULE_PREFIX + Namer.suggestedModuleName(baseName)); - importedModules.put(baseName, moduleId); + private ImportedModule getImportedModule(@NotNull String baseName, @Nullable DeclarationDescriptor descriptor) { + ImportedModuleKey key = new ImportedModuleKey(baseName, descriptor); + + ImportedModule module = importedModules.get(key); + if (module == null) { + JsName internalName = rootScope.declareFreshName(Namer.LOCAL_MODULE_PREFIX + Namer.suggestedModuleName(baseName)); + JsName plainName = descriptor != null ? rootScope.declareName(getPlainId(descriptor)) : null; + module = new ImportedModule(baseName, internalName, plainName != null ? pureFqn(plainName, null) : null); + importedModules.put(key, module); } - return moduleId; + return module; + } + + @NotNull + private String getPlainId(@NotNull DeclarationDescriptor declaration) { + SuggestedName suggestedName = nameSuggestion.suggest(declaration); + assert suggestedName != null : "Declaration should not be ModuleDescriptor, therefore suggestedName should be non-null"; + return suggestedName.getNames().get(0); } private static JsExpression applySideEffects(JsExpression expression, DeclarationDescriptor descriptor) { @@ -742,4 +757,69 @@ public final class StaticContext { } return false; } + + public static class ImportedModule { + @NotNull + private final String externalName; + + @NotNull + private final JsName internalName; + + @Nullable + private final JsExpression plainReference; + + ImportedModule(@NotNull String externalName, @NotNull JsName internalName, @Nullable JsExpression plainReference) { + this.externalName = externalName; + this.internalName = internalName; + this.plainReference = plainReference; + } + + @NotNull + public String getExternalName() { + return externalName; + } + + @NotNull + public JsName getInternalName() { + return internalName; + } + + @Nullable + public JsExpression getPlainReference() { + return plainReference; + } + } + + private static class ImportedModuleKey { + @NotNull + private final String baseName; + + @Nullable + private final DeclarationDescriptor declaration; + + public ImportedModuleKey(@NotNull String baseName, @Nullable DeclarationDescriptor declaration) { + this.baseName = baseName; + this.declaration = declaration; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ImportedModuleKey key = (ImportedModuleKey) o; + + if (!baseName.equals(key.baseName)) return false; + if (declaration != null ? !declaration.equals(key.declaration) : key.declaration != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = baseName.hashCode(); + result = 31 * result + (declaration != null ? declaration.hashCode() : 0); + return result; + } + } } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java index 5cc6c051704..4a0a9f79c9d 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java @@ -94,7 +94,7 @@ public class TranslationContext { } @NotNull - public Map getImportedModules() { + public Collection getImportedModules() { return staticContext.getImportedModules(); } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/ModuleWrapperTranslation.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/ModuleWrapperTranslation.kt index 88d6702387b..d9e25a3764b 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/ModuleWrapperTranslation.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/ModuleWrapperTranslation.kt @@ -18,12 +18,13 @@ package org.jetbrains.kotlin.js.translate.general import com.google.dart.compiler.backend.js.ast.* import org.jetbrains.kotlin.js.translate.context.Namer +import org.jetbrains.kotlin.js.translate.context.StaticContext import org.jetbrains.kotlin.js.translate.utils.JsAstUtils import org.jetbrains.kotlin.serialization.js.ModuleKind object ModuleWrapperTranslation { @JvmStatic fun wrapIfNecessary( - moduleId: String, function: JsExpression, importedModules: List, + moduleId: String, function: JsExpression, importedModules: List, program: JsProgram, kind: ModuleKind ): List { return when (kind) { @@ -36,7 +37,7 @@ object ModuleWrapperTranslation { private fun wrapUmd( moduleId: String, function: JsExpression, - importedModules: List, program: JsProgram + importedModules: List, program: JsProgram ): List { val scope = program.scope val defineName = scope.declareName("define") @@ -78,13 +79,13 @@ object ModuleWrapperTranslation { private fun wrapAmd( moduleId: String,function: JsExpression, - importedModules: List, program: JsProgram + importedModules: List, program: JsProgram ): List { val scope = program.scope val defineName = scope.declareName("define") val invocationArgs = listOf( program.getStringLiteral(moduleId), - JsArrayLiteral(listOf(program.getStringLiteral("exports")) + importedModules.map { program.getStringLiteral(it) }), + JsArrayLiteral(listOf(program.getStringLiteral("exports")) + importedModules.map { program.getStringLiteral(it.externalName) }), function ) @@ -92,19 +93,23 @@ object ModuleWrapperTranslation { return listOf(invocation.makeStmt()) } - private fun wrapCommonJs(function: JsExpression, importedModules: List, program: JsProgram): List { + private fun wrapCommonJs( + function: JsExpression, + importedModules: List, + program: JsProgram + ): List { val scope = program.scope val moduleName = scope.declareName("module") val requireName = scope.declareName("require") - val invocationArgs = importedModules.map { JsInvocation(requireName.makeRef(), program.getStringLiteral(it)) } + val invocationArgs = importedModules.map { JsInvocation(requireName.makeRef(), program.getStringLiteral(it.externalName)) } val invocation = JsInvocation(function, listOf(JsNameRef("exports", moduleName.makeRef())) + invocationArgs) return listOf(invocation.makeStmt()) } private fun wrapPlain( moduleId: String, function: JsExpression, - importedModules: List, program: JsProgram + importedModules: List, program: JsProgram ): List { val invocation = makePlainInvocation(moduleId, function, importedModules, program) val statements = mutableListOf() @@ -126,13 +131,13 @@ object ModuleWrapperTranslation { private fun addModuleValidation( currentModuleId: String, program: JsProgram, - moduleName: String + module: StaticContext.ImportedModule ): JsStatement { - val moduleRef = makePlainModuleRef(moduleName, program) + val moduleRef = makePlainModuleRef(module, program) val moduleExistsCond = JsAstUtils.typeOfIs(moduleRef, program.getStringLiteral("undefined")) val moduleNotFoundMessage = program.getStringLiteral( - "Error loading module '" + currentModuleId + "'. Its dependency '" + moduleName + "' was not found. " + - "Please, check whether '" + moduleName + "' is loaded prior to '" + currentModuleId + "'.") + "Error loading module '" + currentModuleId + "'. Its dependency '" + module.externalName + "' was not found. " + + "Please, check whether '" + module.externalName + "' is loaded prior to '" + currentModuleId + "'.") val moduleNotFoundThrow = JsThrow(JsNew(JsNameRef("Error"), listOf(moduleNotFoundMessage))) return JsIf(moduleExistsCond, JsBlock(moduleNotFoundThrow)) } @@ -140,7 +145,7 @@ object ModuleWrapperTranslation { private fun makePlainInvocation( moduleId: String, function: JsExpression, - importedModules: List, + importedModules: List, program: JsProgram ): JsInvocation { val invocationArgs = importedModules.map { makePlainModuleRef(it, program) } @@ -151,6 +156,10 @@ object ModuleWrapperTranslation { return JsInvocation(function, listOf(selfArg) + invocationArgs) } + private fun makePlainModuleRef(module: StaticContext.ImportedModule, program: JsProgram): JsExpression { + return module.plainReference ?: makePlainModuleRef(module.externalName, program) + } + private fun makePlainModuleRef(moduleId: String, program: JsProgram): JsExpression { // TODO: we could use `this.moduleName` syntax. However, this does not work for `kotlin` module in Rhino, since // we run kotlin.js in a parent scope. Consider better solution diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java index cef9f941463..8255e9044a6 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java @@ -274,13 +274,10 @@ public final class Translation { // Invoke function passing modules as arguments // This should help minifier tool to recognize references to these modules as local variables and make them shorter. - List importedModuleList = new ArrayList(); - JsName kotlinName = program.getScope().declareName(Namer.KOTLIN_NAME); - rootFunction.getParameters().add(new JsParameter((kotlinName))); - importedModuleList.add(Namer.KOTLIN_LOWER_NAME); + List importedModuleList = new ArrayList(); - for (String importedModule : staticContext.getImportedModules().keySet()) { - rootFunction.getParameters().add(new JsParameter(staticContext.getImportedModules().get(importedModule))); + for (StaticContext.ImportedModule importedModule : staticContext.getImportedModules()) { + rootFunction.getParameters().add(new JsParameter(importedModule.getInternalName())); importedModuleList.add(importedModule); } diff --git a/js/js.translator/testData/box/jsModule/externalFunctionUmdFallback.js b/js/js.translator/testData/box/jsModule/externalFunctionUmdFallback.js index c13cd43caa0..0d72a9a8d06 100644 --- a/js/js.translator/testData/box/jsModule/externalFunctionUmdFallback.js +++ b/js/js.translator/testData/box/jsModule/externalFunctionUmdFallback.js @@ -1,3 +1,6 @@ -function lib(y) { +function foo(y) { return 23 + y; +} +function baz(y) { + return 42 + y; } \ No newline at end of file diff --git a/js/js.translator/testData/box/jsModule/externalFunctionUmdFallback.kt b/js/js.translator/testData/box/jsModule/externalFunctionUmdFallback.kt index d9e1182d55c..40ca3978d0c 100644 --- a/js/js.translator/testData/box/jsModule/externalFunctionUmdFallback.kt +++ b/js/js.translator/testData/box/jsModule/externalFunctionUmdFallback.kt @@ -2,11 +2,17 @@ // NO_JS_MODULE_SYSTEM package foo -@JsModule("lib") +@JsModule("libfoo") @JsNonModule @native fun foo(x: Int): Int = noImpl +@JsModule("libbar") +@JsNonModule +@JsName("baz") +@native fun bar(x: Int): Int = noImpl + fun box(): String { assertEquals(65, foo(42)) + assertEquals(142, bar(100)) return "OK" } \ No newline at end of file diff --git a/js/js.translator/testData/box/jsModule/externalPackagePlain.js b/js/js.translator/testData/box/jsModule/externalPackagePlain.js new file mode 100644 index 00000000000..4437093dbac --- /dev/null +++ b/js/js.translator/testData/box/jsModule/externalPackagePlain.js @@ -0,0 +1,29 @@ +var lib = function() { + function A(x) { + this.x = x; + } + A.prototype.foo = function (y) { + return this.x + y; + }; + + B = { + x: 123, + foo: function(y) { + return this.x + y; + } + }; + + function foo(y) { + return 323 + y; + } + + var bar = 423; + + return { + A: A, + B: B, + foo: foo, + bar: bar, + mbar: -1 + }; +}(); \ No newline at end of file diff --git a/js/js.translator/testData/box/jsModule/externalPackagePlain.kt b/js/js.translator/testData/box/jsModule/externalPackagePlain.kt new file mode 100644 index 00000000000..827a5ae2a78 --- /dev/null +++ b/js/js.translator/testData/box/jsModule/externalPackagePlain.kt @@ -0,0 +1,36 @@ +@file:JsModule("lib") +@file:JsNonModule +package foo + +@native class A(@native val x: Int = noImpl) { + @native fun foo(y: Int): Int = noImpl +} + +@native object B { + @native val x: Int = noImpl + + @native fun foo(y: Int): Int = noImpl +} + +@native fun foo(y: Int): Int = noImpl + +@native val bar: Int = noImpl + +@native var mbar: Int = noImpl + +fun box(): String { + val a = A(23) + assertEquals(23, a.x) + assertEquals(65, a.foo(42)) + + assertEquals(123, B.x) + assertEquals(265, B.foo(142)) + + assertEquals(365, foo(42)) + assertEquals(423, bar) + + mbar = 523 + assertEquals(523, mbar) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/jsModule/externalPackageUmdFallback.js b/js/js.translator/testData/box/jsModule/externalPackageUmdFallback.js new file mode 100644 index 00000000000..4437093dbac --- /dev/null +++ b/js/js.translator/testData/box/jsModule/externalPackageUmdFallback.js @@ -0,0 +1,29 @@ +var lib = function() { + function A(x) { + this.x = x; + } + A.prototype.foo = function (y) { + return this.x + y; + }; + + B = { + x: 123, + foo: function(y) { + return this.x + y; + } + }; + + function foo(y) { + return 323 + y; + } + + var bar = 423; + + return { + A: A, + B: B, + foo: foo, + bar: bar, + mbar: -1 + }; +}(); \ No newline at end of file diff --git a/js/js.translator/testData/box/jsModule/externalPackageUmdFallback.kt b/js/js.translator/testData/box/jsModule/externalPackageUmdFallback.kt new file mode 100644 index 00000000000..9e96a0299c4 --- /dev/null +++ b/js/js.translator/testData/box/jsModule/externalPackageUmdFallback.kt @@ -0,0 +1,38 @@ +// MODULE_KIND: UMD +// NO_JS_MODULE_SYSTEM +@file:JsModule("lib") +@file:JsNonModule +package foo + +@native class A(@native val x: Int = noImpl) { + @native fun foo(y: Int): Int = noImpl +} + +@native object B { + @native val x: Int = noImpl + + @native fun foo(y: Int): Int = noImpl +} + +@native fun foo(y: Int): Int = noImpl + +@native val bar: Int = noImpl + +@native var mbar: Int = noImpl + +fun box(): String { + val a = A(23) + assertEquals(23, a.x) + assertEquals(65, a.foo(42)) + + assertEquals(123, B.x) + assertEquals(265, B.foo(142)) + + assertEquals(365, foo(42)) + assertEquals(423, bar) + + mbar = 523 + assertEquals(523, mbar) + + return "OK" +} \ No newline at end of file