[JS IR] Support object declaration export

Fixes KT-39117 and KT-39367
This commit is contained in:
Svyatoslav Kuzmich
2020-06-19 10:40:37 +03:00
parent 4027dae594
commit 4a803e9d2f
17 changed files with 155 additions and 43 deletions
@@ -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")
}
@@ -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
@@ -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
}
@@ -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())
}
@@ -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)
}
}
@@ -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
)
}