KT-12877: support plain reference to declaration in UMD wrapper, support plain reference to package

This commit is contained in:
Alexey Andreev
2016-07-05 11:45:33 +03:00
committed by Alexey Andreev
parent 495c876b3b
commit d63a727474
12 changed files with 285 additions and 42 deletions
@@ -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");
@@ -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<String> importedModules = new ArrayList<String>(context.getImportedModules().keySet());
List<String> importedModules = new ArrayList<String>();
for (StaticContext.ImportedModule module : context.getImportedModules()) {
importedModules.add(module.getExternalName());
}
return new TranslationResult.Success(config, files, program, diagnostics, importedModules, moduleDescriptor,
bindingTrace.getBindingContext());
}
@@ -121,9 +121,9 @@ public final class StaticContext {
private final Map<DeclarationDescriptor, JsExpression> fqnCache = new HashMap<DeclarationDescriptor, JsExpression>();
@NotNull
private final Map<String, JsName> importedModules = new LinkedHashMap<String, JsName>();
private final Map<ImportedModuleKey, ImportedModule> importedModules = new LinkedHashMap<ImportedModuleKey, ImportedModule>();
private Map<String, JsName> readOnlyImportedModules;
private Collection<ImportedModule> 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>", "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<String, JsName> getImportedModules() {
public Collection<ImportedModule> 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;
}
}
}
@@ -94,7 +94,7 @@ public class TranslationContext {
}
@NotNull
public Map<String, JsName> getImportedModules() {
public Collection<StaticContext.ImportedModule> getImportedModules() {
return staticContext.getImportedModules();
}
@@ -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<String>,
moduleId: String, function: JsExpression, importedModules: List<StaticContext.ImportedModule>,
program: JsProgram, kind: ModuleKind
): List<JsStatement> {
return when (kind) {
@@ -36,7 +37,7 @@ object ModuleWrapperTranslation {
private fun wrapUmd(
moduleId: String, function: JsExpression,
importedModules: List<String>, program: JsProgram
importedModules: List<StaticContext.ImportedModule>, program: JsProgram
): List<JsStatement> {
val scope = program.scope
val defineName = scope.declareName("define")
@@ -78,13 +79,13 @@ object ModuleWrapperTranslation {
private fun wrapAmd(
moduleId: String,function: JsExpression,
importedModules: List<String>, program: JsProgram
importedModules: List<StaticContext.ImportedModule>, program: JsProgram
): List<JsStatement> {
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<String>, program: JsProgram): List<JsStatement> {
private fun wrapCommonJs(
function: JsExpression,
importedModules: List<StaticContext.ImportedModule>,
program: JsProgram
): List<JsStatement> {
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<String>, program: JsProgram
importedModules: List<StaticContext.ImportedModule>, program: JsProgram
): List<JsStatement> {
val invocation = makePlainInvocation(moduleId, function, importedModules, program)
val statements = mutableListOf<JsStatement>()
@@ -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<JsExpression>(moduleNotFoundMessage)))
return JsIf(moduleExistsCond, JsBlock(moduleNotFoundThrow))
}
@@ -140,7 +145,7 @@ object ModuleWrapperTranslation {
private fun makePlainInvocation(
moduleId: String,
function: JsExpression,
importedModules: List<String>,
importedModules: List<StaticContext.ImportedModule>,
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
@@ -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<String> importedModuleList = new ArrayList<String>();
JsName kotlinName = program.getScope().declareName(Namer.KOTLIN_NAME);
rootFunction.getParameters().add(new JsParameter((kotlinName)));
importedModuleList.add(Namer.KOTLIN_LOWER_NAME);
List<StaticContext.ImportedModule> importedModuleList = new ArrayList<StaticContext.ImportedModule>();
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);
}
@@ -1,3 +1,6 @@
function lib(y) {
function foo(y) {
return 23 + y;
}
function baz(y) {
return 42 + y;
}
@@ -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"
}
@@ -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
};
}();
@@ -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"
}
@@ -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
};
}();
@@ -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"
}