[WasmJs] Add support for external class reflection

Fix #KT-64890
This commit is contained in:
Igor Yakovlev
2024-01-26 21:16:29 +01:00
parent e302420197
commit 6930fc8fed
24 changed files with 330 additions and 81 deletions
@@ -305,7 +305,6 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
override val getKClassFromExpression = getInternalWithoutPackage("getKClassFromExpression") override val getKClassFromExpression = getInternalWithoutPackage("getKClassFromExpression")
override val primitiveClassesObject = context.getIrClass(FqName("kotlin.reflect.js.internal.PrimitiveClasses")) override val primitiveClassesObject = context.getIrClass(FqName("kotlin.reflect.js.internal.PrimitiveClasses"))
override val kTypeClass: IrClassSymbol = context.getIrClass(FqName("kotlin.reflect.KType")) override val kTypeClass: IrClassSymbol = context.getIrClass(FqName("kotlin.reflect.KType"))
override val getClassData: IrSimpleFunctionSymbol get() = jsClass
} }
internal val reflectionSymbols: JsReflectionSymbols = JsReflectionSymbols() internal val reflectionSymbols: JsReflectionSymbols = JsReflectionSymbols()
@@ -670,8 +670,8 @@ private val jsClassUsageInReflectionPhase = makeIrModulePhase(
) )
private val classReferenceLoweringPhase = makeIrModulePhase( private val classReferenceLoweringPhase = makeIrModulePhase(
::ClassReferenceLowering, ::JsClassReferenceLowering,
name = "ClassReferenceLowering", name = "JsClassReferenceLowering",
description = "Handle class references", description = "Handle class references",
prerequisite = setOf(jsClassUsageInReflectionPhase) prerequisite = setOf(jsClassUsageInReflectionPhase)
) )
@@ -56,4 +56,7 @@ class JsMapping : DefaultMapping() {
val wasmExternalClassToInstanceCheck = val wasmExternalClassToInstanceCheck =
DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrSimpleFunction>() DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrSimpleFunction>()
val wasmGetJsClass =
DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrSimpleFunction>()
} }
@@ -7,12 +7,10 @@ package org.jetbrains.kotlin.ir.backend.js
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.IrType
interface ReflectionSymbols { interface ReflectionSymbols {
val getKClassFromExpression: IrSimpleFunctionSymbol val getKClassFromExpression: IrSimpleFunctionSymbol
val getKClass: IrSimpleFunctionSymbol val getKClass: IrSimpleFunctionSymbol
val getClassData: IrSimpleFunctionSymbol
val createKType: IrSimpleFunctionSymbol? val createKType: IrSimpleFunctionSymbol?
val createDynamicKType: IrSimpleFunctionSymbol? val createDynamicKType: IrSimpleFunctionSymbol?
val createKTypeParameter: IrSimpleFunctionSymbol? val createKTypeParameter: IrSimpleFunctionSymbol?
@@ -25,9 +25,36 @@ import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.*
class ClassReferenceLowering(val context: JsCommonBackendContext) : BodyLoweringPass { class JsClassReferenceLowering(context: JsIrBackendContext) : ClassReferenceLowering(context) {
private val getClassData = context.intrinsics.jsClass
private val reflectionSymbols get() = context.reflectionSymbols override fun callGetKClass(
returnType: IrType,
typeArgument: IrType
): IrCall {
val primitiveKClass =
getFinalPrimitiveKClass(returnType, typeArgument) ?: getOpenPrimitiveKClass(returnType, typeArgument)
if (primitiveKClass != null)
return primitiveKClass
return JsIrBuilder.buildCall(reflectionSymbols.getKClass, returnType, listOf(typeArgument))
.apply {
putValueArgument(0, callGetClassByType(typeArgument))
}
}
private fun callGetClassByType(type: IrType) =
JsIrBuilder.buildCall(
getClassData,
typeArguments = listOf(type),
origin = JsStatementOrigins.CLASS_REFERENCE
)
}
abstract class ClassReferenceLowering(val context: JsCommonBackendContext) : BodyLoweringPass {
protected val reflectionSymbols get() = context.reflectionSymbols
private val primitiveClassProperties by lazy(LazyThreadSafetyMode.NONE) { private val primitiveClassProperties by lazy(LazyThreadSafetyMode.NONE) {
reflectionSymbols.primitiveClassesObject.owner.declarations.filterIsInstance<IrProperty>() reflectionSymbols.primitiveClassesObject.owner.declarations.filterIsInstance<IrProperty>()
@@ -96,7 +123,7 @@ class ClassReferenceLowering(val context: JsCommonBackendContext) : BodyLowering
) )
} }
private fun getFinalPrimitiveKClass(returnType: IrType, typeArgument: IrType): IrCall? { protected fun getFinalPrimitiveKClass(returnType: IrType, typeArgument: IrType): IrCall? {
for ((typePredicate, v) in finalPrimitiveClasses) { for ((typePredicate, v) in finalPrimitiveClasses) {
if (typePredicate(typeArgument)) if (typePredicate(typeArgument))
return getPrimitiveClass(v, returnType) return getPrimitiveClass(v, returnType)
@@ -106,7 +133,7 @@ class ClassReferenceLowering(val context: JsCommonBackendContext) : BodyLowering
} }
private fun getOpenPrimitiveKClass(returnType: IrType, typeArgument: IrType): IrCall? { protected fun getOpenPrimitiveKClass(returnType: IrType, typeArgument: IrType): IrCall? {
for ((typePredicate, v) in openPrimitiveClasses) { for ((typePredicate, v) in openPrimitiveClasses) {
if (typePredicate(typeArgument)) if (typePredicate(typeArgument))
return getPrimitiveClass(v, returnType) return getPrimitiveClass(v, returnType)
@@ -123,28 +150,10 @@ class ClassReferenceLowering(val context: JsCommonBackendContext) : BodyLowering
return null return null
} }
private fun callGetKClass( abstract fun callGetKClass(
returnType: IrType = reflectionSymbols.getKClass.owner.returnType, returnType: IrType = reflectionSymbols.getKClass.owner.returnType,
typeArgument: IrType typeArgument: IrType
): IrCall { ): IrCall
val primitiveKClass =
getFinalPrimitiveKClass(returnType, typeArgument) ?: getOpenPrimitiveKClass(returnType, typeArgument)
if (primitiveKClass != null)
return primitiveKClass
return JsIrBuilder.buildCall(reflectionSymbols.getKClass, returnType, listOf(typeArgument))
.apply {
putValueArgument(0, callGetClassByType(typeArgument))
}
}
private fun callGetClassByType(type: IrType) =
JsIrBuilder.buildCall(
reflectionSymbols.getClassData,
typeArguments = listOf(type),
origin = JsStatementOrigins.CLASS_REFERENCE
)
private fun buildCall(name: IrSimpleFunctionSymbol, vararg args: IrExpression): IrExpression = private fun buildCall(name: IrSimpleFunctionSymbol, vararg args: IrExpression): IrExpression =
JsIrBuilder.buildCall(name).apply { JsIrBuilder.buildCall(name).apply {
@@ -154,7 +163,6 @@ class ClassReferenceLowering(val context: JsCommonBackendContext) : BodyLowering
} }
private fun createKType(type: IrType, visitedTypeParams: MutableSet<IrTypeParameter>): IrExpression { private fun createKType(type: IrType, visitedTypeParams: MutableSet<IrTypeParameter>): IrExpression {
if (type is IrSimpleType) if (type is IrSimpleType)
return createSimpleKType(type, visitedTypeParams) return createSimpleKType(type, visitedTypeParams)
if (type is IrDynamicType) if (type is IrDynamicType)
@@ -470,8 +470,8 @@ private val staticMembersLoweringPhase = makeIrModulePhase(
) )
private val classReferenceLoweringPhase = makeIrModulePhase( private val classReferenceLoweringPhase = makeIrModulePhase(
::ClassReferenceLowering, ::WasmClassReferenceLowering,
name = "ClassReferenceLowering", name = "WasmClassReferenceLowering",
description = "Handle class references" description = "Handle class references"
) )
@@ -52,10 +52,9 @@ class WasmSymbols(
internal inner class WasmReflectionSymbols : ReflectionSymbols { internal inner class WasmReflectionSymbols : ReflectionSymbols {
override val createKType: IrSimpleFunctionSymbol = getInternalFunction("createKType") override val createKType: IrSimpleFunctionSymbol = getInternalFunction("createKType")
override val getClassData: IrSimpleFunctionSymbol = getInternalFunction("wasmGetTypeInfoData")
override val getKClass: IrSimpleFunctionSymbol = getInternalFunction("getKClass") override val getKClass: IrSimpleFunctionSymbol = getInternalFunction("getKClass")
override val getKClassFromExpression: IrSimpleFunctionSymbol = getInternalFunction("getKClassFromExpression") override val getKClassFromExpression: IrSimpleFunctionSymbol = getInternalFunction("getKClassFromExpression")
override val createDynamicKType: IrSimpleFunctionSymbol get() = error("Dynamic type is not supported by WASM") override val createDynamicKType: IrSimpleFunctionSymbol get() = error("Dynamic type is not supported by Wasm")
override val createKTypeParameter: IrSimpleFunctionSymbol = getInternalFunction("createKTypeParameter") override val createKTypeParameter: IrSimpleFunctionSymbol = getInternalFunction("createKTypeParameter")
override val getStarKTypeProjection = getInternalFunction("getStarKTypeProjection") override val getStarKTypeProjection = getInternalFunction("getStarKTypeProjection")
override val createCovariantKTypeProjection = getInternalFunction("createCovariantKTypeProjection") override val createCovariantKTypeProjection = getInternalFunction("createCovariantKTypeProjection")
@@ -67,6 +66,7 @@ class WasmSymbols(
val getTypeInfoTypeDataByPtr: IrSimpleFunctionSymbol = getInternalFunction("getTypeInfoTypeDataByPtr") val getTypeInfoTypeDataByPtr: IrSimpleFunctionSymbol = getInternalFunction("getTypeInfoTypeDataByPtr")
val wasmTypeInfoData: IrClassSymbol = getInternalClass("TypeInfoData") val wasmTypeInfoData: IrClassSymbol = getInternalClass("TypeInfoData")
val kClassImpl: IrClassSymbol = getInternalClass("KClassImpl")
} }
internal val reflectionSymbols: WasmReflectionSymbols = WasmReflectionSymbols() internal val reflectionSymbols: WasmReflectionSymbols = WasmReflectionSymbols()
@@ -408,6 +408,8 @@ class WasmSymbols(
internal val throwAsJsException: IrSimpleFunctionSymbol = internal val throwAsJsException: IrSimpleFunctionSymbol =
getInternalFunction("throwAsJsException") getInternalFunction("throwAsJsException")
val kExternalClassImpl: IrClassSymbol = getInternalClass("KExternalClassImpl")
} }
private val wasmExportClass = getIrClass(FqName("kotlin.wasm.WasmExport")) private val wasmExportClass = getIrClass(FqName("kotlin.wasm.WasmExport"))
@@ -63,6 +63,7 @@ class AssociatedObjectsLowering(val context: WasmBackendContext) : FileLoweringP
for (klassAnnotation in declaration.annotations) { for (klassAnnotation in declaration.annotations) {
val annotationClass = klassAnnotation.symbol.owner.parentClassOrNull ?: continue val annotationClass = klassAnnotation.symbol.owner.parentClassOrNull ?: continue
if (klassAnnotation.valueArgumentsCount != 1) continue if (klassAnnotation.valueArgumentsCount != 1) continue
if (declaration.isEffectivelyExternal()) continue
val associatedObject = klassAnnotation.associatedObject() ?: continue val associatedObject = klassAnnotation.associatedObject() ?: continue
val builder = cachedBuilder ?: context.createIrBuilder(context.wasmSymbols.tryGetAssociatedObject) val builder = cachedBuilder ?: context.createIrBuilder(context.wasmSymbols.tryGetAssociatedObject)
@@ -102,7 +103,7 @@ private fun IrBuilderWithScope.createAssociatedObjectSelector(
condition = irEquals(classIdParam, classId), condition = irEquals(classIdParam, classId),
thenPart = irIfThen( thenPart = irIfThen(
condition = irEquals(keyIdParam, keyId), condition = irEquals(keyIdParam, keyId),
thenPart = irReturn(irGetObjectValue(irBuiltIns.anyType, associatedObject)) thenPart = irReturn(irGetObjectValue(associatedObject.defaultType, associatedObject))
) )
) )
} }
@@ -12,21 +12,24 @@ import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
import org.jetbrains.kotlin.config.AnalysisFlags import org.jetbrains.kotlin.config.AnalysisFlags
import org.jetbrains.kotlin.config.languageVersionSettings import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.lower.calls.EnumIntrinsicsUtils import org.jetbrains.kotlin.ir.backend.js.lower.calls.EnumIntrinsicsUtils
import org.jetbrains.kotlin.ir.backend.js.utils.erasedUpperBound import org.jetbrains.kotlin.ir.backend.js.utils.erasedUpperBound
import org.jetbrains.kotlin.ir.backend.js.utils.isEqualsInheritedFromAny import org.jetbrains.kotlin.ir.backend.js.utils.isEqualsInheritedFromAny
import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.IrCall import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
import org.jetbrains.kotlin.ir.expressions.putClassTypeArgument
import org.jetbrains.kotlin.ir.util.toIrConst import org.jetbrains.kotlin.ir.util.toIrConst
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.WasmTarget
import org.jetbrains.kotlin.name.parentOrNull import org.jetbrains.kotlin.name.parentOrNull
class BuiltInsLowering(val context: WasmBackendContext) : FileLoweringPass { class BuiltInsLowering(val context: WasmBackendContext) : FileLoweringPass {
@@ -160,35 +163,35 @@ class BuiltInsLowering(val context: WasmBackendContext) : FileLoweringPass {
val newSymbol = irBuiltins.suspendFunctionN(arity).getSimpleFunction("invoke")!! val newSymbol = irBuiltins.suspendFunctionN(arity).getSimpleFunction("invoke")!!
return irCall(call, newSymbol, argumentsAsReceivers = true) return irCall(call, newSymbol, argumentsAsReceivers = true)
} }
symbols.reflectionSymbols.getClassData -> { context.reflectionSymbols.getKClass -> {
val type = call.getTypeArgument(0)!! val type = call.getTypeArgument(0)!!
val klass = type.classOrNull?.owner ?: error("Invalid type") val klass = type.classOrNull?.owner ?: error("Invalid type")
val typeId = builder.irCall(symbols.wasmTypeId).also { val constructorArgument: IrExpression
it.putTypeArgument(0, type) val kclassConstructor: IrConstructor
if (klass.isEffectivelyExternal()) {
check(context.isWasmJsTarget) { "External classes reflection in WASI mode are not supported" }
kclassConstructor = symbols.jsRelatedSymbols.kExternalClassImpl.owner.constructors.first()
constructorArgument = getExternalKClassCtorArgument(type, builder)
} else {
kclassConstructor = symbols.reflectionSymbols.kClassImpl.owner.constructors.first()
constructorArgument = getKClassCtorArgument(type, builder)
} }
if (!klass.isInterface) { return IrConstructorCallImpl(
return builder.irCall(context.wasmSymbols.reflectionSymbols.getTypeInfoTypeDataByPtr).also { startOffset = UNDEFINED_OFFSET,
it.putValueArgument(0, typeId) endOffset = UNDEFINED_OFFSET,
} type = kclassConstructor.returnType,
} else { symbol = kclassConstructor.symbol,
val infoDataCtor = symbols.reflectionSymbols.wasmTypeInfoData.constructors.first() typeArgumentsCount = 1,
val fqName = type.classFqName!! valueArgumentsCount = 1,
val fqnShouldBeEmitted = constructorTypeArgumentsCount = 0
context.configuration.languageVersionSettings.getFlag(AnalysisFlags.allowFullyQualifiedNameInKClass) ).also {
val packageName = if (fqnShouldBeEmitted) fqName.parentOrNull()?.asString() ?: "" else "" it.putClassTypeArgument(0, type)
val typeName = fqName.shortName().asString() it.putValueArgument(0, constructorArgument)
return with(builder) {
irCallConstructor(infoDataCtor, emptyList()).also {
it.putValueArgument(0, typeId)
it.putValueArgument(1, packageName.toIrConst(context.irBuiltIns.stringType))
it.putValueArgument(2, typeName.toIrConst(context.irBuiltIns.stringType))
}
}
} }
} }
symbols.enumValueOfIntrinsic -> symbols.enumValueOfIntrinsic ->
return EnumIntrinsicsUtils.transformEnumValueOfIntrinsic(call) return EnumIntrinsicsUtils.transformEnumValueOfIntrinsic(call)
symbols.enumValuesIntrinsic -> symbols.enumValuesIntrinsic ->
@@ -200,6 +203,40 @@ class BuiltInsLowering(val context: WasmBackendContext) : FileLoweringPass {
return call return call
} }
private fun getKClassCtorArgument(type: IrType, builder: DeclarationIrBuilder): IrExpression {
val klass = type.classOrNull?.owner ?: error("Invalid type")
val typeId = builder.irCall(symbols.wasmTypeId).also {
it.putTypeArgument(0, type)
}
if (!klass.isInterface) {
return builder.irCall(context.wasmSymbols.reflectionSymbols.getTypeInfoTypeDataByPtr).also {
it.putValueArgument(0, typeId)
}
} else {
val fqName = type.classFqName!!
val fqnShouldBeEmitted =
context.configuration.languageVersionSettings.getFlag(AnalysisFlags.allowFullyQualifiedNameInKClass)
val packageName = if (fqnShouldBeEmitted) fqName.parentOrNull()?.asString() ?: "" else ""
val typeName = fqName.shortName().asString()
return builder.irCallConstructor(symbols.reflectionSymbols.wasmTypeInfoData.constructors.first(), emptyList()).also {
it.putValueArgument(0, typeId)
it.putValueArgument(1, packageName.toIrConst(context.irBuiltIns.stringType))
it.putValueArgument(2, typeName.toIrConst(context.irBuiltIns.stringType))
}
}
}
private fun getExternalKClassCtorArgument(type: IrType, builder: DeclarationIrBuilder): IrExpression {
val klass = type.classOrNull?.owner ?: error("Invalid type")
check(klass.kind != ClassKind.INTERFACE) { "External interface must not be a class literal" }
val classGetClassFunction = context.mapping.wasmGetJsClass[klass]!!
val wrappedGetClassIfAny = context.mapping.wasmJsInteropFunctionToWrapper[classGetClassFunction] ?: classGetClassFunction
return builder.irCall(wrappedGetClassIfAny)
}
override fun lower(irFile: IrFile) { override fun lower(irFile: IrFile) {
val builder = context.createIrBuilder(irFile.symbol) val builder = context.createIrBuilder(irFile.symbol)
irFile.transformChildrenVoid(object : IrElementTransformerVoidWithContext() { irFile.transformChildrenVoid(object : IrElementTransformerVoidWithContext() {
@@ -15,10 +15,7 @@ import org.jetbrains.kotlin.backend.wasm.utils.getWasmImportDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.utils.getJsModule import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
import org.jetbrains.kotlin.ir.backend.js.utils.getJsQualifier
import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.builders.declarations.buildFun import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.builders.irCallConstructor import org.jetbrains.kotlin.ir.builders.irCallConstructor
@@ -28,6 +25,7 @@ import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.makeNullable
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.* import org.jetbrains.kotlin.ir.visitors.*
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
@@ -96,8 +94,10 @@ class ComplexExternalDeclarationsToTopLevelFunctionsLowering(val context: WasmBa
if (klass.kind == ClassKind.OBJECT) if (klass.kind == ClassKind.OBJECT)
generateExternalObjectInstanceGetter(klass) generateExternalObjectInstanceGetter(klass)
if (klass.kind != ClassKind.INTERFACE) if (klass.kind != ClassKind.INTERFACE) {
generateInstanceCheckForExternalClass(klass) generateInstanceCheckForExternalClass(klass)
generateGetClassForExternalClass(klass)
}
} }
fun processExternalProperty(property: IrProperty) { fun processExternalProperty(property: IrProperty) {
@@ -374,6 +374,18 @@ class ComplexExternalDeclarationsToTopLevelFunctionsLowering(val context: WasmBa
} }
} }
fun generateGetClassForExternalClass(klass: IrClass) {
context.mapping.wasmGetJsClass[klass] = createExternalJsFunction(
klass.name,
"_\$external_class_get",
resultType = context.wasmSymbols.jsRelatedSymbols.jsAnyType.makeNullable(),
jsCode = buildString {
append("() => ")
appendExternalClassReference(klass)
}
)
}
private fun createExternalJsFunction( private fun createExternalJsFunction(
originalName: Name, originalName: Name,
suffix: String, suffix: String,
@@ -0,0 +1,29 @@
/*
* 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.lower
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.lower.ClassReferenceLowering
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.types.IrType
class WasmClassReferenceLowering(context: WasmBackendContext) : ClassReferenceLowering(context) {
override fun callGetKClass(
returnType: IrType,
typeArgument: IrType
): IrCall {
val primitiveKClass =
getFinalPrimitiveKClass(returnType, typeArgument) ?: getOpenPrimitiveKClass(returnType, typeArgument)
if (primitiveKClass != null)
return primitiveKClass
return JsIrBuilder.buildCall(reflectionSymbols.getKClass, returnType).also {
it.putTypeArgument(0, typeArgument)
}
}
}
@@ -0,0 +1,25 @@
// TARGET_BACKEND: WASM
// FILE: findAssociatedExternalObject.js
const JS_OBJECT = {}
// FILE: findAssociatedExternalObject.kt
import kotlin.reflect.*
@OptIn(ExperimentalAssociatedObjects::class)
@AssociatedObjectKey
@Retention(AnnotationRetention.BINARY)
annotation class Associated(val kClass: KClass<*>)
external object JS_OBJECT
@Associated(JS_OBJECT::class)
class ObjectKey
@OptIn(ExperimentalAssociatedObjects::class)
fun box(): String {
if (ObjectKey::class.findAssociatedObject<Associated>() !== JS_OBJECT) return "FAIL2"
return "OK"
}
@@ -0,0 +1,51 @@
// TARGET_BACKEND: WASM
// FILE: reflectionOnExternals.js
class Outer { }
Outer.Inner = Promise;
// FILE: reflectionOnExternals.kt
import kotlin.js.Promise
import kotlin.reflect.KClass
import kotlin.reflect.typeOf
@JsName("Promise")
external class PromiseAlias : JsAny
external class Outer : JsAny {
class Inner : JsAny
@JsName("Inner")
class InnerAlias : JsAny
}
fun checkPromise(kClass: KClass<*>, promiseObject: Any, nonPromiseObject: Any): String? {
if (kClass.simpleName != "Promise") return "FAIL1"
if (kClass != Promise::class) return "FAIL2"
if (Promise::class != kClass) return "FAIL3"
if (!kClass.isInstance(promiseObject)) return "FAIL4"
if (kClass.isInstance(nonPromiseObject)) return "FAIL5"
return null
}
fun box(): String {
val promiseObj: Any = Promise<JsAny> { resolve, reject -> Unit }
val someObject: Any = Any()
checkPromise(Promise::class, promiseObj, someObject)?.let { return "1_" + it }
checkPromise(PromiseAlias::class, promiseObj, someObject)?.let { return "2_" + it }
checkPromise(promiseObj::class, promiseObj, someObject)?.let { return "3_" + it }
checkPromise(Outer.Inner::class, promiseObj, someObject)?.let { return "4_" + it }
checkPromise(Outer.InnerAlias::class, promiseObj, someObject)?.let { return "5_" + it }
val typeOfPromiseClassifier = typeOf<Promise<*>>().classifier
if (typeOfPromiseClassifier !is KClass<*>) return "FAIL6"
checkPromise(typeOfPromiseClassifier, promiseObj, someObject)?.let { return "7_" + it }
val typeOfPromiseWithArguments = typeOf<Promise<Promise<*>>>()
if (typeOfPromiseWithArguments.arguments.size != 1) return "FAIL8"
val typeOfPromiseArgumentClassifier = typeOfPromiseWithArguments.arguments[0].type?.classifier
if (typeOfPromiseArgumentClassifier !is KClass<*>) return "FAIL9"
checkPromise(typeOfPromiseArgumentClassifier, promiseObj, someObject)?.let { return "10_" + it }
return "OK"
}
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: WASM
// EXPECTED_REACHABLE_NODES: 1414 // EXPECTED_REACHABLE_NODES: 1414
// FILE: main.kt // FILE: main.kt
@@ -78,6 +78,7 @@ internal fun <T> wasmIsInterface(obj: Any): Boolean =
internal fun <T> wasmTypeId(): Int = internal fun <T> wasmTypeId(): Int =
implementedAsIntrinsic implementedAsIntrinsic
//TODO("Remove after bootstrap KT-65322")
@ExcludedFromCodegen @ExcludedFromCodegen
internal fun <T> wasmGetTypeInfoData(): TypeInfoData = internal fun <T> wasmGetTypeInfoData(): TypeInfoData =
implementedAsIntrinsic implementedAsIntrinsic
@@ -0,0 +1,31 @@
/*
* 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 kotlin.wasm.internal
import kotlin.reflect.*
@Suppress("UNUSED_PARAMETER")
private fun getJsClassName(jsKlass: JsAny): String? =
js("jsKlass.name")
@Suppress("UNUSED_PARAMETER")
private fun instanceOf(ref: JsAny, jsKlass: JsAny): Boolean =
js("ref instanceof jsKlass")
internal class KExternalClassImpl<T : Any> @WasmPrimitiveConstructor constructor(private val jsConstructor: JsAny) : KClass<T> {
override val simpleName: String? get() = getJsClassName(jsConstructor)
override val qualifiedName: String? get() = null
override fun isInstance(value: Any?): Boolean =
value is JsExternalBox && instanceOf(value.ref, jsConstructor)
override fun equals(other: Any?): Boolean =
other is KExternalClassImpl<*> && jsConstructor == other.jsConstructor
override fun hashCode(): Int = simpleName.hashCode()
override fun toString(): String = "class $simpleName"
}
@@ -0,0 +1,19 @@
/*
* 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 kotlin.wasm.internal
import kotlin.reflect.*
@Suppress("UNUSED_PARAMETER")
private fun getConstructor(obj: JsAny): JsAny? =
js("obj.constructor")
@Suppress("UNCHECKED_CAST")
internal actual fun <T : Any> getKClassForObject(obj: Any): KClass<T> {
if (obj !is JsExternalBox) return KClassImpl(getTypeInfoTypeDataByPtr(obj.typeInfo))
val jsConstructor = getConstructor(obj.ref) ?: error("JavaScript constructor is not defined")
return KExternalClassImpl(jsConstructor)
}
@@ -2,13 +2,9 @@
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. * Copyright 2010-2021 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. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/ */
package kotlin.reflect.wasm.internal package kotlin.wasm.internal
import kotlin.reflect.* import kotlin.reflect.KClass
import kotlin.wasm.internal.TypeInfoData
import kotlin.wasm.internal.getSuperTypeId
import kotlin.wasm.internal.isInterfaceById
import kotlin.wasm.internal.isInterfaceType
internal object NothingKClassImpl : KClass<Nothing> { internal object NothingKClassImpl : KClass<Nothing> {
override val simpleName: String = "Nothing" override val simpleName: String = "Nothing"
@@ -30,10 +26,10 @@ internal object ErrorKClass : KClass<Nothing> {
override fun hashCode(): Int = super.hashCode() // KT-24971 override fun hashCode(): Int = super.hashCode() // KT-24971
} }
internal class KClassImpl<T : Any>(internal val typeData: TypeInfoData) : KClass<T> { internal class KClassImpl<T : Any> @WasmPrimitiveConstructor constructor(internal val typeData: TypeInfoData) : KClass<T> {
override val simpleName: String get() = typeData.typeName override val simpleName: String get() = typeData.typeName
override val qualifiedName: String = override val qualifiedName: String
if (typeData.packageName.isEmpty()) typeData.typeName else "${typeData.packageName}.${typeData.typeName}" get() = if (typeData.packageName.isEmpty()) typeData.typeName else "${typeData.packageName}.${typeData.typeName}"
private fun checkSuperTypeInstance(obj: Any): Boolean { private fun checkSuperTypeInstance(obj: Any): Boolean {
var typeId = obj.typeInfo var typeId = obj.typeInfo
@@ -5,7 +5,6 @@
package kotlin.wasm.internal package kotlin.wasm.internal
import kotlin.reflect.wasm.internal.KClassImpl
import kotlin.reflect.KClass import kotlin.reflect.KClass
@PublishedApi @PublishedApi
@@ -7,7 +7,6 @@ package kotlin.wasm.internal
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KFunction import kotlin.reflect.KFunction
import kotlin.reflect.wasm.internal.*
internal object PrimitiveClasses { internal object PrimitiveClasses {
@@ -3,12 +3,16 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/ */
// a package is omitted to get declarations directly under the module
package kotlin.wasm.internal package kotlin.wasm.internal
import kotlin.reflect.* import kotlin.reflect.*
import kotlin.reflect.wasm.internal.*
internal expect fun <T : Any> getKClassForObject(obj: Any): KClass<T>
//TODO(Replace getKClass to intrinsic argument-less implementation after bootstrap KT-65322")
//@ExcludedFromCodegen
//internal fun <T : Any> getKClass(): KClass<T> =
// implementedAsIntrinsic
internal fun <T : Any> getKClass(typeInfoData: TypeInfoData): KClass<T> = internal fun <T : Any> getKClass(typeInfoData: TypeInfoData): KClass<T> =
KClassImpl(typeInfoData) KClassImpl(typeInfoData)
@@ -34,12 +38,12 @@ internal fun <T : Any> getKClassFromExpression(e: T): KClass<T> =
is DoubleArray -> PrimitiveClasses.doubleArrayClass is DoubleArray -> PrimitiveClasses.doubleArrayClass
is KClass<*> -> KClass::class is KClass<*> -> KClass::class
is Array<*> -> PrimitiveClasses.arrayClass is Array<*> -> PrimitiveClasses.arrayClass
else -> getKClass(getTypeInfoTypeDataByPtr(e.typeInfo)) else -> getKClassForObject(e)
} as KClass<T> } as KClass<T>
@Suppress("REIFIED_TYPE_PARAMETER_NO_INLINE") @Suppress("REIFIED_TYPE_PARAMETER_NO_INLINE")
internal inline fun <reified T : Any> wasmGetKClass(): KClass<T> = internal inline fun <reified T : Any> wasmGetKClass(): KClass<T> =
KClassImpl(wasmGetTypeInfoData<T>()) KClassImpl(getTypeInfoTypeDataByPtr(wasmTypeId<T>()))
internal fun createKType(classifier: KClassifier, arguments: Array<KTypeProjection>, isMarkedNullable: Boolean): KType = internal fun createKType(classifier: KClassifier, arguments: Array<KTypeProjection>, isMarkedNullable: Boolean): KType =
KTypeImpl(classifier, arguments.asList(), isMarkedNullable) KTypeImpl(classifier, arguments.asList(), isMarkedNullable)
@@ -0,0 +1,12 @@
/*
* 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.
*/
// a package is omitted to get declarations directly under the module
package kotlin.wasm.internal
import kotlin.reflect.*
internal actual fun <T : Any> getKClassForObject(obj: Any): KClass<T> =
KClassImpl(getTypeInfoTypeDataByPtr(obj.typeInfo))
@@ -24,6 +24,12 @@ public class FirWasmJsCodegenInteropTestGenerated extends AbstractFirWasmJsCodeg
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/boxWasmJsInterop"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.WASM, true); KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/boxWasmJsInterop"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.WASM, true);
} }
@Test
@TestMetadata("associatedExternalObject.kt")
public void testAssociatedExternalObject() {
runTest("compiler/testData/codegen/boxWasmJsInterop/associatedExternalObject.kt");
}
@Test @Test
@TestMetadata("callingWasmDirectly.kt") @TestMetadata("callingWasmDirectly.kt")
public void testCallingWasmDirectly() { public void testCallingWasmDirectly() {
@@ -162,6 +168,12 @@ public class FirWasmJsCodegenInteropTestGenerated extends AbstractFirWasmJsCodeg
runTest("compiler/testData/codegen/boxWasmJsInterop/nullableExternRefs.kt"); runTest("compiler/testData/codegen/boxWasmJsInterop/nullableExternRefs.kt");
} }
@Test
@TestMetadata("reflectionOnExternals.kt")
public void testReflectionOnExternals() {
runTest("compiler/testData/codegen/boxWasmJsInterop/reflectionOnExternals.kt");
}
@Test @Test
@TestMetadata("types.kt") @TestMetadata("types.kt")
public void testTypes() { public void testTypes() {
@@ -24,6 +24,12 @@ public class K1WasmCodegenWasmJsInteropTestGenerated extends AbstractK1WasmCodeg
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/boxWasmJsInterop"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.WASM, true); KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/boxWasmJsInterop"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.WASM, true);
} }
@Test
@TestMetadata("associatedExternalObject.kt")
public void testAssociatedExternalObject() {
runTest("compiler/testData/codegen/boxWasmJsInterop/associatedExternalObject.kt");
}
@Test @Test
@TestMetadata("callingWasmDirectly.kt") @TestMetadata("callingWasmDirectly.kt")
public void testCallingWasmDirectly() { public void testCallingWasmDirectly() {
@@ -162,6 +168,12 @@ public class K1WasmCodegenWasmJsInteropTestGenerated extends AbstractK1WasmCodeg
runTest("compiler/testData/codegen/boxWasmJsInterop/nullableExternRefs.kt"); runTest("compiler/testData/codegen/boxWasmJsInterop/nullableExternRefs.kt");
} }
@Test
@TestMetadata("reflectionOnExternals.kt")
public void testReflectionOnExternals() {
runTest("compiler/testData/codegen/boxWasmJsInterop/reflectionOnExternals.kt");
}
@Test @Test
@TestMetadata("types.kt") @TestMetadata("types.kt")
public void testTypes() { public void testTypes() {