chore: intermidiate step of a new way of generating object type.
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 5b72f66..39ccba1 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
@@ -20,7 +20,8 @@
class ExportedNamespace(
val name: String,
- val declarations: List<ExportedDeclaration>
+ val declarations: List<ExportedDeclaration>,
+ val isSynthetic: Boolean = false
) : ExportedDeclaration()
data class ExportedFunction(
@@ -59,7 +60,6 @@
val isField: Boolean,
val irGetter: IrFunction?,
val irSetter: IrFunction?,
- val exportedObject: ExportedClass? = null,
) : ExportedDeclaration()
@@ -78,6 +78,16 @@
val ir: IrClass
) : ExportedDeclaration()
+data class ExportedObject(
+ val name: String,
+ val superClass: ExportedType? = null,
+ val superInterfaces: List<ExportedType> = emptyList(),
+ val members: List<ExportedDeclaration>,
+ val nestedClasses: List<ExportedClass>,
+ val ir: IrClass,
+ val irGetter: IrFunction,
+) : ExportedDeclaration()
+
class ExportedParameter(
val name: String,
val type: ExportedType,
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 379acfb..9b86bf8 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
@@ -353,43 +353,29 @@
val name = klass.getExportedIdentifier()
- val exportedClass = ExportedClass(
- name = name,
- isInterface = klass.isInterface,
- isAbstract = klass.modality == Modality.ABSTRACT,
- superClass = superType,
- superInterfaces = superInterfaces,
- typeParameters = typeParameters,
- members = members,
- nestedClasses = nestedClasses,
- ir = klass
- )
-
- 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(
+ return if (klass.kind == ClassKind.OBJECT) {
+ ExportedObject(
name = name,
- type = t,
- mutable = false,
- isMember = klass.parent is IrClass,
- isStatic = !klass.isInner,
- isAbstract = false,
- isProtected = klass.visibility == DescriptorVisibilities.PROTECTED,
- irGetter = context.mapping.objectToGetInstanceFunction[klass]!!,
- irSetter = null,
- exportedObject = exportedClass,
- isField = false,
+ superClass = superType,
+ superInterfaces = superInterfaces,
+ members = members,
+ nestedClasses = nestedClasses,
+ ir = klass,
+ irGetter = context.mapping.objectToGetInstanceFunction[klass]!!
+ )
+ } else {
+ ExportedClass(
+ name = name,
+ isInterface = klass.isInterface,
+ isAbstract = klass.modality == Modality.ABSTRACT,
+ superClass = superType,
+ superInterfaces = superInterfaces,
+ typeParameters = typeParameters,
+ members = members,
+ nestedClasses = nestedClasses,
+ ir = klass
)
}
-
- return exportedClass
}
private fun exportAsEnumMember(
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 04cd437..ce0722f 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
@@ -84,14 +84,25 @@
is ExportedConstructor -> emptyList()
is ExportedConstructSignature -> emptyList()
+ is ExportedObject -> {
+ require(namespace != null) { "Only namespaced objects are allowed" }
+ val getter = JsNameRef(namer.getNameForStaticDeclaration(declaration.irGetter))
+
+ val newNameSpace = jsElementAccess(declaration.name, namespace)
+
+ val objectProperties = declaration.members.filterIsInstance<ExportedObject>()
+
+ val staticsExport = (objectProperties + declaration.nestedClasses)
+ .flatMap { generateDeclarationExport(it, newNameSpace, esModules) }
+
+ listOf(defineProperty(namespace, declaration.name, getter, null).makeStmt()) + staticsExport
+ }
+
is ExportedProperty -> {
require(namespace != null) { "Only namespaced properties are allowed" }
- val underlying: List<JsStatement> = declaration.exportedObject?.let {
- generateDeclarationExport(it, null, esModules)
- } ?: emptyList()
val getter = declaration.irGetter?.let { JsNameRef(namer.getNameForStaticDeclaration(it)) }
val setter = declaration.irSetter?.let { JsNameRef(namer.getNameForStaticDeclaration(it)) }
- listOf(defineProperty(namespace, declaration.name, getter, setter).makeStmt()) + underlying
+ listOf(defineProperty(namespace, declaration.name, getter, setter).makeStmt())
}
is ErrorDeclaration -> emptyList()
@@ -121,15 +132,13 @@
.takeIf { !declaration.ir.isInner }.orEmpty()
// Nested objects are exported as static properties
- val staticProperties = declaration.members.mapNotNull {
- (it as? ExportedProperty)?.takeIf { it.isStatic }
- }
+ val objectProperties = declaration.members.filterIsInstance<ExportedObject>()
val innerClassesAssignments = declaration.nestedClasses
.filter { it.ir.isInner }
.map { it.generateInnerClassAssignment(declaration) }
- val staticsExport = (staticFunctions + staticProperties + declaration.nestedClasses)
+ val staticsExport = (staticFunctions + objectProperties + declaration.nestedClasses)
.flatMap { generateDeclarationExport(it, newNameSpace, esModules) }
listOfNotNull(klassExport) + staticsExport + innerClassesAssignments
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 28e3475..51059b2 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
@@ -5,314 +5,400 @@
package org.jetbrains.kotlin.ir.backend.js.export
+import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
+import org.jetbrains.kotlin.ir.backend.js.utils.getFqNameWithJsNameWhenAvailable
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.js.common.isValidES5Identifier
+import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.serialization.js.ModuleKind
+const val Nullable = "Nullable"
+const val doNotImplementIt = "__doNotImplementIt"
+const val objectInstances = "__object_instances__"
+const val Instance = "INSTANCE"
// TODO: Support module kinds other than plain
fun ExportedModule.toTypeScript(): String {
- return wrapTypeScript(name, moduleKind, declarations.toTypeScript(moduleKind))
+ return ExportModelToTsDeclarations().generateTypeScriptFor(name, moduleKind, this)
}
-fun wrapTypeScript(name: String, moduleKind: ModuleKind, dts: String): String {
- val declareKeyword = when (moduleKind) {
- ModuleKind.PLAIN -> ""
- else -> "declare "
- }
- val types = """
- type Nullable<T> = T | null | undefined
- ${declareKeyword}const __doNotImplementIt: unique symbol
- type __doNotImplementIt = typeof __doNotImplementIt
- """.trimIndent().prependIndent(moduleKind.indent) + "\n"
-
- val declarationsDts = types + dts
-
- val namespaceName = sanitizeName(name, withHash = false)
-
- return when (moduleKind) {
- ModuleKind.PLAIN -> "declare namespace $namespaceName {\n$declarationsDts\n}\n"
- ModuleKind.AMD, ModuleKind.COMMON_JS, ModuleKind.ES -> declarationsDts
- ModuleKind.UMD -> "$declarationsDts\nexport as namespace $namespaceName;"
- }
-}
-
-private val ModuleKind.indent: String
- get() = if (this == ModuleKind.PLAIN) " " else ""
-
fun List<ExportedDeclaration>.toTypeScript(moduleKind: ModuleKind): String {
- return joinToString("\n") {
- it.toTypeScript(
- indent = moduleKind.indent,
- prefix = if (moduleKind == ModuleKind.PLAIN) "" else "export "
- )
- }
+ return ExportModelToTsDeclarations().generateTypeScriptFor(moduleKind, this)
}
-fun List<ExportedDeclaration>.toTypeScript(indent: String): String =
- joinToString("") { it.toTypeScript(indent) + "\n" }
+private class ExportModelToTsDeclarations {
+ private var declaredObjectClasses = mutableListOf<ExportedClass>()
-fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): String =
- indent + when (this) {
- is ErrorDeclaration -> "/* ErrorDeclaration: $message */"
+ fun generateTypeScriptFor(name: String, moduleKind: ModuleKind, module: ExportedModule): String {
+ val declareKeyword = when (moduleKind) {
+ ModuleKind.PLAIN -> ""
+ else -> "declare "
+ }
+ val types = """
+ type $Nullable<T> = T | null | undefined
+ ${declareKeyword}const $doNotImplementIt: unique symbol
+ type $doNotImplementIt = typeof $doNotImplementIt
+ """.trimIndent().prependIndent(moduleKind.indent) + "\n"
- is ExportedNamespace ->
- "${prefix}namespace $name {\n" + declarations.toTypeScript("$indent ") + "$indent}"
+ val declarationsDts =
+ types + module.declarations.toTypeScript(moduleKind)
- is ExportedFunction -> {
- val visibility = if (isProtected) "protected " else ""
+ val namespaceName = sanitizeName(name, withHash = false)
- val keyword: String = when {
- isMember -> when {
- isStatic -> "static "
- isAbstract -> "abstract "
- else -> ""
+ return when (moduleKind) {
+ ModuleKind.PLAIN -> "declare namespace $namespaceName {\n$declarationsDts\n}\n"
+ ModuleKind.AMD, ModuleKind.COMMON_JS, ModuleKind.ES -> declarationsDts
+ ModuleKind.UMD -> "$declarationsDts\nexport as namespace $namespaceName;"
+ }
+ }
+
+ fun generateTypeScriptFor(moduleKind: ModuleKind, exportedDeclarations: List<ExportedDeclaration>): String {
+ return exportedDeclarations.toTypeScript(moduleKind)
+ }
+
+ private val ModuleKind.indent: String
+ get() = if (this == ModuleKind.PLAIN) " " else ""
+
+ private fun List<ExportedDeclaration>.toTypeScript(moduleKind: ModuleKind): String {
+ return joinToString("\n") {
+ it.toTypeScript(
+ indent = moduleKind.indent,
+ prefix = if (moduleKind == ModuleKind.PLAIN) "" else "export "
+ )
+ }
+ }
+
+ private fun List<ExportedDeclaration>.toTypeScript(indent: String): String =
+ joinToString("") { it.toTypeScript(indent) + "\n" }
+
+ private fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): String =
+ indent + when (this) {
+ is ErrorDeclaration -> "/* ErrorDeclaration: $message */"
+
+ is ExportedNamespace -> {
+ val body = declarations.toTypeScript("$indent ")
+ val objectInstances = if (isSynthetic) "" else "${generateObjectInstancesNamespace("$indent ")}\n"
+ "${prefix}namespace $name {\n" + body + objectInstances + "$indent}"
+ }
+
+ is ExportedFunction -> {
+ val visibility = if (isProtected) "protected " else ""
+
+ val keyword: String = when {
+ isMember -> when {
+ isStatic -> "static "
+ isAbstract -> "abstract "
+ else -> ""
+ }
+ else -> "function "
}
- else -> "function "
- }
- val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) }
+ val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) }
- val renderedTypeParameters =
- if (typeParameters.isNotEmpty())
- "<" + typeParameters.joinToString(", ") { it.toTypeScript(indent) } + ">"
- else
- ""
+ val renderedTypeParameters =
+ if (typeParameters.isNotEmpty())
+ "<" + typeParameters.joinToString(", ") { it.toTypeScript(indent) } + ">"
+ else
+ ""
- val renderedReturnType = returnType.toTypeScript(indent)
- val containsUnresolvedChar = !name.isValidES5Identifier()
+ val renderedReturnType = returnType.toTypeScript(indent)
+ val containsUnresolvedChar = !name.isValidES5Identifier()
- val escapedName = when {
- isMember && containsUnresolvedChar -> "\"$name\""
- else -> name
- }
-
- if (!isMember && containsUnresolvedChar) "" else "${prefix}$visibility$keyword$escapedName$renderedTypeParameters($renderedParameters): $renderedReturnType;"
- }
-
- is ExportedConstructor -> {
- val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) }
- "${visibility.keyword}constructor($renderedParameters);"
- }
-
- is ExportedConstructSignature -> {
- val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) }
- "new($renderedParameters): ${returnType.toTypeScript(indent)};"
- }
-
- is ExportedProperty -> {
- val visibility = if (isProtected) "protected " else ""
- val keyword = when {
- isMember -> (if (isAbstract) "abstract " else "")
- else -> if (mutable) "let " else "const "
- }
- val possibleStatic = if (isMember && isStatic) "static " else ""
- val containsUnresolvedChar = !name.isValidES5Identifier()
- val memberName = when {
- isMember && containsUnresolvedChar -> "\"$name\""
- else -> name
- }
- val typeToTypeScript = type.toTypeScript(indent)
- if (isMember && !isField) {
- val getter = "$prefix$visibility$possibleStatic${keyword}get $memberName(): $typeToTypeScript;"
- if (!mutable) getter
- else getter + "\n" + "$indent$prefix$visibility$possibleStatic${keyword}set $memberName(value: $typeToTypeScript);"
- } else {
- if (!isMember && containsUnresolvedChar) ""
- else {
- val readonly = if (isMember && !mutable) "readonly " else ""
- "$prefix$visibility$possibleStatic$keyword$readonly$memberName: $typeToTypeScript;"
+ val escapedName = when {
+ isMember && containsUnresolvedChar -> "\"$name\""
+ else -> name
}
+
+ if (!isMember && containsUnresolvedChar) "" else "${prefix}$visibility$keyword$escapedName$renderedTypeParameters($renderedParameters): $renderedReturnType;"
}
- }
- is ExportedClass -> {
- val keyword = if (isInterface) "interface" else "class"
- val superInterfacesKeyword = if (isInterface) "extends" else "implements"
+ is ExportedConstructor -> {
+ val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) }
+ "${visibility.keyword}constructor($renderedParameters);"
+ }
- val superClassClause = superClass?.let { it.toExtendsClause(indent) } ?: ""
- val superInterfacesClause = superInterfaces.toImplementsClause(superInterfacesKeyword, indent)
+ is ExportedConstructSignature -> {
+ val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) }
+ "new($renderedParameters): ${returnType.toTypeScript(indent)};"
+ }
- val members = members
- .let { if (shouldNotBeImplemented()) it.withMagicProperty() else it }
- .map {
- if (!ir.isInner || it !is ExportedFunction || !it.isStatic) {
- it
- } else {
- // Remove $outer argument from secondary constructors of inner classes
- it.copy(parameters = it.parameters.drop(1))
+ is ExportedProperty -> {
+ val visibility = if (isProtected) "protected " else ""
+ val keyword = when {
+ isMember -> (if (isAbstract) "abstract " else "")
+ else -> if (mutable) "let " else "const "
+ }
+ val possibleStatic = if (isMember && isStatic) "static " else ""
+ val containsUnresolvedChar = !name.isValidES5Identifier()
+ val memberName = when {
+ isMember && containsUnresolvedChar -> "\"$name\""
+ else -> name
+ }
+ val typeToTypeScript = type.toTypeScript(indent)
+ if (isMember && !isField) {
+ val getter = "$prefix$visibility$possibleStatic${keyword}get $memberName(): $typeToTypeScript;"
+ if (!mutable) getter
+ else getter + "\n" + "$indent$prefix$visibility$possibleStatic${keyword}set $memberName(value: $typeToTypeScript);"
+ } else {
+ if (!isMember && containsUnresolvedChar) ""
+ else {
+ val readonly = if (isMember && !mutable) "readonly " else ""
+ "$prefix$visibility$possibleStatic$keyword$readonly$memberName: $typeToTypeScript;"
}
}
+ }
- val (innerClasses, nonInnerClasses) = nestedClasses.partition { it.ir.isInner }
- val innerClassesProperties = innerClasses.map { it.toReadonlyProperty() }
- val membersString = (members + innerClassesProperties).joinToString("") { it.toTypeScript("$indent ") + "\n" }
+ is ExportedObject ->
+ generateObjectProperty().toTypeScript("", prefix).also {
+ declaredObjectClasses.add(generateObjectClass())
+ }
- // If there are no exported constructors, add a private constructor to disable default one
- val privateCtorString =
- if (!isInterface && !isAbstract && members.none { it is ExportedConstructor })
- "$indent private constructor();\n"
- else
- ""
+ is ExportedClass -> {
+ val keyword = if (isInterface) "interface" else "class"
+ val superInterfacesKeyword = if (isInterface) "extends" else "implements"
- val renderedTypeParameters =
- if (typeParameters.isNotEmpty())
- "<" + typeParameters.joinToString(", ") + ">"
- else
- ""
+ val superClassClause = superClass?.let { it.toExtendsClause(indent) } ?: ""
+ val superInterfacesClause = superInterfaces.toImplementsClause(superInterfacesKeyword, indent)
- val modifiers = if (isAbstract && !isInterface) "abstract " else ""
+ val members = members
+ .let { if (shouldNotBeImplemented()) it.withMagicProperty() else it }
+ .map {
+ if (!ir.isInner || it !is ExportedFunction || !it.isStatic) {
+ it
+ } else {
+ // Remove $outer argument from secondary constructors of inner classes
+ it.copy(parameters = it.parameters.drop(1))
+ }
+ }
- val bodyString = privateCtorString + membersString + indent
+ val (innerClasses, nonInnerClasses) = nestedClasses.partition { it.ir.isInner }
+ val innerClassesProperties = innerClasses.map { it.toReadonlyProperty() }
+ val membersString = (members + innerClassesProperties).joinToString("") { it.toTypeScript("$indent ") + "\n" }
- val nestedClasses = nonInnerClasses + innerClasses.map { it.withProtectedConstructors() }
- val klassExport =
- "$prefix$modifiers$keyword $name$renderedTypeParameters$superClassClause$superInterfacesClause {\n$bodyString}"
- val staticsExport =
- if (nestedClasses.isNotEmpty()) "\n" + ExportedNamespace(name, nestedClasses).toTypeScript(indent, prefix) else ""
+ // If there are no exported constructors, add a private constructor to disable default one
+ val privateCtorString =
+ if (!isInterface && !isAbstract && members.none { it is ExportedConstructor })
+ "$indent private constructor();\n"
+ else
+ ""
- if (name.isValidES5Identifier()) klassExport + staticsExport else ""
+ val renderedTypeParameters =
+ if (typeParameters.isNotEmpty())
+ "<" + typeParameters.joinToString(", ") + ">"
+ else
+ ""
+
+ val modifiers = if (isAbstract && !isInterface) "abstract " else ""
+
+ val bodyString = privateCtorString + membersString + indent
+
+ val nestedClasses = nonInnerClasses + innerClasses.map { it.withProtectedConstructors() }
+ val klassExport =
+ "$prefix$modifiers$keyword $name$renderedTypeParameters$superClassClause$superInterfacesClause {\n$bodyString}"
+ val staticsExport =
+ if (nestedClasses.isNotEmpty()) "\n" + ExportedNamespace(name, nestedClasses, true).toTypeScript(indent, prefix) else ""
+
+ if (name.isValidES5Identifier()) klassExport + staticsExport else ""
+ }
+ }
+
+ private fun ExportedObject.generateObjectClass(): ExportedClass {
+ return ExportedClass(
+ ir = ir,
+ name = Instance,
+ isInterface = false,
+ isAbstract = false,
+ members = members,
+ superClass = superClass,
+ superInterfaces = superInterfaces,
+ typeParameters = emptyList(),
+ nestedClasses = nestedClasses,
+ )
+ }
+
+ private fun ExportedObject.generateObjectProperty(): ExportedProperty {
+ val instancesNamespace = Name.identifier(objectInstances)
+ val instanceRef = Name.identifier(Instance)
+ val classRef = ir.getFqNameWithJsNameWhenAvailable(instancesNamespace).child(instanceRef).asString()
+ val type = ExportedType.IntersectionType(
+ ExportedType.ClassType(classRef, emptyList(), ir),
+ ExportedType.TypeOf(classRef)
+ )
+ return ExportedProperty(
+ name = name,
+ type = type,
+ mutable = false,
+ isMember = ir.parent is IrClass,
+ isStatic = !ir.isInner,
+ isAbstract = false,
+ isProtected = ir.visibility == DescriptorVisibilities.PROTECTED,
+ irGetter = irGetter,
+ irSetter = null,
+ isField = false,
+ )
+ }
+
+ private fun ExportedType.toExtendsClause(indent: String): String {
+ val isImplicitlyExportedType = this is ExportedType.ImplicitlyExportedType
+ val extendsClause = " extends ${toTypeScript(indent, isImplicitlyExportedType)}"
+ return when {
+ isImplicitlyExportedType -> " /*$extendsClause */"
+ else -> extendsClause
}
}
-fun ExportedType.toExtendsClause(indent: String): String {
- val isImplicitlyExportedType = this is ExportedType.ImplicitlyExportedType
- val extendsClause = " extends ${toTypeScript(indent, isImplicitlyExportedType)}"
- return when {
- isImplicitlyExportedType -> " /*$extendsClause */"
- else -> extendsClause
- }
-}
-
-fun List<ExportedType>.toImplementsClause(superInterfacesKeyword: String, indent: String): String {
- val (exportedInterfaces, nonExportedInterfaces) = partition { it !is ExportedType.ImplicitlyExportedType }
- val listOfNonExportedInterfaces = nonExportedInterfaces.joinToString(", ") {
- (it as ExportedType.ImplicitlyExportedType).type.toTypeScript(indent, true)
- }
- return when {
- exportedInterfaces.isEmpty() && nonExportedInterfaces.isNotEmpty() ->
- " /* $superInterfacesKeyword $listOfNonExportedInterfaces */"
- exportedInterfaces.isNotEmpty() -> {
- val nonExportedInterfacesTsString = if (nonExportedInterfaces.isNotEmpty()) "/*, $listOfNonExportedInterfaces */" else ""
- " $superInterfacesKeyword " + exportedInterfaces.joinToString(", ") { it.toTypeScript(indent) } + nonExportedInterfacesTsString
+ private fun List<ExportedType>.toImplementsClause(superInterfacesKeyword: String, indent: String): String {
+ val (exportedInterfaces, nonExportedInterfaces) = partition { it !is ExportedType.ImplicitlyExportedType }
+ val listOfNonExportedInterfaces = nonExportedInterfaces.joinToString(", ") {
+ (it as ExportedType.ImplicitlyExportedType).type.toTypeScript(indent, true)
}
- else -> ""
+ return when {
+ exportedInterfaces.isEmpty() && nonExportedInterfaces.isNotEmpty() ->
+ " /* $superInterfacesKeyword $listOfNonExportedInterfaces */"
+ exportedInterfaces.isNotEmpty() -> {
+ val nonExportedInterfacesTsString = if (nonExportedInterfaces.isNotEmpty()) "/*, $listOfNonExportedInterfaces */" else ""
+ " $superInterfacesKeyword " + exportedInterfaces.joinToString(", ") { it.toTypeScript(indent) } + nonExportedInterfacesTsString
+ }
+ else -> ""
+ }
}
-}
-fun ExportedClass.shouldNotBeImplemented(): Boolean {
- return (isInterface && !ir.isExternal) || superInterfaces.any { it is ExportedType.ClassType && !it.ir.isExternal }
-}
+ private fun ExportedClass.shouldNotBeImplemented(): Boolean {
+ return (isInterface && !ir.isExternal) || superInterfaces.any { it is ExportedType.ClassType && !it.ir.isExternal }
+ }
-fun List<ExportedDeclaration>.withMagicProperty(): List<ExportedDeclaration> {
- return plus(
- ExportedProperty(
- "__doNotUseIt",
- ExportedType.TypeParameter("__doNotImplementIt"),
+ private fun List<ExportedDeclaration>.withMagicProperty(): List<ExportedDeclaration> {
+ return plus(
+ ExportedProperty(
+ "__doNotUseIt",
+ ExportedType.TypeParameter(doNotImplementIt),
+ mutable = false,
+ isMember = true,
+ isStatic = false,
+ isAbstract = false,
+ isProtected = false,
+ isField = true,
+ irGetter = null,
+ irSetter = null,
+ )
+ )
+ }
+
+ private fun IrClass.asNestedClassAccess(): String {
+ val name = getJsNameOrKotlinName().identifier
+ if (parent !is IrClass) return name
+ return "${parentAsClass.asNestedClassAccess()}.$name"
+ }
+
+ private fun ExportedClass.withProtectedConstructors(): ExportedClass {
+ return copy(members = members.map {
+ if (it !is ExportedConstructor || it.isProtected) {
+ it
+ } else {
+ it.copy(visibility = ExportedVisibility.PROTECTED)
+ }
+ })
+ }
+
+ private fun ExportedClass.toReadonlyProperty(): ExportedProperty {
+ val innerClassReference = ir.asNestedClassAccess()
+ val allPublicConstructors = members.asSequence()
+ .filterIsInstance<ExportedConstructor>()
+ .filterNot { it.isProtected }
+ .map {
+ ExportedConstructSignature(
+ parameters = it.parameters.drop(1),
+ returnType = ExportedType.TypeParameter(innerClassReference),
+ )
+ }
+ .toList()
+
+ val type = ExportedType.IntersectionType(
+ ExportedType.InlineInterfaceType(allPublicConstructors),
+ ExportedType.TypeOf(innerClassReference)
+ )
+
+ return ExportedProperty(
+ name = name,
+ type = type,
mutable = false,
isMember = true,
isStatic = false,
isAbstract = false,
isProtected = false,
- isField = true,
+ isField = false,
irGetter = null,
- irSetter = null,
+ irSetter = null
)
- )
-}
+ }
-fun IrClass.asNestedClassAccess(): String {
- val name = getJsNameOrKotlinName().identifier
- if (parent !is IrClass) return name
- return "${parentAsClass.asNestedClassAccess()}.$name"
-}
+ private fun ExportedParameter.toTypeScript(indent: String): String {
+ val name = sanitizeName(name, withHash = false)
+ val type = type.toTypeScript(indent)
+ val questionMark = if (hasDefaultValue) "?" else ""
+ return "$name$questionMark: $type"
+ }
-fun ExportedClass.withProtectedConstructors(): ExportedClass {
- return copy(members = members.map {
- if (it !is ExportedConstructor || it.isProtected) {
- it
+ private fun ExportedType.toTypeScript(indent: String, isInCommentContext: Boolean = false): String = when (this) {
+ is ExportedType.Primitive -> typescript
+ is ExportedType.Array -> "Array<${elementType.toTypeScript(indent, isInCommentContext)}>"
+ is ExportedType.Function -> "(" + parameterTypes
+ .withIndex()
+ .joinToString(", ") { (index, type) ->
+ "p$index: ${type.toTypeScript(indent, isInCommentContext)}"
+ } + ") => " + returnType.toTypeScript(indent, isInCommentContext)
+
+ is ExportedType.ClassType ->
+ name + if (arguments.isNotEmpty()) "<${arguments.joinToString(", ") { it.toTypeScript(indent, isInCommentContext) }}>" else ""
+ is ExportedType.TypeOf ->
+ "typeof $name"
+
+ is ExportedType.ErrorType -> if (isInCommentContext) comment else "any /*$comment*/"
+ is ExportedType.Nullable -> "$Nullable<" + baseType.toTypeScript(indent, isInCommentContext) + ">"
+ is ExportedType.InlineInterfaceType -> {
+ members.joinToString(prefix = "{\n", postfix = "$indent}", separator = "") { it.toTypeScript("$indent ") + "\n" }
+ }
+ is ExportedType.IntersectionType -> {
+ lhs.toTypeScript(indent) + " & " + rhs.toTypeScript(indent, isInCommentContext)
+ }
+ is ExportedType.UnionType -> {
+ lhs.toTypeScript(indent) + " | " + rhs.toTypeScript(indent, isInCommentContext)
+ }
+ is ExportedType.LiteralType.StringLiteralType -> "\"$value\""
+ is ExportedType.LiteralType.NumberLiteralType -> value.toString()
+ is ExportedType.ImplicitlyExportedType -> {
+ val typeString = type.toTypeScript("", true)
+ if (isInCommentContext) typeString else ExportedType.Primitive.Any.toTypeScript(indent) + "/* $typeString */"
+ }
+ is ExportedType.TypeParameter -> if (constraint == null) {
+ name
} else {
- it.copy(visibility = ExportedVisibility.PROTECTED)
+ "$name extends ${constraint.toTypeScript(indent, isInCommentContext)}"
}
- })
-}
+ }
-fun ExportedClass.toReadonlyProperty(): ExportedProperty {
- val innerClassReference = ir.asNestedClassAccess()
- val allPublicConstructors = members.asSequence()
- .filterIsInstance<ExportedConstructor>()
- .filterNot { it.isProtected }
- .map {
- ExportedConstructSignature(
- parameters = it.parameters.drop(1),
- returnType = ExportedType.TypeParameter(innerClassReference),
- )
+ private fun generateObjectInstancesNamespace(indent: String): String {
+ if (declaredObjectClasses.isEmpty()) return ""
+
+ val result = StringBuilder()
+ var previousDeclaredObjectClasses = declaredObjectClasses
+ declaredObjectClasses = mutableListOf()
+
+ while (previousDeclaredObjectClasses.isNotEmpty()) {
+ result.append(previousDeclaredObjectClasses.toExportedNamespace().toTypeScript("$indent "))
+ previousDeclaredObjectClasses = declaredObjectClasses
+ declaredObjectClasses = mutableListOf()
}
- .toList()
- val type = ExportedType.IntersectionType(
- ExportedType.InlineInterfaceType(allPublicConstructors),
- ExportedType.TypeOf(innerClassReference)
- )
+ return "${indent}namespace $objectInstances {\n$result\n$indent}"
+ }
- return ExportedProperty(
- name = name,
- type = type,
- mutable = false,
- isMember = true,
- isStatic = false,
- isAbstract = false,
- isProtected = false,
- isField = false,
- irGetter = null,
- irSetter = null
- )
+ private fun List<ExportedClass>.toExportedNamespace(): List<ExportedNamespace> {
+ return map {
+ ExportedNamespace(it.ir.getFqNameWithJsNameWhenAvailable(false).asString(), listOf(it), true)
+ }
+ }
}
-
-fun ExportedParameter.toTypeScript(indent: String): String {
- val name = sanitizeName(name, withHash = false)
- val type = type.toTypeScript(indent)
- val questionMark = if (hasDefaultValue) "?" else ""
- return "$name$questionMark: $type"
-}
-
-fun ExportedType.toTypeScript(indent: String, isInCommentContext: Boolean = false): String = when (this) {
- is ExportedType.Primitive -> typescript
- is ExportedType.Array -> "Array<${elementType.toTypeScript(indent, isInCommentContext)}>"
- is ExportedType.Function -> "(" + parameterTypes
- .withIndex()
- .joinToString(", ") { (index, type) ->
- "p$index: ${type.toTypeScript(indent, isInCommentContext)}"
- } + ") => " + returnType.toTypeScript(indent, isInCommentContext)
-
- is ExportedType.ClassType ->
- name + if (arguments.isNotEmpty()) "<${arguments.joinToString(", ") { it.toTypeScript(indent, isInCommentContext) }}>" else ""
- is ExportedType.TypeOf ->
- "typeof $name"
-
- is ExportedType.ErrorType -> if (isInCommentContext) comment else "any /*$comment*/"
- is ExportedType.Nullable -> "Nullable<" + baseType.toTypeScript(indent, isInCommentContext) + ">"
- is ExportedType.InlineInterfaceType -> {
- members.joinToString(prefix = "{\n", postfix = "$indent}", separator = "") { it.toTypeScript("$indent ") + "\n" }
- }
- is ExportedType.IntersectionType -> {
- lhs.toTypeScript(indent) + " & " + rhs.toTypeScript(indent, isInCommentContext)
- }
- is ExportedType.UnionType -> {
- lhs.toTypeScript(indent) + " | " + rhs.toTypeScript(indent, isInCommentContext)
- }
- is ExportedType.LiteralType.StringLiteralType -> "\"$value\""
- is ExportedType.LiteralType.NumberLiteralType -> value.toString()
- is ExportedType.ImplicitlyExportedType -> {
- val typeString = type.toTypeScript("", true)
- if (isInCommentContext) typeString else ExportedType.Primitive.Any.toTypeScript(indent) + "/* $typeString */"
- }
- is ExportedType.TypeParameter -> if (constraint == null) {
- name
- } else {
- "$name extends ${constraint.toTypeScript(indent, isInCommentContext)}"
- }
-}
\ No newline at end of file
diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt
index 7e454cf..d43f35c 100644
--- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt
+++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt
@@ -84,7 +84,7 @@
}
}
- val dts = wrapTypeScript(mainModuleName, moduleKind, exportData.values.flatMap { it.values.flatMap { it } }.toTypeScript(moduleKind))
+ val dts = ExportedModule(mainModuleName, moduleKind, exportData.values.flatMap { it.values.flatten() }).toTypeScript()
modules.forEach { module ->
module.files.forEach { StaticMembersLowering(backendContext).lower(it) }
diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/IrJsUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/IrJsUtils.kt
index 1c8b7ed..1df0f73 100644
--- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/IrJsUtils.kt
+++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/IrJsUtils.kt
@@ -7,6 +7,7 @@
import org.jetbrains.kotlin.descriptors.isClass
import org.jetbrains.kotlin.descriptors.isInterface
+import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.export.isExported
import org.jetbrains.kotlin.ir.declarations.*
@@ -14,6 +15,7 @@
import org.jetbrains.kotlin.ir.symbols.IrReturnableBlockSymbol
import org.jetbrains.kotlin.ir.util.parentClassOrNull
import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.name.Name
fun IrDeclaration.isExportedMember(context: JsIrBackendContext) =
(this is IrDeclarationWithVisibility && visibility.isPublicAPI) &&
@@ -29,15 +31,25 @@
return target.owner.statements.lastOrNull() === this
}
-fun IrDeclarationWithName.getFqNameWithJsNameWhenAvailable(shouldIncludePackage: Boolean): FqName {
- val name = getJsNameOrKotlinName()
- return when (val parent = parent) {
- is IrDeclarationWithName -> parent.getFqNameWithJsNameWhenAvailable(shouldIncludePackage).child(name)
- is IrPackageFragment -> getKotlinOrJsQualifier(parent, shouldIncludePackage)?.child(name) ?: FqName(name.identifier)
- else -> FqName(name.identifier)
+private fun IrElement.getFqNameWithJsNameWhenAvailable(getSubQualifier: (IrPackageFragment) -> FqName?): FqName? {
+ return when (this) {
+ is IrDeclarationWithName -> {
+ val name = getJsNameOrKotlinName()
+ return parent.getFqNameWithJsNameWhenAvailable(getSubQualifier)?.child(name) ?: FqName(name.identifier)
+ }
+ is IrPackageFragment -> getSubQualifier(this)
+ else -> null
}
}
+fun IrDeclarationWithName.getFqNameWithJsNameWhenAvailable(shouldIncludePackage: Boolean): FqName {
+ return getFqNameWithJsNameWhenAvailable { getKotlinOrJsQualifier(it, shouldIncludePackage) }!!
+}
+
+fun IrDeclarationWithName.getFqNameWithJsNameWhenAvailable(subQualifier: Name): FqName {
+ return getFqNameWithJsNameWhenAvailable { getKotlinOrJsQualifier(it, true)?.child(subQualifier) }!!
+}
+
private fun getKotlinOrJsQualifier(parent: IrPackageFragment, shouldIncludePackage: Boolean): FqName? {
return (parent as? IrFile)?.getJsQualifier()?.let { FqName(it) } ?: parent.fqName.takeIf { shouldIncludePackage }
}
diff --git a/js/js.translator/testData/typescript-export/declarations/declarations.kt b/js/js.translator/testData/typescript-export/declarations/declarations.kt
index e303e35..703ba96 100644
--- a/js/js.translator/testData/typescript-export/declarations/declarations.kt
+++ b/js/js.translator/testData/typescript-export/declarations/declarations.kt
@@ -246,4 +246,43 @@
@JsName("NestedJsName")
class __NestJsNameTest(@JsName("value") val __value: Int)
+}
+
+//@JsExport
+//object Parent {
+// val value = 4
+// fun test(): String = "Test"
+//
+// class NestedClass
+// interface NestedInterface
+// object NestedObject
+// enum class NestedEnumClass { A, B }
+//
+// class NestedParentClass {
+// class NestedChildClass {
+// companion object {
+// class CompanionNestedClass
+// }
+// }
+// }
+//
+// object NestedParentObject {
+// object NestedChildObject
+// }
+//}
+
+@JsExport
+object Parent {
+ object Nested1 {
+ class Nested2 {
+ companion object {
+ class Nested3
+ }
+ }
+ }
+}
+
+@JsExport
+fun createNested3(): Parent.Nested1.Nested2.Companion.Nested3 {
+ return Parent.Nested1.Nested2.Companion.Nested3()
}
\ No newline at end of file