JS BE: expose imported modules via $$importsForInline$$ property

... and use it as prefix to FQN in inline functions. This allows
to properly inline function declared in module A to module B,
when this function calls another function in module C.

See KT-18201
This commit is contained in:
Alexey Andreev
2017-05-31 17:49:25 +03:00
parent 978661c53d
commit 1df6f2f9a0
13 changed files with 346 additions and 50 deletions
@@ -349,25 +349,30 @@ class NameSuggestion {
val first = name.first().let { if (it.isES5IdentifierStart()) it else '_' }
return first.toString() + name.drop(1).map { if (it.isES5IdentifierPart()) it else '_' }.joinToString("")
}
// See ES 5.1 spec: https://www.ecma-international.org/ecma-262/5.1/#sec-7.6
private fun Char.isES5IdentifierStart() =
Character.isLetter(this) || // Lu | Ll | Lt | Lm | Lo
Character.getType(this).toByte() == Character.LETTER_NUMBER ||
// Nl which is missing in Character.isLetter, but present in UnicodeLetter in spec
this == '_' ||
this == '$'
private fun Char.isES5IdentifierPart() =
isES5IdentifierStart() ||
when (Character.getType(this).toByte()) {
Character.NON_SPACING_MARK,
Character.COMBINING_SPACING_MARK,
Character.DECIMAL_DIGIT_NUMBER,
Character.CONNECTOR_PUNCTUATION -> true
else -> false
} ||
this == '\u200C' || // Zero-width non-joiner
this == '\u200D' // Zero-width joiner
}
}
// See ES 5.1 spec: https://www.ecma-international.org/ecma-262/5.1/#sec-7.6
fun Char.isES5IdentifierStart() =
Character.isLetter(this) || // Lu | Ll | Lt | Lm | Lo
Character.getType(this).toByte() == Character.LETTER_NUMBER ||
// Nl which is missing in Character.isLetter, but present in UnicodeLetter in spec
this == '_' ||
this == '$'
fun Char.isES5IdentifierPart() =
isES5IdentifierStart() ||
when (Character.getType(this).toByte()) {
Character.NON_SPACING_MARK,
Character.COMBINING_SPACING_MARK,
Character.DECIMAL_DIGIT_NUMBER,
Character.CONNECTOR_PUNCTUATION -> true
else -> false
} ||
this == '\u200C' || // Zero-width non-joiner
this == '\u200D' // Zero-width joiner
fun String.isValidES5Identifier() =
isNotEmpty() &&
first().isES5IdentifierStart() &&
drop(1).all { it.isES5IdentifierPart() }
@@ -821,6 +821,30 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
doTest(fileName);
}
@TestMetadata("inlineJsModule.kt")
public void testInlineJsModule() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/crossModuleRef/inlineJsModule.kt");
doTest(fileName);
}
@TestMetadata("inlineJsModulePackage.kt")
public void testInlineJsModulePackage() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/crossModuleRef/inlineJsModulePackage.kt");
doTest(fileName);
}
@TestMetadata("inlineModule.kt")
public void testInlineModule() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/crossModuleRef/inlineModule.kt");
doTest(fileName);
}
@TestMetadata("inlineModuleNonIndentifier.kt")
public void testInlineModuleNonIndentifier() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/crossModuleRef/inlineModuleNonIndentifier.kt");
doTest(fileName);
}
@TestMetadata("lambda.kt")
public void testLambda() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/crossModuleRef/lambda.kt");
@@ -3497,6 +3521,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
doTest(fileName);
}
@TestMetadata("multipleReimport.kt")
public void testMultipleReimport() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/incremental/multipleReimport.kt");
doTest(fileName);
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/incremental/simple.kt");
@@ -19,17 +19,13 @@ package org.jetbrains.kotlin.js.translate.context
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.isEffectivelyInlineOnly
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.exportedPackage
import org.jetbrains.kotlin.js.backend.ast.metadata.exportedTag
import org.jetbrains.kotlin.js.backend.ast.metadata.staticRef
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
import org.jetbrains.kotlin.js.translate.utils.*
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.isLibraryObject
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.isNativeObject
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.assignment
import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.DescriptorUtils
@@ -138,13 +134,9 @@ internal class DeclarationExporter(val context: StaticContext) {
var name = localPackageNames[packageName]
if (name == null) {
name = JsScope.declareTemporaryName("package$" + packageName.shortName().asString())
localPackageNames.put(packageName, name)
val parentRef = getLocalPackageReference(packageName.parent())
val selfRef = JsNameRef(packageName.shortName().asString(), parentRef)
val rhs = JsAstUtils.or(selfRef, assignment(selfRef.deepCopy(), JsObjectLiteral(false)))
statements.add(JsAstUtils.newVar(name, rhs).apply { exportedPackage = packageName.asString() })
localPackageNames[packageName] = name
statements += definePackageAlias(packageName.shortName().asString(), name, packageName.asString(),
getLocalPackageReference(packageName.parent()))
}
return name.makeRef()
}
@@ -115,6 +115,8 @@ public final class Namer {
public static final String ENUM_NAME_FIELD = "name$";
public static final String ENUM_ORDINAL_FIELD = "ordinal$";
public static final String IMPORTS_FOR_INLINE_PROPERTY = "$$importsForInline$$";
@NotNull
public static String getFunctionTag(@NotNull CallableDescriptor functionDescriptor, @NotNull JsConfig config) {
String intrinsicTag = ArrayFIF.INSTANCE.getTag(functionDescriptor, config);
@@ -53,7 +53,10 @@ import org.jetbrains.kotlin.serialization.js.ModuleKind;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.typeUtil.TypeUtilsKt;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.jetbrains.kotlin.js.config.JsConfig.UNKNOWN_EXTERNAL_MODULE_NAME;
import static org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.isLibraryObject;
@@ -135,6 +138,9 @@ public final class StaticContext {
@NotNull
private final ClassModelGenerator classModelGenerator;
@Nullable
private JsName nameForImportsForInline;
public StaticContext(
@NotNull BindingTrace bindingTrace,
@NotNull JsConfig config,
@@ -230,6 +236,11 @@ public final class StaticContext {
return fqn.deepCopy();
}
@Nullable
public SuggestedName suggestName(@NotNull DeclarationDescriptor descriptor) {
return nameSuggestion.suggest(descriptor);
}
@NotNull
private JsExpression buildQualifiedExpression(@NotNull DeclarationDescriptor descriptor) {
if (descriptor instanceof ClassDescriptor) {
@@ -265,7 +276,7 @@ public final class StaticContext {
}
}
SuggestedName suggested = nameSuggestion.suggest(descriptor);
SuggestedName suggested = suggestName(descriptor);
if (suggested == null) {
ModuleDescriptor module = DescriptorUtils.getContainingModule(descriptor);
JsExpression result = getModuleExpressionFor(module);
@@ -686,14 +697,7 @@ public final class StaticContext {
if (currentModule == module) {
return rootScope.declareName(Namer.getRootPackageName());
}
String moduleName;
if (module == module.getBuiltIns().getBuiltInsModule()) {
moduleName = Namer.KOTLIN_LOWER_NAME;
}
else {
moduleName = module.getName().asString();
moduleName = moduleName.substring(1, moduleName.length() - 1);
}
String moduleName = suggestModuleName(module);
if (UNKNOWN_EXTERNAL_MODULE_NAME.equals(moduleName)) return null;
@@ -701,7 +705,18 @@ public final class StaticContext {
}
@NotNull
private JsImportedModule getImportedModule(@NotNull String baseName, @Nullable DeclarationDescriptor descriptor) {
public static String suggestModuleName(@NotNull ModuleDescriptor module) {
if (module == module.getBuiltIns().getBuiltInsModule()) {
return Namer.KOTLIN_LOWER_NAME;
}
else {
String moduleName = module.getName().asString();
return moduleName.substring(1, moduleName.length() - 1);
}
}
@NotNull
public JsImportedModule getImportedModule(@NotNull String baseName, @Nullable DeclarationDescriptor descriptor) {
String plainName = descriptor != null && config.getModuleKind() == ModuleKind.UMD ? getPlainId(descriptor) : null;
JsImportedModuleKey key = new JsImportedModuleKey(baseName, plainName);
@@ -785,4 +800,17 @@ public final class StaticContext {
String tag = Namer.getFunctionTag(descriptor, config);
fragment.getInlineModuleMap().put(tag, getModuleExpressionFor(descriptor));
}
@NotNull
public JsName getNameForImportsForInline() {
if (nameForImportsForInline == null) {
JsName name = JsScope.declareTemporaryName(Namer.IMPORTS_FOR_INLINE_PROPERTY);
fragment.getNameBindings().add(new JsNameBinding(Namer.IMPORTS_FOR_INLINE_PROPERTY, name));
nameForImportsForInline = name;
return name;
}
else {
return nameForImportsForInline;
}
}
}
@@ -26,9 +26,14 @@ import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor;
import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
import org.jetbrains.kotlin.js.backend.ast.*;
import org.jetbrains.kotlin.js.backend.ast.metadata.MetadataProperties;
import org.jetbrains.kotlin.js.backend.ast.metadata.SideEffectKind;
import org.jetbrains.kotlin.js.config.JsConfig;
import org.jetbrains.kotlin.js.naming.NameSuggestionKt;
import org.jetbrains.kotlin.js.naming.SuggestedName;
import org.jetbrains.kotlin.js.translate.intrinsic.Intrinsics;
import org.jetbrains.kotlin.js.translate.reference.CallExpressionTranslator;
import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator;
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils;
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils;
import org.jetbrains.kotlin.name.Name;
@@ -38,12 +43,15 @@ import org.jetbrains.kotlin.resolve.BindingTrace;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver;
import org.jetbrains.kotlin.serialization.js.ModuleKind;
import java.util.*;
import static org.jetbrains.kotlin.js.descriptorUtils.DescriptorUtilsKt.isCoroutineLambda;
import static org.jetbrains.kotlin.js.translate.context.UsageTrackerKt.getNameForCapturedDescriptor;
import static org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.isNativeObject;
import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getDescriptorForElement;
import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.pureFqn;
/**
* All the info about the state of the translation process.
@@ -66,6 +74,8 @@ public class TranslationContext {
@Nullable
private final VariableDescriptor continuationParameterDescriptor;
private final Set<String> modulesImportedForInline = new HashSet<>();
@NotNull
public static TranslationContext rootContext(@NotNull StaticContext staticContext) {
DynamicContext rootDynamicContext = DynamicContext.rootContext(
@@ -265,15 +275,121 @@ public class TranslationContext {
@NotNull
public JsNameRef getQualifiedReference(@NotNull DeclarationDescriptor descriptor) {
if (descriptor instanceof MemberDescriptor && isFromCurrentModule(descriptor) && isPublicInlineFunction()) {
staticContext.export((MemberDescriptor) descriptor, true);
JsNameRef result = staticContext.getQualifiedReference(descriptor);
if (isPublicInlineFunction()) {
if (isFromCurrentModule(descriptor)) {
if (descriptor instanceof MemberDescriptor) {
staticContext.export((MemberDescriptor) descriptor, true);
}
}
else {
ModuleDescriptor module = DescriptorUtils.getContainingModule(descriptor);
ModuleDescriptor currentModule = staticContext.getCurrentModule();
if (module != currentModule && !isInlineFunction(descriptor)) {
result = exportModuleForInline(currentModule, module, result);
}
}
}
return result;
}
private boolean isInlineFunction(@NotNull DeclarationDescriptor descriptor) {
if (!(descriptor instanceof CallableDescriptor)) return false;
return CallExpressionTranslator.shouldBeInlined((CallableDescriptor) descriptor, this);
}
@NotNull
private JsNameRef exportModuleForInline(
@NotNull ModuleDescriptor currentModule, @NotNull ModuleDescriptor module,
@NotNull JsNameRef fqn
) {
if (currentModule.getBuiltIns().getBuiltInsModule() == module) return fqn;
String moduleName = StaticContext.suggestModuleName(module);
if (moduleName.equals(Namer.KOTLIN_LOWER_NAME)) return fqn;
return exportModuleForInline(currentModule, moduleName, staticContext.getInnerNameForDescriptor(module), fqn);
}
private JsNameRef exportModuleForInline(
@NotNull ModuleDescriptor currentModule,
@NotNull String moduleId, @NotNull JsName moduleName,
@NotNull JsNameRef fqn) {
JsExpression currentModuleRef = pureFqn(staticContext.getInnerNameForDescriptor(currentModule), null);
JsExpression importsRef = pureFqn(Namer.IMPORTS_FOR_INLINE_PROPERTY, currentModuleRef);
JsExpression currentImports = pureFqn(staticContext.getNameForImportsForInline(), null);
JsExpression moduleRef;
JsExpression lhsModuleRef;
if (NameSuggestionKt.isValidES5Identifier(moduleId)) {
moduleRef = pureFqn(moduleId, importsRef);
lhsModuleRef = pureFqn(moduleId, currentImports);
}
else {
moduleRef = new JsArrayAccess(importsRef, new JsStringLiteral(moduleId));
MetadataProperties.setSideEffects(moduleRef, SideEffectKind.PURE);
lhsModuleRef = new JsArrayAccess(currentImports, new JsStringLiteral(moduleId));
}
fqn = (JsNameRef) replaceModuleReference(fqn, moduleName, moduleRef);
if (modulesImportedForInline.add(moduleId)) {
JsExpressionStatement importStmt = new JsExpressionStatement(JsAstUtils.assignment(lhsModuleRef, moduleName.makeRef()));
MetadataProperties.setExportedTag(importStmt, "imports:" + moduleId);
staticContext.getFragment().getExportBlock().getStatements().add(importStmt);
}
return fqn;
}
private static JsExpression replaceModuleReference(
@NotNull JsExpression expression,
@NotNull JsName expectedModuleName,
@NotNull JsExpression reexportExpr
) {
if (expression instanceof JsNameRef) {
JsNameRef nameRef = (JsNameRef) expression;
if (nameRef.getQualifier() == null) {
return expectedModuleName == nameRef.getName() ? reexportExpr : expression;
}
else {
JsExpression newQualifier = replaceModuleReference(nameRef.getQualifier(), expectedModuleName, reexportExpr);
if (newQualifier == nameRef.getQualifier()) {
return expression;
}
JsExpression result = nameRef.getName() != null ?
new JsNameRef(nameRef.getName(), newQualifier) :
new JsNameRef(nameRef.getIdent(), newQualifier);
result.copyMetadataFrom(nameRef);
return result;
}
}
else {
return expression;
}
return staticContext.getQualifiedReference(descriptor);
}
@NotNull
public JsNameRef getInnerReference(@NotNull DeclarationDescriptor descriptor) {
return JsAstUtils.pureFqn(getInnerNameForDescriptor(descriptor), null);
JsNameRef result = pureFqn(getInnerNameForDescriptor(descriptor), null);
SuggestedName suggested = staticContext.suggestName(descriptor);
if (suggested != null && getConfig().getModuleKind() != ModuleKind.PLAIN && isPublicInlineFunction()) {
String moduleId = AnnotationsUtils.getModuleName(suggested.getDescriptor());
if (moduleId != null && result.getName() != null && result.getQualifier() == null) {
result = exportModuleForInline(getCurrentModule(), moduleId, result.getName(), result);
}
else if (isNativeObject(suggested.getDescriptor()) && DescriptorUtils.isTopLevelDeclaration(suggested.getDescriptor())) {
String fileModuleId = AnnotationsUtils.getFileModuleName(bindingContext(), suggested.getDescriptor());
if (fileModuleId != null) {
JsName fileModuleName = staticContext.getImportedModule(fileModuleId, null).getInternalName();
result = exportModuleForInline(getCurrentModule(), fileModuleId, fileModuleName,
staticContext.getQualifiedReference(descriptor));
}
}
}
return result;
}
@NotNull
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.js.backend.ast.metadata.exportedPackage
import org.jetbrains.kotlin.js.backend.ast.metadata.exportedTag
import org.jetbrains.kotlin.js.translate.context.Namer
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.utils.definePackageAlias
class Merger(private val rootFunction: JsFunction, val internalModuleName: JsName, val module: ModuleDescriptor) {
// Maps unique signature (see generateSignature) to names
@@ -134,8 +135,8 @@ class Merger(private val rootFunction: JsFunction, val internalModuleName: JsNam
fragment.inlineModuleMap.forEach { (_, value) -> rename(value) }
}
private fun <T: JsNode> Map<JsName, JsName>.rename(node: T): T {
node.accept(object : RecursiveJsVisitor() {
private fun <T: JsNode> Map<JsName, JsName>.rename(rootNode: T): T {
rootNode.accept(object : RecursiveJsVisitor() {
override fun visitElement(node: JsNode) {
super.visitElement(node)
if (node is HasName) {
@@ -152,12 +153,13 @@ class Merger(private val rootFunction: JsFunction, val internalModuleName: JsNam
}
}
})
return node
return rootNode
}
// Adds different boilerplate code (like imports, class prototypes, etc) to resulting program.
fun merge() {
rootFunction.body.statements.apply {
addImportForInlineDeclarationIfNecessary()
this += importBlock.statements
addClassPrototypes(this)
this += declarationBlock.statements
@@ -167,6 +169,12 @@ class Merger(private val rootFunction: JsFunction, val internalModuleName: JsNam
}
}
private fun MutableList<JsStatement>.addImportForInlineDeclarationIfNecessary() {
val importsForInlineName = nameTable[Namer.IMPORTS_FOR_INLINE_PROPERTY] ?: return
this += definePackageAlias(Namer.IMPORTS_FOR_INLINE_PROPERTY, importsForInlineName, Namer.IMPORTS_FOR_INLINE_PROPERTY,
JsNameRef(Namer.getRootPackageName()))
}
private fun addClassPrototypes(statements: MutableList<JsStatement>) {
val visited = mutableSetOf<JsName>()
for (cls in classes.keys) {
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.CoroutineMetadata
import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata
import org.jetbrains.kotlin.js.backend.ast.metadata.exportedPackage
import org.jetbrains.kotlin.js.translate.context.Namer
import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.intrinsic.functions.basic.FunctionIntrinsicWithReceiverComputed
@@ -193,3 +194,10 @@ fun JsFunction.fillCoroutineMetadata(
psiElement = descriptor.source.getPsi()
)
}
fun definePackageAlias(name: String, varName: JsName, tag: String, parentRef: JsExpression): JsStatement {
val selfRef = JsNameRef(name, parentRef)
val rhs = JsAstUtils.or(selfRef, JsAstUtils.assignment(selfRef.deepCopy(), JsObjectLiteral(false)))
return JsAstUtils.newVar(varName, rhs).apply { exportedPackage = tag }
}
@@ -0,0 +1,25 @@
// EXPECTED_REACHABLE_NODES: 547
// MODULE: lib1
// FILE: lib1.js
define("lib1", [], function() {
return function() {
return "OK";
}
})
// MODULE: lib2(lib1)
// FILE: lib2.kt
// MODULE_KIND: AMD
@JsModule("lib1")
external fun foo(): String
// MODULE: lib3(lib2)
// FILE: lib3.kt
// MODULE_KIND: AMD
inline fun bar() = foo()
// MODULE: main(lib3)
// FILE: main.kt
// MODULE_KIND: AMD
fun box() = bar()
@@ -0,0 +1,28 @@
// EXPECTED_REACHABLE_NODES: 490
// MODULE: lib1
// FILE: lib1.js
define("lib1", [], function() {
return {
foo: function() {
return "OK";
}
};
})
// MODULE: lib2(lib1)
// FILE: lib2.kt
// MODULE_KIND: AMD
@file:JsModule("lib1")
external fun foo(): String
// MODULE: lib3(lib2)
// FILE: lib3.kt
// MODULE_KIND: AMD
inline fun bar() = foo()
// MODULE: main(lib3)
// FILE: main.kt
// MODULE_KIND: AMD
fun box() = bar()
@@ -0,0 +1,15 @@
// EXPECTED_REACHABLE_NODES: 489
// MODULE: lib1
// FILE: lib1.kt
fun foo() = "OK"
// MODULE: lib2(lib1)
// FILE: lib2.kt
inline fun bar() = foo()
// MODULE: main(lib1, lib2)
// FILE: main.kt
fun box() = bar()
@@ -0,0 +1,15 @@
// EXPECTED_REACHABLE_NODES: 489
// MODULE: 1
// FILE: lib1.kt
fun foo() = "OK"
// MODULE: 2(1)
// FILE: lib2.kt
inline fun bar() = foo()
// MODULE: main(2)
// FILE: main.kt
fun box() = bar()
@@ -0,0 +1,24 @@
// EXPECTED_REACHABLE_NODES: 491
// MODULE: lib1
// FILE: lib1.kt
fun foo() = "O"
fun bar() = "K"
// MODULE: lib2(lib1)
// FILE: lib2a.kt
// PROPERTY_WRITE_COUNT: name=lib1 count=1
// PROPERTY_WRITE_COUNT: name=$$imports$$ count=1
inline fun o() = foo()
// FILE: lib2b.kt
inline fun k() = bar()
// MODULE: main(lib2)
// FILE: main.kt
// RECOMPILE
fun box() = o() + k()