[JS IR BE] Add JsExport annotations

This commit is contained in:
Svyatoslav Kuzmich
2019-06-27 15:12:14 +03:00
parent 685597d20a
commit 69962cbf8c
22 changed files with 91 additions and 31 deletions
@@ -48,6 +48,7 @@ class JsIrBackendContext(
override val irBuiltIns: IrBuiltIns, override val irBuiltIns: IrBuiltIns,
val symbolTable: SymbolTable, val symbolTable: SymbolTable,
irModuleFragment: IrModuleFragment, irModuleFragment: IrModuleFragment,
val additionalExportedDeclarations: Set<FqName>,
override val configuration: CompilerConfiguration override val configuration: CompilerConfiguration
) : CommonBackendContext { ) : CommonBackendContext {
@@ -275,6 +276,8 @@ class JsIrBackendContext(
val throwableConstructors by lazy { throwableClass.owner.declarations.filterIsInstance<IrConstructor>().map { it.symbol } } val throwableConstructors by lazy { throwableClass.owner.declarations.filterIsInstance<IrConstructor>().map { it.symbol } }
val defaultThrowableCtor by lazy { throwableConstructors.single { it.owner.valueParameters.size == 0 } } val defaultThrowableCtor by lazy { throwableConstructors.single { it.owner.valueParameters.size == 0 } }
private fun referenceOperators(): Map<Name, MutableMap<IrClassifierSymbol, IrSimpleFunctionSymbol>> { private fun referenceOperators(): Map<Name, MutableMap<IrClassifierSymbol, IrSimpleFunctionSymbol>> {
val primitiveIrSymbols = irBuiltIns.primitiveIrTypes.map { it.classifierOrFail as IrClassSymbol } val primitiveIrSymbols = irBuiltIns.primitiveIrTypes.map { it.classifierOrFail as IrClassSymbol }
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
import org.jetbrains.kotlin.ir.util.patchDeclarationParents import org.jetbrains.kotlin.ir.util.patchDeclarationParents
import org.jetbrains.kotlin.library.KotlinLibrary import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.utils.DFS import org.jetbrains.kotlin.utils.DFS
@@ -34,7 +35,8 @@ fun compile(
phaseConfig: PhaseConfig, phaseConfig: PhaseConfig,
allDependencies: List<KotlinLibrary>, allDependencies: List<KotlinLibrary>,
friendDependencies: List<KotlinLibrary>, friendDependencies: List<KotlinLibrary>,
mainArguments: List<String>? mainArguments: List<String>?,
exportedDeclarations: Set<FqName> = emptySet()
): String { ): String {
val (moduleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer) = val (moduleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer) =
loadIr(project, files, configuration, allDependencies, friendDependencies) loadIr(project, files, configuration, allDependencies, friendDependencies)
@@ -43,7 +45,7 @@ fun compile(
val mainFunction = JsMainFunctionDetector.getMainFunctionOrNull(moduleFragment) val mainFunction = JsMainFunctionDetector.getMainFunctionOrNull(moduleFragment)
val context = JsIrBackendContext(moduleDescriptor, irBuiltIns, symbolTable, moduleFragment, configuration) val context = JsIrBackendContext(moduleDescriptor, irBuiltIns, symbolTable, moduleFragment, exportedDeclarations, configuration)
// Load declarations referenced during `context` initialization // Load declarations referenced during `context` initialization
dependencyModules.forEach { dependencyModules.forEach {
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.utils.* import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.isEffectivelyExternal import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
import org.jetbrains.kotlin.ir.util.isObject import org.jetbrains.kotlin.ir.util.isObject
import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.*
@@ -94,6 +95,9 @@ class IrModuleToJsTransformer(
return null return null
} }
if (!declaration.isExported())
return null
if (declaration.isEffectivelyExternal()) if (declaration.isEffectivelyExternal())
return null return null
@@ -278,4 +282,28 @@ class IrModuleToJsTransformer(
declarationHandler declarationHandler
) )
} }
fun IrDeclarationWithName.isExported(): Boolean {
if (fqNameWhenAvailable in backendContext.additionalExportedDeclarations)
return true
// Hack to support properties
val correspondingProperty = when {
this is IrField -> correspondingPropertySymbol
this is IrSimpleFunction -> correspondingPropertySymbol
else -> null
}
correspondingProperty?.let {
return it.owner.isExported()
}
if (isJsExport())
return true
return when (val parent = parent) {
is IrDeclarationWithName -> parent.isExported()
is IrAnnotationContainer -> parent.isJsExport()
else -> false
}
}
} }
@@ -5,8 +5,7 @@
package org.jetbrains.kotlin.ir.backend.js.utils package org.jetbrains.kotlin.ir.backend.js.utils
import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
import org.jetbrains.kotlin.ir.expressions.IrCall import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrConst import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
@@ -20,6 +19,7 @@ object JsAnnotations {
val jsNonModuleFqn = FqName("kotlin.js.JsNonModule") val jsNonModuleFqn = FqName("kotlin.js.JsNonModule")
val jsNameFqn = FqName("kotlin.js.JsName") val jsNameFqn = FqName("kotlin.js.JsName")
val jsQualifierFqn = FqName("kotlin.js.JsQualifier") val jsQualifierFqn = FqName("kotlin.js.JsQualifier")
val jsExportFqn = FqName("kotlin.js.JsExport")
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@@ -38,6 +38,8 @@ fun IrAnnotationContainer.getJsQualifier(): String? =
fun IrAnnotationContainer.getJsName(): String? = fun IrAnnotationContainer.getJsName(): String? =
getAnnotation(JsAnnotations.jsNameFqn)?.getSingleConstStringArgument() getAnnotation(JsAnnotations.jsNameFqn)?.getSingleConstStringArgument()
fun IrAnnotationContainer.isJsExport(): Boolean =
hasAnnotation(JsAnnotations.jsExportFqn)
fun IrDeclarationWithName.getJsNameOrKotlinName(): Name = fun IrDeclarationWithName.getJsNameOrKotlinName(): Name =
when (val jsName = getJsName()) { when (val jsName = getJsName()) {
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.JsConfig import org.jetbrains.kotlin.js.config.JsConfig
import org.jetbrains.kotlin.js.facade.MainCallParameters import org.jetbrains.kotlin.js.facade.MainCallParameters
import org.jetbrains.kotlin.js.facade.TranslationUnit import org.jetbrains.kotlin.js.facade.TranslationUnit
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.test.TargetBackend import org.jetbrains.kotlin.test.TargetBackend
import java.io.File import java.io.File
@@ -108,7 +109,8 @@ abstract class BasicIrBoxTest(
phaseConfig = phaseConfig, phaseConfig = phaseConfig,
allDependencies = allDependencies, allDependencies = allDependencies,
friendDependencies = emptyList(), friendDependencies = emptyList(),
mainArguments = mainCallParameters.run { if (shouldBeGenerated()) arguments() else null } mainArguments = mainCallParameters.run { if (shouldBeGenerated()) arguments() else null },
exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction)))
) )
val wrappedCode = wrapWithModuleEmulationMarkers(jsCode, moduleId = config.moduleId, moduleKind = config.moduleKind) val wrappedCode = wrapWithModuleEmulationMarkers(jsCode, moduleId = config.moduleId, moduleKind = config.moduleKind)
@@ -1520,11 +1520,6 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
runTest("js/js.translator/testData/box/expression/cast/checkThrowCCE.kt"); runTest("js/js.translator/testData/box/expression/cast/checkThrowCCE.kt");
} }
@TestMetadata("explicitUpcast.kt")
public void testExplicitUpcast() throws Exception {
runTest("js/js.translator/testData/box/expression/cast/explicitUpcast.kt");
}
@TestMetadata("implicitCastToLong.kt") @TestMetadata("implicitCastToLong.kt")
public void testImplicitCastToLong() throws Exception { public void testImplicitCastToLong() throws Exception {
runTest("js/js.translator/testData/box/expression/cast/implicitCastToLong.kt"); runTest("js/js.translator/testData/box/expression/cast/implicitCastToLong.kt");
@@ -1,11 +1,15 @@
// SKIP_MINIFICATION // SKIP_MINIFICATION
@JsName("foo") @JsName("foo")
@JsExport
fun foo(): Char = '1' fun foo(): Char = '1'
@JsExport
val p1: Char = '2' val p1: Char = '2'
@JsExport
var p2: Char = '3' var p2: Char = '3'
@JsExport
var p3: Char = '4' var p3: Char = '4'
get() = field + 1 get() = field + 1
set(value) { set(value) {
@@ -7,6 +7,7 @@
// MODULE: non-identifier-module-name // MODULE: non-identifier-module-name
// FILE: lib.kt // FILE: lib.kt
@JsName("foo") @JsName("foo")
@JsExport
public fun foo(k: String): String = "O$k" public fun foo(k: String): String = "O$k"
// FILE: test.js // FILE: test.js
@@ -7,6 +7,7 @@
// MODULE: if // MODULE: if
// FILE: lib.kt // FILE: lib.kt
@JsName("foo") @JsName("foo")
@JsExport
public fun foo(k: String): String = "O$k" public fun foo(k: String): String = "O$k"
// FILE: test.js // FILE: test.js
@@ -2,6 +2,9 @@
// SKIP_MINIFICATION // SKIP_MINIFICATION
// This test assumes that external JS code calls Kotlin code directly // This test assumes that external JS code calls Kotlin code directly
// Legacy export, wrong types
// DONT_TARGET_EXACT_BACKEND: JS_IR
open class A open class A
class B : A() class B : A()
@@ -11,6 +11,7 @@ inline fun ok(): I {
} }
@JsName("convolutedOk") @JsName("convolutedOk")
@JsExport
inline fun convolutedOk(): I { inline fun convolutedOk(): I {
val fail = object : I { val fail = object : I {
override fun ok() = "fail" override fun ok() = "fail"
+1
View File
@@ -1,5 +1,6 @@
// EXPECTED_REACHABLE_NODES: 1290 // EXPECTED_REACHABLE_NODES: 1290
@JsExport
data class A(val value: Int) data class A(val value: Int)
fun box(): String { fun box(): String {
+4 -4
View File
@@ -6,10 +6,10 @@ object A {
@JsName("js_property") val f: String get() = "property" @JsName("js_property") val f: String get() = "property"
} }
fun test() = js(""" fun test(): dynamic {
var a = JS_TESTS.A; val a = A.asDynamic()
return a.js_method() + ";" + a.js_property; return a.js_method() + ";" + a.js_property
""") }
fun box(): String { fun box(): String {
val result = test() val result = test()
+3 -4
View File
@@ -8,10 +8,9 @@ class B : A() {
override fun f(x: Int) = "B.f($x)" override fun f(x: Int) = "B.f($x)"
} }
fun test() = js(""" fun test(): dynamic {
var module = JS_TESTS; return A().asDynamic().js_f(23) + ";" + B().asDynamic().js_f(42)
return new (module.A)().js_f(23) + ";" + new (module.B)().js_f(42); }
""")
fun box(): String { fun box(): String {
val result = test() val result = test()
@@ -8,10 +8,9 @@ class B : A {
override fun f(x: Int) = "B.f($x)" override fun f(x: Int) = "B.f($x)"
} }
fun test() = js(""" fun test(): dynamic {
var module = JS_TESTS; return B().asDynamic().js_f(23)
return new (module.B)().js_f(23); }
""")
fun box(): String { fun box(): String {
val result = test() val result = test()
+4 -4
View File
@@ -10,10 +10,10 @@ object A {
@JsName("js_q") val q: String get() = "q" @JsName("js_q") val q: String get() = "q"
} }
fun test() = js(""" fun test(): dynamic {
var a = JS_TESTS.A; var a = A.asDynamic()
return a.js_f(23) + ";" + a.js_g(42) + ";" + a.js_p + ";" + a.js_q; return a.js_f(23) + ";" + a.js_g(42) + ";" + a.js_p + ";" + a.js_q
""") }
fun box(): String { fun box(): String {
val result = test() val result = test()
+1
View File
@@ -1,5 +1,6 @@
// SKIP_MINIFICATION // SKIP_MINIFICATION
@JsExport
val top = "TOP LEVEL" val top = "TOP LEVEL"
fun box(): String { fun box(): String {
@@ -1,6 +1,7 @@
// SKIP_MINIFICATION // SKIP_MINIFICATION
// Contains calls from external JS code // Contains calls from external JS code
@JsExport
open class A { open class A {
@JsName("foo") @JsName("foo")
open protected fun foo(n: Int) = 23 open protected fun foo(n: Int) = 23
@@ -9,6 +10,7 @@ open class A {
fun bar(n: Int) = foo(n) + 100 fun bar(n: Int) = foo(n) + 100
} }
@JsExport
open class B { open class B {
@JsName("foo") @JsName("foo")
protected fun foo(n: Int) = 42 protected fun foo(n: Int) = 42
@@ -11,6 +11,7 @@ class A {
} }
} }
@JsExport
val A.z: Int val A.z: Int
@JsName("getZ_") get() = 42 @JsName("getZ_") get() = 42
@@ -1,8 +1,10 @@
// EXPECTED_REACHABLE_NODES: 1288 // EXPECTED_REACHABLE_NODES: 1288
@JsExport
val x: Int val x: Int
@JsName("getX_") get() = 23 @JsName("getX_") get() = 23
@JsExport
var y: Int = 0 var y: Int = 0
@JsName("getY_") get() = field + 10 @JsName("getY_") get() = field + 10
@JsName("setY_") set(value) { @JsName("setY_") set(value) {
@@ -27,10 +27,13 @@ fun box(): String {
if (!hasProp(b, "foo")) return "B hasn't foo" if (!hasProp(b, "foo")) return "B hasn't foo"
if (!hasProp(b, "boo")) return "B hasn't boo" if (!hasProp(b, "boo")) return "B hasn't boo"
val PREFIX = "_" // Legacy scheme exports interfaces
if (eval("$PREFIX.A") == null) return "$PREFIX.A not found" if (testUtils.isLegacyBackend()) {
if (eval("$PREFIX.B") == null) return "$PREFIX.B not found" val PREFIX = "_"
if (eval("$PREFIX.A === $PREFIX.B") as Boolean) return "A and B refer to the same object" if (eval("$PREFIX.A") == null) return "$PREFIX.A not found"
if (eval("$PREFIX.B") == null) return "$PREFIX.B not found"
if (eval("$PREFIX.A === $PREFIX.B") as Boolean) return "A and B refer to the same object"
}
return "OK" return "OK"
} }
@@ -160,3 +160,13 @@ public annotation class JsNonModule
@Retention(AnnotationRetention.BINARY) @Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.FILE) @Target(AnnotationTarget.FILE)
public annotation class JsQualifier(val value: String) public annotation class JsQualifier(val value: String)
/**
* Exports top-level declaration.
*
* Used in future IR-based backend.
* Has no effect in current JS backend.
*/
@Retention(AnnotationRetention.BINARY)
@Target(CLASS, PROPERTY, FUNCTION, FILE)
public annotation class JsExport