diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt index 16bc5223f81..5d366f196f9 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt @@ -5,7 +5,9 @@ package org.jetbrains.kotlin.ir.backend.js.export -import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrProperty +import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction sealed class ExportedDeclaration @@ -56,7 +58,7 @@ class ExportedClass( val superInterfaces: List = emptyList(), val typeParameters: List, val members: List, - val statics: List, + val nestedClasses: List, val ir: IrClass ) : ExportedDeclaration() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt index f46155964bf..4167469b139 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt @@ -6,7 +6,6 @@ package org.jetbrains.kotlin.ir.backend.js.export import org.jetbrains.kotlin.backend.common.ir.isExpect -import org.jetbrains.kotlin.backend.common.ir.isMethodOfAny import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality @@ -23,8 +22,6 @@ import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* -import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.name.FqNameUnsafe import org.jetbrains.kotlin.utils.addIfNotNull class ExportModelGenerator(val context: JsIrBackendContext) { @@ -139,14 +136,14 @@ class ExportModelGenerator(val context: JsIrBackendContext) { private fun exportClass( klass: IrClass - ): ExportedDeclaration? { + ): ExportedClass? { when (val exportability = classExportability(klass)) { - is Exportability.Prohibited -> return ErrorDeclaration(exportability.reason) + is Exportability.Prohibited -> return error(exportability.reason) is Exportability.NotNeeded -> return null } val members = mutableListOf() - val statics = mutableListOf() + val nestedClasses = mutableListOf() for (declaration in klass.declarations) { val candidate = getExportCandidate(declaration) ?: continue @@ -163,7 +160,7 @@ class ExportModelGenerator(val context: JsIrBackendContext) { members.addIfNotNull(exportProperty(candidate)) is IrClass -> - statics.addIfNotNull(exportClass(candidate)) + nestedClasses.addIfNotNull(exportClass(candidate)) is IrField -> { assert(candidate.correspondingPropertySymbol != null) { @@ -198,7 +195,7 @@ class ExportModelGenerator(val context: JsIrBackendContext) { superInterfaces = superInterfaces, typeParameters = typeParameters, members = members, - statics = statics, + nestedClasses = nestedClasses, ir = klass ) } @@ -413,4 +410,4 @@ val strictModeReservedWords = setOf( "yield" ) -private val allReservedWords = reservedWords + strictModeReservedWords \ No newline at end of file +private val allReservedWords = reservedWords + strictModeReservedWords diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt index f1693a9ed8e..83b82428232 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt @@ -8,7 +8,8 @@ package org.jetbrains.kotlin.ir.backend.js.export import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsAstUtils import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.defineProperty import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.jsAssignment -import org.jetbrains.kotlin.ir.backend.js.utils.* +import org.jetbrains.kotlin.ir.backend.js.utils.NameTable +import org.jetbrains.kotlin.ir.backend.js.utils.NameTables import org.jetbrains.kotlin.js.backend.ast.* @@ -85,7 +86,12 @@ class ExportModelToJsStatements( ) ) ).makeStmt() - val staticsExport = declaration.statics.flatMap { generateDeclarationExport(it, newNameSpace) } + + val staticFunctions = declaration.members.filter { it is ExportedFunction && it.isStatic } + + val staticsExport = (staticFunctions + declaration.nestedClasses) + .flatMap { generateDeclarationExport(it, newNameSpace) } + listOf(klassExport) + staticsExport } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt index 12c8477e7d9..edd16530ef9 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt @@ -75,7 +75,7 @@ fun ExportedDeclaration.toTypeScript(indent: String): String = indent + when (th val modifiers = if (isAbstract && !isInterface) "abstract " else "" val klassExport = "$modifiers$keyword $name$renderedTypeParameters$superClassClause$superInterfacesClause {\n$membersString$indent}" - val staticsExport = if (statics.isNotEmpty()) "\n" + ExportedNamespace(name, statics).toTypeScript(indent) else "" + val staticsExport = if (nestedClasses.isNotEmpty()) "\n" + ExportedNamespace(name, nestedClasses).toTypeScript(indent) else "" klassExport + staticsExport } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/SecondaryCtorLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/SecondaryCtorLowering.kt index 9e825bba0a8..f8844d3138e 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/SecondaryCtorLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/SecondaryCtorLowering.kt @@ -17,6 +17,7 @@ import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder +import org.jetbrains.kotlin.ir.backend.js.utils.getJsName import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.IrBlockBodyImpl @@ -176,9 +177,10 @@ private fun buildFactoryDeclaration(constructor: IrConstructor, irClass: IrClass Modality.FINAL, constructor.isInline, constructor.isExternal - ).also { - it.copyTypeParametersFrom(constructor.parentAsClass) - it.valueParameters += constructor.valueParameters.map { p -> p.copyTo(it) } + ).also { factory -> + factory.copyTypeParametersFrom(constructor.parentAsClass) + factory.valueParameters += constructor.valueParameters.map { p -> p.copyTo(factory) } + factory.annotations = constructor.annotations } } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsTypeScriptExportTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsTypeScriptExportTestGenerated.java index 277a4b5d3f3..4d72e23d0bd 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsTypeScriptExportTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsTypeScriptExportTestGenerated.java @@ -29,6 +29,24 @@ public class IrJsTypeScriptExportTestGenerated extends AbstractIrJsTypeScriptExp KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true); } + @TestMetadata("js/js.translator/testData/typescript-export/constructors") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Constructors extends AbstractIrJsTypeScriptExportTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath); + } + + public void testAllFilesPresentInConstructors() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export/constructors"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true); + } + + @TestMetadata("constructors.kt") + public void testConstructors() throws Exception { + runTest("js/js.translator/testData/typescript-export/constructors/constructors.kt"); + } + } + @TestMetadata("js/js.translator/testData/typescript-export/declarations") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/LegacyJsTypeScriptExportTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/LegacyJsTypeScriptExportTestGenerated.java index 8f9cc730252..ad9510071ad 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/LegacyJsTypeScriptExportTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/LegacyJsTypeScriptExportTestGenerated.java @@ -29,6 +29,19 @@ public class LegacyJsTypeScriptExportTestGenerated extends AbstractLegacyJsTypeS KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true); } + @TestMetadata("js/js.translator/testData/typescript-export/constructors") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Constructors extends AbstractLegacyJsTypeScriptExportTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath); + } + + public void testAllFilesPresentInConstructors() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export/constructors"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true); + } + } + @TestMetadata("js/js.translator/testData/typescript-export/declarations") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.translator/testData/box/jsName/avoidNameClash.kt b/js/js.translator/testData/box/jsName/avoidNameClash.kt index fd6498c8ee8..25745501f8c 100644 --- a/js/js.translator/testData/box/jsName/avoidNameClash.kt +++ b/js/js.translator/testData/box/jsName/avoidNameClash.kt @@ -1,5 +1,8 @@ // EXPECTED_REACHABLE_NODES: 1290 +// TODO: Support JsExport on object declarations: KT-39117 +// IGNORE_BACKEND: JS_IR + @JsExport object A { @JsName("js_method") fun f() = "method" diff --git a/js/js.translator/testData/box/jsName/simpleJsName.kt b/js/js.translator/testData/box/jsName/simpleJsName.kt index 52081f11ba0..68aeb468fa1 100644 --- a/js/js.translator/testData/box/jsName/simpleJsName.kt +++ b/js/js.translator/testData/box/jsName/simpleJsName.kt @@ -1,5 +1,8 @@ // EXPECTED_REACHABLE_NODES: 1291 +// TODO: Support JsExport on object declarations: KT-39117 +// IGNORE_BACKEND: JS_IR + @JsExport object A { @JsName("js_f") fun f(x: Int) = "f($x)" diff --git a/js/js.translator/testData/typescript-export/constructors/constructors.d.ts b/js/js.translator/testData/typescript-export/constructors/constructors.d.ts new file mode 100644 index 00000000000..0aa55b64c22 --- /dev/null +++ b/js/js.translator/testData/typescript-export/constructors/constructors.d.ts @@ -0,0 +1,30 @@ +declare namespace JS_TESTS { + type Nullable = T | null | undefined + class ClassWithDefaultCtor { + constructor() + readonly x: string; + } + class ClassWithPrimaryCtor { + constructor(x: string) + readonly x: string; + } + class ClassWithSecondaryCtor { + readonly x: string; + static create(y: string): ClassWithSecondaryCtor + } + class ClassWithMultipleSecondaryCtors { + readonly x: string; + static createFromString(y: string): ClassWithMultipleSecondaryCtors + static createFromInts(y: number, z: number): ClassWithMultipleSecondaryCtors + } + class OpenClassWithMixedConstructors { + constructor(x: string) + readonly x: string; + static createFromStrings(y: string, z: string): OpenClassWithMixedConstructors + static createFromInts(y: number, z: number): OpenClassWithMixedConstructors + } + class DerivedClassWithSecondaryCtor extends OpenClassWithMixedConstructors { + static delegateToPrimary(y: string): DerivedClassWithSecondaryCtor + static delegateToCreateFromInts(y: number, z: number): DerivedClassWithSecondaryCtor + } +} diff --git a/js/js.translator/testData/typescript-export/constructors/constructors.kt b/js/js.translator/testData/typescript-export/constructors/constructors.kt new file mode 100644 index 00000000000..c6a428f5b89 --- /dev/null +++ b/js/js.translator/testData/typescript-export/constructors/constructors.kt @@ -0,0 +1,56 @@ +// TARGET_BACKEND: JS_IR +// CHECK_TYPESCRIPT_DECLARATIONS +// RUN_PLAIN_BOX_FUNCTION +// SKIP_MINIFICATION +// SKIP_NODE_JS + +// TODO fix statics export in DCE-driven mode +// SKIP_DCE_DRIVEN + +@file:JsExport + +class ClassWithDefaultCtor { + val x = "ClassWithDefaultCtor::x" +} + +class ClassWithPrimaryCtor( + val x: String +) + +class ClassWithSecondaryCtor { + val x: String + @JsName("create") + constructor(y: String) { + x = y + } +} + +class ClassWithMultipleSecondaryCtors { + val x: String + + @JsName("createFromString") + constructor(y: String) { + x = "fromString:$y" + } + + @JsName("createFromInts") + constructor(y: Int, z: Int) { + x = "fromInts:$y:$z" + } +} + +open class OpenClassWithMixedConstructors(val x: String) { + @JsName("createFromStrings") + constructor(y: String, z: String) : this("fromStrings:$y:$z") + + @JsName("createFromInts") + constructor(y: Int, z: Int) : this(y.toString(), z.toString()) +} + +class DerivedClassWithSecondaryCtor : OpenClassWithMixedConstructors { + @JsName("delegateToPrimary") + constructor(y: String) : super(y) + + @JsName("delegateToCreateFromInts") + constructor(y: Int, z: Int) : super(y, z) +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/constructors/constructors__main.js b/js/js.translator/testData/typescript-export/constructors/constructors__main.js new file mode 100644 index 00000000000..9958fedf771 --- /dev/null +++ b/js/js.translator/testData/typescript-export/constructors/constructors__main.js @@ -0,0 +1,35 @@ +"use strict"; +var ClassWithDefaultCtor = JS_TESTS.ClassWithDefaultCtor, ClassWithPrimaryCtor = JS_TESTS.ClassWithPrimaryCtor, ClassWithSecondaryCtor = JS_TESTS.ClassWithSecondaryCtor, ClassWithMultipleSecondaryCtors = JS_TESTS.ClassWithMultipleSecondaryCtors, DerivedClassWithSecondaryCtor = JS_TESTS.DerivedClassWithSecondaryCtor, OpenClassWithMixedConstructors = JS_TESTS.OpenClassWithMixedConstructors; +function box() { + var o1 = new ClassWithDefaultCtor(); + if (o1.x !== "ClassWithDefaultCtor::x") + return "Fail: ClassWithDefaultCtor"; + var o2 = new ClassWithPrimaryCtor("foo"); + if (o2.x !== "foo") + return "Fail: ClassWithPrimaryCtor"; + var o3 = ClassWithSecondaryCtor.create("foo2"); + if (o3.x !== "foo2") + return "Fail: ClassWithSecondaryCtor.create"; + var o4 = ClassWithMultipleSecondaryCtors.createFromString("foo3"); + if (o4.x !== "fromString:foo3") + return "Fail: ClassWithMultipleSecondaryCtors.createFromString"; + var o5 = ClassWithMultipleSecondaryCtors.createFromInts(1, 2); + if (o5.x !== "fromInts:1:2") + return "Fail: ClassWithMultipleSecondaryCtors.createFromInts"; + var o6 = new OpenClassWithMixedConstructors("foo4"); + if (o6.x !== "foo4") + return "Fail: OpenClassWithMixedConstructors"; + var o7 = OpenClassWithMixedConstructors.createFromStrings("foo", "bar"); + if (o7.x !== "fromStrings:foo:bar") + return "Fail: OpenClassWithMixedConstructors.createFromStrings"; + var o8 = OpenClassWithMixedConstructors.createFromInts(10, -20); + if (o8.x !== "fromStrings:10:-20") + return "Fail: OpenClassWithMixedConstructors.createFromInts"; + var o9 = DerivedClassWithSecondaryCtor.delegateToPrimary("foo6"); + if (o9.x !== "foo6") + return "Fail: DerivedClassWithSecondaryCtor.delegateToPrimary"; + var o10 = DerivedClassWithSecondaryCtor.delegateToCreateFromInts(-10, 20); + if (o10.x !== "fromStrings:-10:20") + return "Fail: DerivedClassWithSecondaryCtor.delegateToCreateFromInts"; + return "OK"; +} diff --git a/js/js.translator/testData/typescript-export/constructors/constructors__main.ts b/js/js.translator/testData/typescript-export/constructors/constructors__main.ts new file mode 100644 index 00000000000..3a81d7004c3 --- /dev/null +++ b/js/js.translator/testData/typescript-export/constructors/constructors__main.ts @@ -0,0 +1,47 @@ +const { + ClassWithDefaultCtor, + ClassWithPrimaryCtor, + ClassWithSecondaryCtor, + ClassWithMultipleSecondaryCtors, + DerivedClassWithSecondaryCtor, + OpenClassWithMixedConstructors +} = JS_TESTS; + +function box(): string { + const o1 = new ClassWithDefaultCtor(); + if (o1.x !== "ClassWithDefaultCtor::x") return "Fail: ClassWithDefaultCtor"; + + + const o2 = new ClassWithPrimaryCtor("foo"); + if (o2.x !== "foo") return "Fail: ClassWithPrimaryCtor"; + + + const o3 = ClassWithSecondaryCtor.create("foo2"); + if (o3.x !== "foo2") return "Fail: ClassWithSecondaryCtor.create"; + + + const o4 = ClassWithMultipleSecondaryCtors.createFromString("foo3"); + if (o4.x !== "fromString:foo3") return "Fail: ClassWithMultipleSecondaryCtors.createFromString"; + + const o5 = ClassWithMultipleSecondaryCtors.createFromInts(1, 2); + if (o5.x !== "fromInts:1:2") return "Fail: ClassWithMultipleSecondaryCtors.createFromInts"; + + + const o6 = new OpenClassWithMixedConstructors("foo4"); + if (o6.x !== "foo4") return "Fail: OpenClassWithMixedConstructors"; + + const o7 = OpenClassWithMixedConstructors.createFromStrings("foo", "bar"); + if (o7.x !== "fromStrings:foo:bar") return "Fail: OpenClassWithMixedConstructors.createFromStrings"; + + const o8 = OpenClassWithMixedConstructors.createFromInts(10, -20); + if (o8.x !== "fromStrings:10:-20") return "Fail: OpenClassWithMixedConstructors.createFromInts"; + + + const o9 = DerivedClassWithSecondaryCtor.delegateToPrimary("foo6"); + if (o9.x !== "foo6") return "Fail: DerivedClassWithSecondaryCtor.delegateToPrimary"; + + const o10 = DerivedClassWithSecondaryCtor.delegateToCreateFromInts(-10, 20); + if (o10.x !== "fromStrings:-10:20") return "Fail: DerivedClassWithSecondaryCtor.delegateToCreateFromInts"; + + return "OK"; +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/constructors/tsconfig.json b/js/js.translator/testData/typescript-export/constructors/tsconfig.json new file mode 100644 index 00000000000..17612c56c9d --- /dev/null +++ b/js/js.translator/testData/typescript-export/constructors/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [ "./*" ], + "extends": "../common.tsconfig.json" +} \ No newline at end of file