[JS IR] Support object declaration export
Fixes KT-39117 and KT-39367
This commit is contained in:
@@ -214,6 +214,11 @@ fun usefulDeclarations(roots: Iterable<IrDeclaration>, 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) {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
+10
-2
@@ -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<ExportedDeclaration>
|
||||
) : ExportedType()
|
||||
|
||||
class IntersectionType(val lhs: ExportedType, val rhs: ExportedType) : ExportedType()
|
||||
|
||||
fun withNullability(nullable: Boolean) =
|
||||
if (nullable) Nullable(this) else this
|
||||
|
||||
+50
-12
@@ -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
|
||||
}
|
||||
|
||||
+2
-2
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
+22
-14
@@ -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)
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,4 +82,15 @@ class A4 {
|
||||
var _varCustomWithField: Int = 1
|
||||
get() = field * 10
|
||||
set(value) { field = value * 10 }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
object O0
|
||||
|
||||
object O {
|
||||
val x = 10
|
||||
fun foo() = 20
|
||||
}
|
||||
|
||||
fun takesO(o: O): Int =
|
||||
O.x + O.foo()
|
||||
+5
@@ -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";
|
||||
}
|
||||
|
||||
+6
@@ -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";
|
||||
}
|
||||
@@ -30,5 +30,10 @@ declare namespace JS_TESTS {
|
||||
class FC extends foo.OC {
|
||||
constructor();
|
||||
}
|
||||
const O1: {
|
||||
} & foo.OC;
|
||||
const O2: {
|
||||
foo(): number;
|
||||
} & foo.OC;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,4 +40,10 @@ open class OC(
|
||||
private fun privateFun(): String = "privateFun"
|
||||
}
|
||||
|
||||
final class FC : OC(true, "FC")
|
||||
final class FC : OC(true, "FC")
|
||||
|
||||
object O1 : OC(true, "O1")
|
||||
|
||||
object O2 : OC(true, "O2") {
|
||||
fun foo(): Int = 10
|
||||
}
|
||||
+12
-2
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
Reference in New Issue
Block a user