diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt index 213a1c60f6e..fecb09b1c93 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt @@ -48,6 +48,7 @@ class JsIrBackendContext( override val irBuiltIns: IrBuiltIns, val symbolTable: SymbolTable, irModuleFragment: IrModuleFragment, + val additionalExportedDeclarations: Set, override val configuration: CompilerConfiguration ) : CommonBackendContext { @@ -275,6 +276,8 @@ class JsIrBackendContext( val throwableConstructors by lazy { throwableClass.owner.declarations.filterIsInstance().map { it.symbol } } val defaultThrowableCtor by lazy { throwableConstructors.single { it.owner.valueParameters.size == 0 } } + + private fun referenceOperators(): Map> { val primitiveIrSymbols = irBuiltIns.primitiveIrTypes.map { it.classifierOrFail as IrClassSymbol } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt index 2922631e84a..3324a7c158f 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.ir.declarations.IrModuleFragment import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator import org.jetbrains.kotlin.ir.util.patchDeclarationParents import org.jetbrains.kotlin.library.KotlinLibrary +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.utils.DFS @@ -34,7 +35,8 @@ fun compile( phaseConfig: PhaseConfig, allDependencies: List, friendDependencies: List, - mainArguments: List? + mainArguments: List?, + exportedDeclarations: Set = emptySet() ): String { val (moduleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer) = loadIr(project, files, configuration, allDependencies, friendDependencies) @@ -43,7 +45,7 @@ fun compile( 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 dependencyModules.forEach { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt index aff0b2db5e5..7322ad7ae74 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt @@ -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.declarations.* 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.isObject import org.jetbrains.kotlin.js.backend.ast.* @@ -94,6 +95,9 @@ class IrModuleToJsTransformer( return null } + if (!declaration.isExported()) + return null + if (declaration.isEffectivelyExternal()) return null @@ -278,4 +282,28 @@ class IrModuleToJsTransformer( 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 + } + } } \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt index 792b60af031..d37ab001f36 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt @@ -5,8 +5,7 @@ package org.jetbrains.kotlin.ir.backend.js.utils -import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer -import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName +import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.IrCall import org.jetbrains.kotlin.ir.expressions.IrConst import org.jetbrains.kotlin.ir.expressions.IrConstructorCall @@ -20,6 +19,7 @@ object JsAnnotations { val jsNonModuleFqn = FqName("kotlin.js.JsNonModule") val jsNameFqn = FqName("kotlin.js.JsName") val jsQualifierFqn = FqName("kotlin.js.JsQualifier") + val jsExportFqn = FqName("kotlin.js.JsExport") } @Suppress("UNCHECKED_CAST") @@ -38,6 +38,8 @@ fun IrAnnotationContainer.getJsQualifier(): String? = fun IrAnnotationContainer.getJsName(): String? = getAnnotation(JsAnnotations.jsNameFqn)?.getSingleConstStringArgument() +fun IrAnnotationContainer.isJsExport(): Boolean = + hasAnnotation(JsAnnotations.jsExportFqn) fun IrDeclarationWithName.getJsNameOrKotlinName(): Name = when (val jsName = getJsName()) { diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt index 99602444c0f..84eb1d99e82 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.js.config.JsConfig import org.jetbrains.kotlin.js.facade.MainCallParameters import org.jetbrains.kotlin.js.facade.TranslationUnit +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.test.TargetBackend import java.io.File @@ -108,7 +109,8 @@ abstract class BasicIrBoxTest( phaseConfig = phaseConfig, allDependencies = allDependencies, 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) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java index f6d261e4eed..9df05a978c5 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java @@ -1520,11 +1520,6 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { 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") public void testImplicitCastToLong() throws Exception { runTest("js/js.translator/testData/box/expression/cast/implicitCastToLong.kt"); diff --git a/js/js.translator/testData/box/char/topLevelCallables.kt b/js/js.translator/testData/box/char/topLevelCallables.kt index 28b232cbc42..02282b69c38 100644 --- a/js/js.translator/testData/box/char/topLevelCallables.kt +++ b/js/js.translator/testData/box/char/topLevelCallables.kt @@ -1,11 +1,15 @@ // SKIP_MINIFICATION @JsName("foo") +@JsExport fun foo(): Char = '1' +@JsExport val p1: Char = '2' +@JsExport var p2: Char = '3' +@JsExport var p3: Char = '4' get() = field + 1 set(value) { diff --git a/js/js.translator/testData/box/export/nonIndetifierModuleName.kt b/js/js.translator/testData/box/export/nonIndetifierModuleName.kt index bd4fda03c13..688ed6a5793 100644 --- a/js/js.translator/testData/box/export/nonIndetifierModuleName.kt +++ b/js/js.translator/testData/box/export/nonIndetifierModuleName.kt @@ -7,6 +7,7 @@ // MODULE: non-identifier-module-name // FILE: lib.kt @JsName("foo") +@JsExport public fun foo(k: String): String = "O$k" // FILE: test.js diff --git a/js/js.translator/testData/box/export/reservedModuleName.kt b/js/js.translator/testData/box/export/reservedModuleName.kt index e1caac333d6..5c0df6acf65 100644 --- a/js/js.translator/testData/box/export/reservedModuleName.kt +++ b/js/js.translator/testData/box/export/reservedModuleName.kt @@ -7,6 +7,7 @@ // MODULE: if // FILE: lib.kt @JsName("foo") +@JsExport public fun foo(k: String): String = "O$k" // FILE: test.js diff --git a/js/js.translator/testData/box/expression/cast/explicitUpcast.kt b/js/js.translator/testData/box/expression/cast/explicitUpcast.kt index 795c73e1c95..b9f179ee999 100644 --- a/js/js.translator/testData/box/expression/cast/explicitUpcast.kt +++ b/js/js.translator/testData/box/expression/cast/explicitUpcast.kt @@ -2,6 +2,9 @@ // SKIP_MINIFICATION // This test assumes that external JS code calls Kotlin code directly +// Legacy export, wrong types +// DONT_TARGET_EXACT_BACKEND: JS_IR + open class A class B : A() diff --git a/js/js.translator/testData/box/inline/inlinedObjectLiteralIsCheck.kt b/js/js.translator/testData/box/inline/inlinedObjectLiteralIsCheck.kt index 5aa9861bd04..8eece0a7b85 100644 --- a/js/js.translator/testData/box/inline/inlinedObjectLiteralIsCheck.kt +++ b/js/js.translator/testData/box/inline/inlinedObjectLiteralIsCheck.kt @@ -11,6 +11,7 @@ inline fun ok(): I { } @JsName("convolutedOk") +@JsExport inline fun convolutedOk(): I { val fail = object : I { override fun ok() = "fail" diff --git a/js/js.translator/testData/box/jsCode/operators.kt b/js/js.translator/testData/box/jsCode/operators.kt index e3880cea410..85baf167cab 100644 --- a/js/js.translator/testData/box/jsCode/operators.kt +++ b/js/js.translator/testData/box/jsCode/operators.kt @@ -1,5 +1,6 @@ // EXPECTED_REACHABLE_NODES: 1290 +@JsExport data class A(val value: Int) fun box(): String { diff --git a/js/js.translator/testData/box/jsName/avoidNameClash.kt b/js/js.translator/testData/box/jsName/avoidNameClash.kt index 7a713cacdad..899c2311570 100644 --- a/js/js.translator/testData/box/jsName/avoidNameClash.kt +++ b/js/js.translator/testData/box/jsName/avoidNameClash.kt @@ -6,10 +6,10 @@ object A { @JsName("js_property") val f: String get() = "property" } -fun test() = js(""" -var a = JS_TESTS.A; -return a.js_method() + ";" + a.js_property; -""") +fun test(): dynamic { + val a = A.asDynamic() + return a.js_method() + ";" + a.js_property +} fun box(): String { val result = test() diff --git a/js/js.translator/testData/box/jsName/overriddenMethod.kt b/js/js.translator/testData/box/jsName/overriddenMethod.kt index ee316c87748..070dd48ea0a 100644 --- a/js/js.translator/testData/box/jsName/overriddenMethod.kt +++ b/js/js.translator/testData/box/jsName/overriddenMethod.kt @@ -8,10 +8,9 @@ class B : A() { override fun f(x: Int) = "B.f($x)" } -fun test() = js(""" -var module = JS_TESTS; -return new (module.A)().js_f(23) + ";" + new (module.B)().js_f(42); -""") +fun test(): dynamic { + return A().asDynamic().js_f(23) + ";" + B().asDynamic().js_f(42) +} fun box(): String { val result = test() diff --git a/js/js.translator/testData/box/jsName/overridenFromInterface.kt b/js/js.translator/testData/box/jsName/overridenFromInterface.kt index aadb44c9c92..fa5367a7a6f 100644 --- a/js/js.translator/testData/box/jsName/overridenFromInterface.kt +++ b/js/js.translator/testData/box/jsName/overridenFromInterface.kt @@ -8,10 +8,9 @@ class B : A { override fun f(x: Int) = "B.f($x)" } -fun test() = js(""" -var module = JS_TESTS; -return new (module.B)().js_f(23); -""") +fun test(): dynamic { + return B().asDynamic().js_f(23) +} fun box(): String { val result = test() diff --git a/js/js.translator/testData/box/jsName/simpleJsName.kt b/js/js.translator/testData/box/jsName/simpleJsName.kt index 39fd7c7c65f..245e7a687d2 100644 --- a/js/js.translator/testData/box/jsName/simpleJsName.kt +++ b/js/js.translator/testData/box/jsName/simpleJsName.kt @@ -10,10 +10,10 @@ object A { @JsName("js_q") val q: String get() = "q" } -fun test() = js(""" -var a = JS_TESTS.A; -return a.js_f(23) + ";" + a.js_g(42) + ";" + a.js_p + ";" + a.js_q; -""") +fun test(): dynamic { + var a = A.asDynamic() + return a.js_f(23) + ";" + a.js_g(42) + ";" + a.js_p + ";" + a.js_q +} fun box(): String { val result = test() diff --git a/js/js.translator/testData/box/native/eval.kt b/js/js.translator/testData/box/native/eval.kt index 9222cea39bc..4da2663adb0 100644 --- a/js/js.translator/testData/box/native/eval.kt +++ b/js/js.translator/testData/box/native/eval.kt @@ -1,5 +1,6 @@ // SKIP_MINIFICATION +@JsExport val top = "TOP LEVEL" fun box(): String { diff --git a/js/js.translator/testData/box/native/inheritanceInNativeClass.kt b/js/js.translator/testData/box/native/inheritanceInNativeClass.kt index cf610f28af0..087cf5d1f68 100644 --- a/js/js.translator/testData/box/native/inheritanceInNativeClass.kt +++ b/js/js.translator/testData/box/native/inheritanceInNativeClass.kt @@ -1,6 +1,7 @@ // SKIP_MINIFICATION // Contains calls from external JS code +@JsExport open class A { @JsName("foo") open protected fun foo(n: Int) = 23 @@ -9,6 +10,7 @@ open class A { fun bar(n: Int) = foo(n) + 100 } +@JsExport open class B { @JsName("foo") protected fun foo(n: Int) = 42 diff --git a/js/js.translator/testData/box/propertyAccess/accessorsWithJsName.kt b/js/js.translator/testData/box/propertyAccess/accessorsWithJsName.kt index b987c68bce2..65d2060fab9 100644 --- a/js/js.translator/testData/box/propertyAccess/accessorsWithJsName.kt +++ b/js/js.translator/testData/box/propertyAccess/accessorsWithJsName.kt @@ -11,6 +11,7 @@ class A { } } +@JsExport val A.z: Int @JsName("getZ_") get() = 42 diff --git a/js/js.translator/testData/box/propertyAccess/staticAccessorsWithJsName.kt b/js/js.translator/testData/box/propertyAccess/staticAccessorsWithJsName.kt index 38a381f74af..f6888c9faf2 100644 --- a/js/js.translator/testData/box/propertyAccess/staticAccessorsWithJsName.kt +++ b/js/js.translator/testData/box/propertyAccess/staticAccessorsWithJsName.kt @@ -1,8 +1,10 @@ // EXPECTED_REACHABLE_NODES: 1288 +@JsExport val x: Int @JsName("getX_") get() = 23 +@JsExport var y: Int = 0 @JsName("getY_") get() = field + 10 @JsName("setY_") set(value) { diff --git a/js/js.translator/testData/box/trait/checkImplementationCharacteristics.kt b/js/js.translator/testData/box/trait/checkImplementationCharacteristics.kt index f3224ad2071..ebb358716a5 100644 --- a/js/js.translator/testData/box/trait/checkImplementationCharacteristics.kt +++ b/js/js.translator/testData/box/trait/checkImplementationCharacteristics.kt @@ -27,10 +27,13 @@ fun box(): String { if (!hasProp(b, "foo")) return "B hasn't foo" if (!hasProp(b, "boo")) return "B hasn't boo" - val PREFIX = "_" - 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" + // Legacy scheme exports interfaces + if (testUtils.isLegacyBackend()) { + val PREFIX = "_" + 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" } \ No newline at end of file diff --git a/libraries/stdlib/js/src/kotlin/annotations.kt b/libraries/stdlib/js/src/kotlin/annotations.kt index 6f465b25d35..6b6a79681a6 100644 --- a/libraries/stdlib/js/src/kotlin/annotations.kt +++ b/libraries/stdlib/js/src/kotlin/annotations.kt @@ -159,4 +159,14 @@ public annotation class JsNonModule */ @Retention(AnnotationRetention.BINARY) @Target(AnnotationTarget.FILE) -public annotation class JsQualifier(val value: String) \ No newline at end of file +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