[K/Wasm] Add simple TypeScript definitions generating ^KT-65009 Fixed

This commit is contained in:
Artem Kobzar
2024-01-16 11:10:27 +00:00
committed by Space Team
parent baa0748375
commit a55c65e3e2
35 changed files with 1176 additions and 32 deletions
@@ -27,6 +27,7 @@ data class ExportedModule(
class ExportedNamespace(
val name: String,
val declarations: List<ExportedDeclaration>,
val isPrivate: Boolean = false
) : ExportedDeclaration()
data class ExportedFunction(
@@ -100,7 +101,7 @@ data class ExportedObject(
override val members: List<ExportedDeclaration>,
override val nestedClasses: List<ExportedClass>,
override val ir: IrClass,
val irGetter: IrSimpleFunction
val irGetter: IrSimpleFunction? = null
) : ExportedClass()
class ExportedParameter(
@@ -113,6 +114,7 @@ sealed class ExportedType {
sealed class Primitive(val typescript: kotlin.String) : ExportedType() {
object Boolean : Primitive("boolean")
object Number : Primitive("number")
object BigInt : Primitive("bigint")
object ByteArray : Primitive("Int8Array")
object ShortArray : Primitive("Int16Array")
object IntArray : Primitive("Int32Array")
@@ -121,11 +123,14 @@ sealed class ExportedType {
object String : Primitive("string")
object Throwable : Primitive("Error")
object Any : Primitive("any")
object Unknown : Primitive("unknown")
object Undefined : Primitive("undefined")
object Unit : Primitive("void")
object Nothing : Primitive("never")
object UniqueSymbol : Primitive("unique symbol")
object Unknown : Primitive("unknown") {
override fun withNullability(nullable: kotlin.Boolean) =
if (nullable) this else NonNullable(this)
}
}
sealed class LiteralType<T : Any>(val value: T) : ExportedType() {
@@ -142,6 +147,7 @@ sealed class ExportedType {
class ClassType(val name: String, val arguments: List<ExportedType>, val ir: IrClass) : ExportedType()
class TypeParameter(val name: String, val constraint: ExportedType? = null) : ExportedType()
class Nullable(val baseType: ExportedType) : ExportedType()
class NonNullable(val baseType: ExportedType) : ExportedType()
class ErrorType(val comment: String) : ExportedType()
class TypeOf(val name: String) : ExportedType()
@@ -524,7 +524,7 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
return ExportedType.ErrorType("UnknownType ${type.render()}")
}
private fun exportTypeParameter(typeParameter: IrTypeParameter): ExportedType.TypeParameter {
fun exportTypeParameter(typeParameter: IrTypeParameter): ExportedType.TypeParameter {
val constraint = typeParameter.superTypes.asSequence()
.filter { it != context.irBuiltIns.anyNType }
.map {
@@ -550,12 +550,6 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
)
}
private fun ExportedDeclaration.withAttributesFor(declaration: IrDeclaration): ExportedDeclaration {
declaration.getDeprecated()?.let { attributes.add(ExportedAttribute.DeprecatedAttribute(it)) }
return this
}
private val currentlyProcessedTypes = hashSetOf<IrType>()
private fun exportType(type: IrType, shouldCalculateExportedSupertypeForImplicit: Boolean = true): ExportedType {
@@ -639,13 +633,6 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
.also { currentlyProcessedTypes.remove(type) }
}
private fun IrDeclarationWithName.getExportedIdentifier(): String =
with(getJsNameOrKotlinName()) {
if (isSpecial)
error("Cannot export special name: ${name.asString()} for declaration $fqNameWhenAvailable")
else identifier
}
private fun functionExportability(function: IrSimpleFunction): Exportability {
if (function.isInline && function.typeParameters.any { it.isReified })
return Exportability.Prohibited("Inline reified function")
@@ -811,7 +798,7 @@ fun IrDeclaration.isExportedImplicitlyOrExplicitly(context: JsIrBackendContext):
return shouldDeclarationBeExportedImplicitlyOrExplicitly(candidate, context)
}
private fun DescriptorVisibility.toExportedVisibility() =
fun DescriptorVisibility.toExportedVisibility() =
when (this) {
DescriptorVisibilities.PROTECTED -> ExportedVisibility.PROTECTED
else -> ExportedVisibility.DEFAULT
@@ -870,3 +857,17 @@ val strictModeReservedWords = setOf(
)
private val allReservedWords = reservedWords + strictModeReservedWords
fun ExportedDeclaration.withAttributesFor(declaration: IrDeclaration): ExportedDeclaration {
declaration.getDeprecated()?.let { attributes.add(ExportedAttribute.DeprecatedAttribute(it)) }
return this
}
fun IrDeclarationWithName.getExportedIdentifier(): String =
with(getJsNameOrKotlinName()) {
if (isSpecial)
error("Cannot export special name: ${name.asString()} for declaration $fqNameWhenAvailable")
else identifier
}
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.util.companionObject
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.isObject
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.utils.filterIsInstanceAnd
@@ -140,7 +141,10 @@ class ExportModelToJsStatements(
defineProperty(
namespace,
declaration.name,
staticContext.getNameForStaticDeclaration(declaration.irGetter).makeRef(),
staticContext.getNameForStaticDeclaration(
declaration.irGetter
?: error("Expect to have an object getter in its export model, but ${declaration.ir.fqNameWhenAvailable ?: declaration.name} doesn't have it")
).makeRef(),
null,
staticContext
).makeStmt()
@@ -8,7 +8,6 @@ package org.jetbrains.kotlin.ir.backend.js.export
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
import org.jetbrains.kotlin.ir.backend.js.lower.isEs6PrimaryConstructorReplacement
import org.jetbrains.kotlin.ir.backend.js.lower.isSyntheticPrimaryConstructor
import org.jetbrains.kotlin.ir.backend.js.utils.JsAnnotations
import org.jetbrains.kotlin.ir.backend.js.utils.getFqNameWithJsNameWhenAvailable
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
@@ -25,6 +24,7 @@ import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.kotlin.utils.addToStdlib.runIf
import org.jetbrains.kotlin.utils.findIsInstanceAnd
private const val NonNullable = "NonNullable"
private const val Nullable = "Nullable"
private const val objects = "_objects_"
private const val declare = "declare "
@@ -127,7 +127,7 @@ class ExportModelToTsDeclarations {
}
private fun ExportedNamespace.generateTypeScriptString(indent: String, prefix: String): String {
return "${prefix}namespace $name {\n" + declarations.toTypeScript("$indent ") + "$indent}"
return "${prefix.takeIf { !isPrivate } ?: "declare "}namespace $name {\n" + declarations.toTypeScript("$indent ") + "$indent}"
}
private fun ExportedConstructor.generateTypeScriptString(indent: String): String {
@@ -445,6 +445,7 @@ class ExportModelToTsDeclarations {
is ExportedType.ErrorType -> if (isInCommentContext) comment else "any /*$comment*/"
is ExportedType.Nullable -> "$Nullable<" + baseType.toTypeScript(indent, isInCommentContext) + ">"
is ExportedType.NonNullable -> "$NonNullable<" + baseType.toTypeScript(indent, isInCommentContext) + ">"
is ExportedType.InlineInterfaceType -> {
members.joinToString(prefix = "{\n", postfix = "$indent}", separator = "") { it.toTypeScript("$indent ") + "\n" }
}
@@ -365,7 +365,13 @@ class WasmSymbols(
val jsCode = getFunction("js", kotlinJsPackage)
val jsAnyType: IrType by lazy { getIrClass(FqName("kotlin.js.JsAny")).defaultType }
val jsReferenceClass by lazy { getIrClass(FqName("kotlin.js.JsReference")) }
val jsAnyType: IrType by lazy { getIrType("kotlin.js.JsAny") }
val jsBooleanType: IrType by lazy { getIrType("kotlin.js.JsBoolean") }
val jsStringType: IrType by lazy { getIrType("kotlin.js.JsString") }
val jsNumberType: IrType by lazy { getIrType("kotlin.js.JsNumber") }
val jsBigIntType: IrType by lazy { getIrType("kotlin.js.JsBigInt") }
val newJsArray = getInternalFunction("newJsArray")
@@ -424,6 +430,7 @@ class WasmSymbols(
private fun getEnumsFunction(name: String) = getFunction(name, enumsInternalPackage)
private fun getIrClass(fqName: FqName): IrClassSymbol = symbolTable.descriptorExtension.referenceClass(getClass(fqName))
private fun getIrType(fqName: String): IrType = getIrClass(FqName(fqName)).defaultType
private fun getInternalClass(name: String): IrClassSymbol = getIrClass(FqName("kotlin.wasm.internal.$name"))
fun getKFunctionType(type: IrType, list: List<IrType>): IrType {
return irBuiltIns.functionN(list.size).typeWith(list + type)
@@ -14,10 +14,14 @@ import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmModuleFragmentGenerator
import org.jetbrains.kotlin.backend.wasm.ir2wasm.toJsStringLiteral
import org.jetbrains.kotlin.backend.wasm.lower.markExportedDeclarations
import org.jetbrains.kotlin.backend.wasm.utils.SourceMapGenerator
import org.jetbrains.kotlin.backend.wasm.export.ExportModelGenerator
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.ir.backend.js.MainModule
import org.jetbrains.kotlin.ir.backend.js.ModulesStructure
import org.jetbrains.kotlin.ir.backend.js.SourceMapsInfo
import org.jetbrains.kotlin.ir.backend.js.export.ExportModelToTsDeclarations
import org.jetbrains.kotlin.ir.backend.js.export.ExportedModule
import org.jetbrains.kotlin.ir.backend.js.export.TypeScriptFragment
import org.jetbrains.kotlin.ir.backend.js.loadIr
import org.jetbrains.kotlin.ir.declarations.IrFactory
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
@@ -28,6 +32,7 @@ import org.jetbrains.kotlin.js.config.WasmTarget
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver
import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.utils.addToStdlib.runIf
import org.jetbrains.kotlin.wasm.ir.convertors.WasmIrToBinary
import org.jetbrains.kotlin.wasm.ir.convertors.WasmIrToText
@@ -35,13 +40,15 @@ import org.jetbrains.kotlin.wasm.ir.source.location.SourceLocation
import org.jetbrains.kotlin.wasm.ir.source.location.SourceLocationMapping
import java.io.ByteArrayOutputStream
import java.io.File
import kotlin.math.exp
class WasmCompilerResult(
val wat: String?,
val jsUninstantiatedWrapper: String?,
val jsWrapper: String,
val wasm: ByteArray,
val debugInformation: DebugInformation?
val debugInformation: DebugInformation?,
val dts: String?
)
class DebugInformation(
@@ -49,13 +56,20 @@ class DebugInformation(
val sourceMapForText: String?,
)
data class LoweredIrWithExtraArtifacts(
val loweredIr: List<IrModuleFragment>,
val backendContext: WasmBackendContext,
val typeScriptFragment: TypeScriptFragment?
)
fun compileToLoweredIr(
depsDescriptors: ModulesStructure,
phaseConfig: PhaseConfig,
irFactory: IrFactory,
exportedDeclarations: Set<FqName> = emptySet(),
generateTypeScriptFragment: Boolean,
propertyLazyInitialization: Boolean,
): Pair<List<IrModuleFragment>, WasmBackendContext> {
): LoweredIrWithExtraArtifacts {
val mainModule = depsDescriptors.mainModule
val configuration = depsDescriptors.compilerConfiguration
val (moduleFragment, dependencyModules, irBuiltIns, symbolTable, irLinker) = loadIr(
@@ -90,6 +104,13 @@ fun compileToLoweredIr(
for (file in module.files)
markExportedDeclarations(context, file, exportedDeclarations)
val typeScriptFragment = runIf(generateTypeScriptFragment) {
val exportModel = ExportModelGenerator(context).generateExport(allModules)
val exportModelToDtsTranslator = ExportModelToTsDeclarations()
val fragment = exportModelToDtsTranslator.generateTypeScriptFragment(ModuleKind.ES, exportModel.declarations)
TypeScriptFragment(exportModelToDtsTranslator.generateTypeScript("", ModuleKind.ES, listOf(fragment)))
}
val phaserState = PhaserState<IrModuleFragment>()
loweringList.forEachIndexed { _, lowering ->
allModules.forEach { module ->
@@ -97,12 +118,13 @@ fun compileToLoweredIr(
}
}
return Pair(allModules, context)
return LoweredIrWithExtraArtifacts(allModules, context, typeScriptFragment)
}
fun compileWasm(
allModules: List<IrModuleFragment>,
backendContext: WasmBackendContext,
typeScriptFragment: TypeScriptFragment?,
baseFileName: String,
emitNameSection: Boolean = false,
allowIncompleteImplementations: Boolean = false,
@@ -160,6 +182,7 @@ fun compileWasm(
jsWrapper = compiledWasmModule.generateAsyncWasiWrapper("./$baseFileName.wasm")
}
return WasmCompilerResult(
wat = wat,
jsUninstantiatedWrapper = jsUninstantiatedWrapper,
@@ -169,6 +192,7 @@ fun compileWasm(
sourceMapGeneratorForBinary?.generate(),
sourceMapGeneratorForText?.generate(),
),
dts = typeScriptFragment?.raw
)
}
@@ -355,4 +379,8 @@ fun writeCompilationResult(
result.debugInformation?.sourceMapForText?.let {
File(dir, "$fileNameBase.wat.map").writeText(it)
}
if (result.dts != null) {
File(dir, "$fileNameBase.d.ts").writeText(result.dts)
}
}
@@ -0,0 +1,318 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.wasm.export
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.backend.js.export.*
import org.jetbrains.kotlin.ir.backend.js.utils.getFqNameWithJsNameWhenAvailable
import org.jetbrains.kotlin.ir.backend.js.utils.isJsExport
import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
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.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.utils.addToStdlib.runIf
import org.jetbrains.kotlin.utils.memoryOptimizedFilter
import org.jetbrains.kotlin.utils.memoryOptimizedMap
import org.jetbrains.kotlin.utils.memoryOptimizedMapNotNull
private const val NOT_EXPORTED_NAMESPACE = "not.exported"
class ExportModelGenerator(val context: WasmBackendContext) {
private val excludedFromExport = setOf<IrDeclaration>(
context.wasmSymbols.jsRelatedSymbols.jsReferenceClass.owner,
context.wasmSymbols.jsRelatedSymbols.jsAnyType.classOrFail.owner,
context.wasmSymbols.jsRelatedSymbols.jsNumberType.classOrFail.owner,
context.wasmSymbols.jsRelatedSymbols.jsStringType.classOrFail.owner,
context.wasmSymbols.jsRelatedSymbols.jsBooleanType.classOrFail.owner,
context.wasmSymbols.jsRelatedSymbols.jsBigIntType.classOrFail.owner
)
private fun collectAllTheDeclarationsToExport(modules: Iterable<IrModuleFragment>): Iterable<IrDeclaration> {
val declarationsToExport = mutableSetOf<IrDeclaration>()
val queue = ArrayDeque<IrDeclaration>().apply {
modules.asSequence()
.flatMap { it.files }
.flatMap { it.declarations }
.filter { it.isJsExport() }
.forEach {
declarationsToExport.add(it)
addLast(it)
}
}
val declarationVisitor = object : IrElementVisitorVoid {
override fun visitFunction(declaration: IrFunction) {
visitType(declaration.returnType)
declaration.typeParameters.forEach(::visitTypeParameter)
declaration.valueParameters.forEach(::visitValueParameter)
}
override fun visitClass(declaration: IrClass) {
declaration.superTypes.forEach(::visitType)
declaration.acceptChildrenVoid(this)
}
override fun visitField(declaration: IrField) {
visitType(declaration.type)
}
override fun visitValueParameter(declaration: IrValueParameter) {
visitType(declaration.type)
}
override fun visitTypeParameter(declaration: IrTypeParameter) {
declaration.superTypes.forEach(::visitType)
}
private fun visitType(type: IrType) {
if (type !is IrSimpleType) return
val classifier = type.classifier as? IrClassSymbol ?: return
val klass = classifier.owner
if (!klass.isExternal || klass in excludedFromExport || klass in declarationsToExport) return
queue.add(klass)
declarationsToExport.add(klass)
type.arguments.forEach { it.typeOrNull?.let(::visitType) }
}
}
while (queue.isNotEmpty()) {
val declaration = queue.removeFirst()
declaration.acceptVoid(declarationVisitor)
}
return declarationsToExport
}
fun generateExport(modules: Iterable<IrModuleFragment>): ExportedModule =
ExportedModule(
context.configuration[CommonConfigurationKeys.MODULE_NAME]!!,
ModuleKind.ES,
collectAllTheDeclarationsToExport(modules).mapNotNull(::exportDeclaration)
)
private fun exportDeclaration(declaration: IrDeclaration): ExportedDeclaration? {
return when (declaration) {
is IrSimpleFunction -> exportFunction(declaration)
is IrClass -> exportClass(declaration)
else -> error("Can't export declaration $declaration")
}?.withAttributesFor(declaration)
}
private fun exportFunction(function: IrSimpleFunction): ExportedFunction? =
runIf(function.correspondingPropertySymbol == null && function.realOverrideTarget.parentClassOrNull?.symbol != context.irBuiltIns.anyClass) {
val parentClass = function.parentClassOrNull
ExportedFunction(
function.getExportedIdentifier(),
returnType = exportType(function.returnType),
typeParameters = function.typeParameters.memoryOptimizedMap(::exportTypeParameter),
ir = function,
isMember = parentClass != null,
isStatic = function.isStaticMethodOfClass,
isProtected = function.visibility == DescriptorVisibilities.PROTECTED,
isAbstract = parentClass != null && !parentClass.isInterface && function.modality == Modality.ABSTRACT,
parameters = (listOfNotNull(function.extensionReceiverParameter) + function.valueParameters)
.memoryOptimizedMap { exportParameter(it) },
)
}
private fun exportConstructor(constructor: IrConstructor): ExportedDeclaration {
assert(constructor.isPrimary) { "Can't export not-primary constructor" }
val allValueParameters = listOfNotNull(constructor.extensionReceiverParameter) + constructor.valueParameters
return ExportedConstructor(
parameters = allValueParameters.memoryOptimizedMap { exportParameter(it) },
visibility = constructor.visibility.toExportedVisibility()
)
}
private fun exportProperty(
property: IrProperty,
specializeType: ExportedType? = null
): ExportedDeclaration {
val parentClass = property.parent as? IrClass
val isOptional = parentClass != null &&
property.getter?.returnType?.isNullable() == true
return ExportedProperty(
name = property.getExportedIdentifier(),
type = specializeType ?: exportType(property.getter!!.returnType),
mutable = property.isVar,
isMember = parentClass != null,
isAbstract = parentClass?.isInterface == false && property.modality == Modality.ABSTRACT,
isProtected = property.visibility == DescriptorVisibilities.PROTECTED,
isField = parentClass?.isInterface == true,
irGetter = property.getter,
irSetter = property.setter,
isOptional = isOptional,
isStatic = (property.getter ?: property.setter)?.isStaticMethodOfClass == true,
)
}
private fun exportParameter(parameter: IrValueParameter): ExportedParameter =
ExportedParameter(
parameter.name.asString(),
exportType(parameter.type),
parameter.defaultValue != null
)
private val currentlyProcessedTypes = hashSetOf<IrType>()
private fun exportType(type: IrType): ExportedType {
if (type in currentlyProcessedTypes)
return ExportedType.Primitive.Unknown
if (type !is IrSimpleType)
return ExportedType.ErrorType("NonSimpleType ${type.render()}")
currentlyProcessedTypes.add(type)
val classifier = type.classifier
val isMarkedNullable = type.isMarkedNullable()
val nonNullType = type.makeNotNull() as IrSimpleType
val jsRelatedSymbols = context.wasmSymbols.jsRelatedSymbols
val exportedType = when {
nonNullType.isBoolean() || nonNullType == jsRelatedSymbols.jsBooleanType -> ExportedType.Primitive.Boolean
nonNullType.isLong() || nonNullType.isULong() || nonNullType == jsRelatedSymbols.jsBigIntType -> ExportedType.Primitive.BigInt
nonNullType.isPrimitiveType() || nonNullType.isUByte() || nonNullType.isUShort() || nonNullType.isUInt() || nonNullType == jsRelatedSymbols.jsNumberType ->
ExportedType.Primitive.Number
nonNullType.isString() || nonNullType == jsRelatedSymbols.jsStringType -> ExportedType.Primitive.String
nonNullType == jsRelatedSymbols.jsAnyType -> ExportedType.Primitive.Unknown
nonNullType.isUnit() || nonNullType == context.wasmSymbols.voidType -> ExportedType.Primitive.Unit
nonNullType.isFunction() -> ExportedType.Function(
parameterTypes = nonNullType.arguments.dropLast(1).memoryOptimizedMap { exportTypeArgument(it) },
returnType = exportTypeArgument(nonNullType.arguments.last())
)
classifier is IrTypeParameterSymbol -> ExportedType.TypeParameter(classifier.owner.name.identifier)
classifier is IrClassSymbol -> {
val klass = classifier.owner
if (klass.symbol == jsRelatedSymbols.jsReferenceClass) return ExportedType.Primitive.Unknown
require(klass.isExternal) { "Unexpected non-external class: ${klass.fqNameWhenAvailable}" }
val name = "$NOT_EXPORTED_NAMESPACE.${klass.getFqNameWithJsNameWhenAvailable(shouldIncludePackage = true).asString()}"
when (klass.kind) {
ClassKind.OBJECT ->
ExportedType.TypeOf(name)
ClassKind.CLASS,
ClassKind.INTERFACE ->
ExportedType.ClassType(
name,
type.arguments.memoryOptimizedMap { exportTypeArgument(it) },
klass
)
else -> error("Unexpected class kind ${klass.kind}")
}
}
else -> error("Unexpected classifier $classifier")
}
return exportedType.withNullability(isMarkedNullable)
.also { currentlyProcessedTypes.remove(type) }
}
private fun exportTypeArgument(type: IrTypeArgument): ExportedType {
if (type is IrTypeProjection)
return exportType(type.type)
if (type is IrType)
return exportType(type)
return ExportedType.ErrorType("UnknownType ${type.render()}")
}
private fun exportTypeParameter(typeParameter: IrTypeParameter): ExportedType.TypeParameter {
val constraint = typeParameter.superTypes.asSequence()
.filter { !it.isNullable() || it.makeNotNull() != context.wasmSymbols.jsRelatedSymbols.jsAnyType }
.map { exportType(it) }
.filter { it !is ExportedType.ErrorType }
.toList()
return ExportedType.TypeParameter(
typeParameter.name.identifier,
constraint.run {
when (size) {
0 -> null
1 -> single()
else -> reduce(ExportedType::IntersectionType)
}
}
)
}
private fun exportMemberDeclaration(declaration: IrDeclaration): ExportedDeclaration? {
if (declaration !is IrDeclarationWithVisibility || declaration.visibility == DescriptorVisibilities.PRIVATE) return null
return when (declaration) {
is IrSimpleFunction -> exportFunction(declaration)
is IrConstructor -> exportConstructor(declaration)
is IrProperty -> exportProperty(declaration)
else -> null
}?.withAttributesFor(declaration)
}
private fun exportClass(declaration: IrClass): ExportedDeclaration {
val typeParameters = declaration.typeParameters.memoryOptimizedMap(::exportTypeParameter)
val superClass = declaration.superTypes
.find { it != context.irBuiltIns.anyType && !it.classifierOrFail.isInterface }
?.let(::exportType)
?.takeIf { it !is ExportedType.ErrorType }
val superInterfaces = declaration.superTypes
.filter { it != context.wasmSymbols.jsRelatedSymbols.jsAnyType && it.classifierOrFail.isInterface }
.map(::exportType)
.memoryOptimizedFilter { it !is ExportedType.ErrorType }
val name = declaration.getExportedIdentifier()
val members = declaration.declarations.memoryOptimizedMapNotNull(::exportMemberDeclaration)
val exportedDeclaration = if (declaration.kind == ClassKind.OBJECT) {
ExportedObject(
ir = declaration,
name = name,
members = members,
superClasses = listOfNotNull(superClass),
nestedClasses = emptyList(),
superInterfaces = superInterfaces
)
} else {
ExportedRegularClass(
name = name,
isInterface = declaration.isInterface,
isAbstract = declaration.modality == Modality.ABSTRACT || declaration.modality == Modality.SEALED,
superClasses = listOfNotNull(superClass),
superInterfaces = superInterfaces,
typeParameters = typeParameters,
members = members,
nestedClasses = emptyList(),
ir = declaration
)
}
return ExportedNamespace(
name = "$NOT_EXPORTED_NAMESPACE${declaration.packageFqName?.asString()?.takeIf { it.isNotEmpty() }?.let { ".$it" }.orEmpty()}",
declarations = listOf(exportedDeclaration),
isPrivate = true
)
}
private val IrClassifierSymbol.isInterface
get() = (owner as? IrClass)?.isInterface == true
}
@@ -37,6 +37,8 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.util.OperatorNameConventions
val KOTLIN_TO_JS_CLOSURE_ORIGIN by IrDeclarationOriginImpl
/**
* Create wrappers for external and @JsExport functions when type adaptation is needed
*/
@@ -466,6 +468,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
val result = context.irFactory.buildFun {
name = Name.identifier("__callFunction_${info.signatureString}")
returnType = info.adaptedResultType
origin = KOTLIN_TO_JS_CLOSURE_ORIGIN
}
result.parent = currentParent
result.addValueParameter {
@@ -9,8 +9,10 @@ import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmSignature
import org.jetbrains.kotlin.backend.wasm.ir2wasm.wasmSignature
import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext
import org.jetbrains.kotlin.ir.backend.js.lower.BridgesConstruction
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
class WasmBridgesConstruction(context: JsCommonBackendContext) : BridgesConstruction<JsCommonBackendContext>(context) {
override fun getFunctionSignature(function: IrSimpleFunction): WasmSignature =
@@ -20,4 +22,9 @@ class WasmBridgesConstruction(context: JsCommonBackendContext) : BridgesConstruc
override val shouldCastDispatchReceiver: Boolean = true
override fun getBridgeOrigin(bridge: IrSimpleFunction): IrDeclarationOrigin =
IrDeclarationOrigin.BRIDGE
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
if (declaration.isEffectivelyExternal()) return null
return super.transformFlat(declaration)
}
}