diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt index 5ea4d26e656..cce75cf44f4 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt @@ -214,6 +214,11 @@ fun usefulDeclarations(roots: Iterable, context: JsIrBackendConte (it.classifierOrNull as? IrClassSymbol)?.owner?.enqueue("superTypes") } + if (declaration.isObject && declaration.isExported(context)) { + context.mapping.objectToGetInstanceFunction[declaration]!! + .enqueue(declaration, "Exported object getInstance function") + } + declaration.annotations.forEach { val annotationClass = it.symbol.owner.constructedClass if (annotationClass.isAssociatedObjectAnnotatedAnnotation) { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/DeclarationOrigins.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/DeclarationOrigins.kt index b3c843935a5..2bb42453938 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/DeclarationOrigins.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/DeclarationOrigins.kt @@ -14,4 +14,5 @@ object JsLoweredDeclarationOrigin : IrDeclarationOrigin { object JS_CLOSURE_BOX_CLASS : IrStatementOriginImpl("JS_CLOSURE_BOX_CLASS") object JS_CLOSURE_BOX_CLASS_DECLARATION : IrDeclarationOriginImpl("JS_CLOSURE_BOX_CLASS_DECLARATION") object BRIDGE_TO_EXTERNAL_FUNCTION : IrDeclarationOriginImpl("BRIDGE_TO_EXTERNAL_FUNCTION") + object OBJECT_GET_INSTANCE_FUNCTION : IrDeclarationOriginImpl("BRIDGE_TO_EXTERNAL_FUNCTION") } 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 1bb46710c6a..e8cc4ebd73e 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 @@ -6,7 +6,7 @@ package org.jetbrains.kotlin.ir.backend.js.export import org.jetbrains.kotlin.ir.declarations.IrClass -import org.jetbrains.kotlin.ir.declarations.IrProperty +import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.serialization.js.ModuleKind @@ -45,7 +45,8 @@ class ExportedProperty( val isMember: Boolean = false, val isStatic: Boolean = false, val isAbstract: Boolean, - val ir: IrProperty + val irGetter: IrFunction?, + val irSetter: IrFunction?, ) : ExportedDeclaration() @@ -95,6 +96,13 @@ sealed class ExportedType { class TypeParameter(val name: String) : ExportedType() class Nullable(val baseType: ExportedType) : ExportedType() class ErrorType(val comment: String) : ExportedType() + class TypeOf(val name: String) : ExportedType() + + class InlineInterfaceType( + val members: List + ) : ExportedType() + + class IntersectionType(val lhs: ExportedType, val rhs: ExportedType) : ExportedType() fun withNullability(nullable: Boolean) = if (nullable) Nullable(this) else this 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 9c3eedd13fd..2cee7929dc6 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 @@ -112,7 +112,8 @@ class ExportModelGenerator(val context: JsIrBackendContext) { isMember = parentClass != null, isStatic = false, isAbstract = parentClass?.isInterface == false && property.modality == Modality.ABSTRACT, - ir = property + irGetter = property.getter, + irSetter = property.setter ) } @@ -120,10 +121,10 @@ class ExportModelGenerator(val context: JsIrBackendContext) { when (klass.kind) { ClassKind.ANNOTATION_CLASS, ClassKind.ENUM_CLASS, - ClassKind.ENUM_ENTRY, - ClassKind.OBJECT -> + ClassKind.ENUM_ENTRY -> return Exportability.Prohibited("Class ${klass.fqNameWhenAvailable} with kind: ${klass.kind}") + ClassKind.OBJECT, ClassKind.CLASS, ClassKind.INTERFACE -> { } @@ -137,7 +138,7 @@ class ExportModelGenerator(val context: JsIrBackendContext) { private fun exportClass( klass: IrClass - ): ExportedClass? { + ): ExportedDeclaration? { when (val exportability = classExportability(klass)) { is Exportability.Prohibited -> return error(exportability.reason) is Exportability.NotNeeded -> return null @@ -160,8 +161,14 @@ class ExportModelGenerator(val context: JsIrBackendContext) { is IrProperty -> members.addIfNotNull(exportProperty(candidate)) - is IrClass -> - nestedClasses.addIfNotNull(exportClass(candidate)) + is IrClass -> { + val ec = exportClass(candidate) + if (ec is ExportedClass) { + nestedClasses.add(ec) + } else { + members.addIfNotNull(ec) + } + } is IrField -> { assert(candidate.correspondingPropertySymbol != null) { @@ -188,6 +195,27 @@ class ExportModelGenerator(val context: JsIrBackendContext) { val name = klass.getExportedIdentifier() + if (klass.kind == ClassKind.OBJECT) { + var t: ExportedType = ExportedType.InlineInterfaceType(members + nestedClasses) + if (superType != null) + t = ExportedType.IntersectionType(t, superType) + + for (superInterface in superInterfaces) { + t = ExportedType.IntersectionType(t, superInterface) + } + + return ExportedProperty( + name = name, + type = t, + mutable = false, + isMember = klass.parent is IrClass, + isStatic = true, + isAbstract = false, + irGetter = context.mapping.objectToGetInstanceFunction[klass]!!, + irSetter = null + ) + } + return ExportedClass( name = name, isInterface = klass.isInterface, @@ -254,11 +282,20 @@ class ExportModelGenerator(val context: JsIrBackendContext) { classifier is IrClassSymbol -> { val klass = classifier.owner - when (val exportability = classExportability(klass)) { - is Exportability.Prohibited -> ExportedType.ErrorType(exportability.reason) - is Exportability.NotNeeded -> error("Not needed classes types cannot be used") - else -> ExportedType.ClassType( - klass.fqNameWhenAvailable!!.asString(), + val name = klass.fqNameWhenAvailable!!.asString() + + when (klass.kind) { + ClassKind.ANNOTATION_CLASS, + ClassKind.ENUM_CLASS, + ClassKind.ENUM_ENTRY -> + ExportedType.ErrorType("Class $name with kind: ${klass.kind}") + + ClassKind.OBJECT -> + ExportedType.TypeOf(name) + + ClassKind.CLASS, + ClassKind.INTERFACE -> ExportedType.ClassType( + name, type.arguments.map { exportTypeArgument(it) } ) } @@ -286,7 +323,8 @@ class ExportModelGenerator(val context: JsIrBackendContext) { return Exportability.NotNeeded if (function.origin == IrDeclarationOrigin.BRIDGE || function.origin == JsLoweredDeclarationOrigin.BRIDGE_TO_EXTERNAL_FUNCTION || - function.origin == IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER + function.origin == IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER || + function.origin == JsLoweredDeclarationOrigin.OBJECT_GET_INSTANCE_FUNCTION ) { return Exportability.NotNeeded } 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 83b82428232..c3e19e6b9b3 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 @@ -68,8 +68,8 @@ class ExportModelToJsStatements( is ExportedConstructor -> emptyList() is ExportedProperty -> { - val getter = declaration.ir.getter?.let { JsNameRef(nameTables.getNameForStaticDeclaration(it)) } - val setter = declaration.ir.setter?.let { JsNameRef(nameTables.getNameForStaticDeclaration(it)) } + val getter = declaration.irGetter?.let { JsNameRef(nameTables.getNameForStaticDeclaration(it)) } + val setter = declaration.irSetter?.let { JsNameRef(nameTables.getNameForStaticDeclaration(it)) } listOf(defineProperty(namespace, declaration.name, getter, setter).makeStmt()) } 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 505e0c7928f..e69337df76e 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 @@ -50,7 +50,7 @@ fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): Strin else -> "function " } - val renderedParameters = parameters.joinToString(", ") { it.toTypeScript() } + val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) } val renderedTypeParameters = if (typeParameters.isNotEmpty()) @@ -58,28 +58,28 @@ fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): Strin else "" - val renderedReturnType = returnType.toTypeScript() + val renderedReturnType = returnType.toTypeScript(indent) "${prefix}$keyword$name$renderedTypeParameters($renderedParameters): $renderedReturnType;" } is ExportedConstructor -> - "constructor(${parameters.joinToString(", ") { it.toTypeScript() }});" + "constructor(${parameters.joinToString(", ") { it.toTypeScript(indent) }});" is ExportedProperty -> { val keyword = when { isMember -> (if (isAbstract) "abstract " else "") + (if (!mutable) "readonly " else "") else -> if (mutable) "let " else "const " } - prefix + keyword + name + ": " + type.toTypeScript() + ";" + prefix + keyword + name + ": " + type.toTypeScript(indent) + ";" } is ExportedClass -> { val keyword = if (isInterface) "interface" else "class" val superInterfacesKeyword = if (isInterface) "extends" else "implements" - val superClassClause = superClass?.let { " extends ${it.toTypeScript()}" } ?: "" + val superClassClause = superClass?.let { " extends ${it.toTypeScript(indent)}" } ?: "" val superInterfacesClause = if (superInterfaces.isNotEmpty()) { - " $superInterfacesKeyword " + superInterfaces.joinToString(", ") { it.toTypeScript() } + " $superInterfacesKeyword " + superInterfaces.joinToString(", ") { it.toTypeScript(indent) } } else "" val membersString = members.joinToString("") { it.toTypeScript("$indent ") + "\n" } @@ -107,22 +107,30 @@ fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): Strin } } -fun ExportedParameter.toTypeScript(): String = - "$name: ${type.toTypeScript()}" +fun ExportedParameter.toTypeScript(indent: String): String = + "$name: ${type.toTypeScript(indent)}" -fun ExportedType.toTypeScript(): String = when (this) { +fun ExportedType.toTypeScript(indent: String): String = when (this) { is ExportedType.Primitive -> typescript - is ExportedType.Array -> "Array<${elementType.toTypeScript()}>" + is ExportedType.Array -> "Array<${elementType.toTypeScript(indent)}>" is ExportedType.Function -> "(" + parameterTypes .withIndex() .joinToString(", ") { (index, type) -> - "p$index: ${type.toTypeScript()}" - } + ") => " + returnType.toTypeScript() + "p$index: ${type.toTypeScript(indent)}" + } + ") => " + returnType.toTypeScript(indent) is ExportedType.ClassType -> - name + if (arguments.isNotEmpty()) "<${arguments.joinToString(", ") { it.toTypeScript() }}>" else "" + name + if (arguments.isNotEmpty()) "<${arguments.joinToString(", ") { it.toTypeScript(indent) }}>" else "" + is ExportedType.TypeOf -> + "typeof $name" is ExportedType.ErrorType -> "any /*$comment*/" is ExportedType.TypeParameter -> name - is ExportedType.Nullable -> "Nullable<" + baseType.toTypeScript() + ">" + is ExportedType.Nullable -> "Nullable<" + baseType.toTypeScript(indent) + ">" + is ExportedType.InlineInterfaceType -> { + members.joinToString(prefix = "{\n", postfix = "$indent}", separator = "") { it.toTypeScript("$indent ") + "\n" } + } + is ExportedType.IntersectionType -> { + lhs.toTypeScript(indent) + " & " + rhs.toTypeScript(indent) + } } \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ObjectLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ObjectLowering.kt index ef2eaf52be3..c03f517e4d7 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ObjectLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ObjectLowering.kt @@ -12,8 +12,10 @@ import org.jetbrains.kotlin.backend.common.lower.createIrBuilder import org.jetbrains.kotlin.backend.common.lower.irBlockBody import org.jetbrains.kotlin.backend.common.lower.irIfThen import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext +import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.builders.declarations.buildField @@ -111,6 +113,7 @@ private fun JsCommonBackendContext.getOrCreateGetInstanceFunction(obj: IrClass) jsIrDeclarationBuilder.buildFunction( obj.name.asString() + "_getInstance", returnType = obj.defaultType, - parent = obj.parent + parent = obj.parent, + origin = JsLoweredDeclarationOrigin.OBJECT_GET_INSTANCE_FUNCTION ) } diff --git a/js/js.translator/testData/box/jsName/avoidNameClash.kt b/js/js.translator/testData/box/jsName/avoidNameClash.kt index 6b22ab3ae4a..fd6498c8ee8 100644 --- a/js/js.translator/testData/box/jsName/avoidNameClash.kt +++ b/js/js.translator/testData/box/jsName/avoidNameClash.kt @@ -1,9 +1,5 @@ // EXPECTED_REACHABLE_NODES: 1290 -// TODO: Support JsExport on object declarations: KT-39117 -// IGNORE_BACKEND: JS_IR -// IGNORE_BACKEND: JS_IR_ES6 - @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 a8124802f14..52081f11ba0 100644 --- a/js/js.translator/testData/box/jsName/simpleJsName.kt +++ b/js/js.translator/testData/box/jsName/simpleJsName.kt @@ -1,9 +1,5 @@ // EXPECTED_REACHABLE_NODES: 1291 -// TODO: Support JsExport on object declarations: KT-39117 -// IGNORE_BACKEND: JS_IR -// IGNORE_BACKEND: JS_IR_ES6 - @JsExport object A { @JsName("js_f") fun f(x: Int) = "f($x)" diff --git a/js/js.translator/testData/typescript-export/declarations/declarations.d.ts b/js/js.translator/testData/typescript-export/declarations/declarations.d.ts index 6e5ebb03f53..0d6dd8172ae 100644 --- a/js/js.translator/testData/typescript-export/declarations/declarations.d.ts +++ b/js/js.translator/testData/typescript-export/declarations/declarations.d.ts @@ -42,5 +42,12 @@ declare namespace JS_TESTS { _varCustom: number; _varCustomWithField: number; } + const O0: { + }; + const O: { + readonly x: number; + foo(): number; + }; + function takesO(o: typeof foo.O): number; } } diff --git a/js/js.translator/testData/typescript-export/declarations/declarations.kt b/js/js.translator/testData/typescript-export/declarations/declarations.kt index dea6987939a..67955083651 100644 --- a/js/js.translator/testData/typescript-export/declarations/declarations.kt +++ b/js/js.translator/testData/typescript-export/declarations/declarations.kt @@ -82,4 +82,15 @@ class A4 { var _varCustomWithField: Int = 1 get() = field * 10 set(value) { field = value * 10 } -} \ No newline at end of file +} + + +object O0 + +object O { + val x = 10 + fun foo() = 20 +} + +fun takesO(o: O): Int = + O.x + O.foo() \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/declarations/declarations__main.js b/js/js.translator/testData/typescript-export/declarations/declarations__main.js index 20f4866b0ba..584970664d2 100644 --- a/js/js.translator/testData/typescript-export/declarations/declarations__main.js +++ b/js/js.translator/testData/typescript-export/declarations/declarations__main.js @@ -20,6 +20,8 @@ var A3 = JS_TESTS.foo.A3; var _valCustom = JS_TESTS.foo._valCustom; var _valCustomWithField = JS_TESTS.foo._valCustomWithField; var A4 = JS_TESTS.foo.A4; +var O = JS_TESTS.foo.O; +var takesO = JS_TESTS.foo.takesO; function assert(condition) { if (!condition) { throw "Assertion failed"; @@ -75,5 +77,8 @@ function box() { assert(a4._varCustomWithField === 10); a4._varCustomWithField = 10; assert(a4._varCustomWithField === 1000); + assert(O.x === 10); + assert(O.foo() === 20); + assert(takesO(O) === 30); return "OK"; } diff --git a/js/js.translator/testData/typescript-export/declarations/declarations__main.ts b/js/js.translator/testData/typescript-export/declarations/declarations__main.ts index c19af7202e1..3dc5e6d1771 100644 --- a/js/js.translator/testData/typescript-export/declarations/declarations__main.ts +++ b/js/js.translator/testData/typescript-export/declarations/declarations__main.ts @@ -20,6 +20,8 @@ import A3 = JS_TESTS.foo.A3; import _valCustom = JS_TESTS.foo._valCustom; import _valCustomWithField = JS_TESTS.foo._valCustomWithField; import A4 = JS_TESTS.foo.A4; +import O = JS_TESTS.foo.O; +import takesO = JS_TESTS.foo.takesO; function assert(condition: boolean) { if (!condition) { @@ -86,5 +88,9 @@ function box(): string { a4._varCustomWithField = 10; assert(a4._varCustomWithField === 1000); + assert(O.x === 10); + assert(O.foo() === 20); + assert(takesO(O) === 30); + return "OK"; } \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/inheritance/inheritance.d.ts b/js/js.translator/testData/typescript-export/inheritance/inheritance.d.ts index 3e9d1935cb2..1c92ff5378c 100644 --- a/js/js.translator/testData/typescript-export/inheritance/inheritance.d.ts +++ b/js/js.translator/testData/typescript-export/inheritance/inheritance.d.ts @@ -30,5 +30,10 @@ declare namespace JS_TESTS { class FC extends foo.OC { constructor(); } + const O1: { + } & foo.OC; + const O2: { + foo(): number; + } & foo.OC; } } diff --git a/js/js.translator/testData/typescript-export/inheritance/inheritance.kt b/js/js.translator/testData/typescript-export/inheritance/inheritance.kt index fa37793eb71..896df838e19 100644 --- a/js/js.translator/testData/typescript-export/inheritance/inheritance.kt +++ b/js/js.translator/testData/typescript-export/inheritance/inheritance.kt @@ -40,4 +40,10 @@ open class OC( private fun privateFun(): String = "privateFun" } -final class FC : OC(true, "FC") \ No newline at end of file +final class FC : OC(true, "FC") + +object O1 : OC(true, "O1") + +object O2 : OC(true, "O2") { + fun foo(): Int = 10 +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/inheritance/inheritance__main.js b/js/js.translator/testData/typescript-export/inheritance/inheritance__main.js index 96bf2488242..9540ab956b0 100644 --- a/js/js.translator/testData/typescript-export/inheritance/inheritance__main.js +++ b/js/js.translator/testData/typescript-export/inheritance/inheritance__main.js @@ -15,6 +15,8 @@ var __extends = (this && this.__extends) || (function () { var OC = JS_TESTS.foo.OC; var AC = JS_TESTS.foo.AC; var FC = JS_TESTS.foo.FC; +var O1 = JS_TESTS.foo.O1; +var O2 = JS_TESTS.foo.O2; var Impl = /** @class */ (function (_super) { __extends(Impl, _super); function Impl() { @@ -24,12 +26,12 @@ var Impl = /** @class */ (function (_super) { }; Object.defineProperty(Impl.prototype, "acAbstractProp", { get: function () { return "Impl"; }, - enumerable: true, + enumerable: false, configurable: true }); Object.defineProperty(Impl.prototype, "y", { get: function () { return true; }, - enumerable: true, + enumerable: false, configurable: true }); return Impl; @@ -56,5 +58,13 @@ function box() { if (fc.acAbstractProp !== "FC") return "Fail 6"; fc.z(10); + if (O1.y !== true || O2.y !== true) + return "Fail 7"; + if (O1.acAbstractProp != "O1") + return "Fail 8"; + if (O2.acAbstractProp != "O2") + return "Fail 9"; + if (O2.foo() != 10) + return "Fail 10"; return "OK"; } diff --git a/js/js.translator/testData/typescript-export/inheritance/inheritance__main.ts b/js/js.translator/testData/typescript-export/inheritance/inheritance__main.ts index 6186eed0eba..aa47a3b68d3 100644 --- a/js/js.translator/testData/typescript-export/inheritance/inheritance__main.ts +++ b/js/js.translator/testData/typescript-export/inheritance/inheritance__main.ts @@ -1,6 +1,8 @@ import OC = JS_TESTS.foo.OC; import AC = JS_TESTS.foo.AC; import FC = JS_TESTS.foo.FC; +import O1 = JS_TESTS.foo.O1; +import O2 = JS_TESTS.foo.O2; class Impl extends AC { z(z: number): void { @@ -29,5 +31,10 @@ function box(): string { if (fc.acAbstractProp !== "FC") return "Fail 6"; fc.z(10); + if (O1.y !== true || O2.y !== true) return "Fail 7"; + if (O1.acAbstractProp != "O1") return "Fail 8"; + if (O2.acAbstractProp != "O2") return "Fail 9"; + if (O2.foo() != 10) return "Fail 10"; + return "OK"; } \ No newline at end of file