diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt index dd1efe4bef04e..bcbf347c51d02 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt @@ -281,7 +281,6 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC override val getKClassFromExpression = getInternalWithoutPackage("getKClassFromExpression") override val primitiveClassesObject = context.getIrClass(FqName("kotlin.reflect.js.internal.PrimitiveClasses")) override val kTypeClass: IrClassSymbol = context.getIrClass(FqName("kotlin.reflect.KType")) - override val getClassData: IrSimpleFunctionSymbol get() = jsClass } internal val reflectionSymbols: JsReflectionSymbols = JsReflectionSymbols() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt index 316bf563674a9..699adf2147264 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt @@ -758,8 +758,8 @@ private val jsClassUsageInReflectionPhase = makeBodyLoweringPhase( ) private val classReferenceLoweringPhase = makeBodyLoweringPhase( - ::ClassReferenceLowering, - name = "ClassReferenceLowering", + ::JsClassReferenceLowering, + name = "JsClassReferenceLowering", description = "Handle class references", prerequisite = setOf(jsClassUsageInReflectionPhase) ) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt index d959d7cd8e791..4a83aed2b4250 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt @@ -57,4 +57,7 @@ class JsMapping : DefaultMapping() { val wasmExternalClassToInstanceCheck = DefaultDelegateFactory.newDeclarationToDeclarationMapping() + + val wasmGetJsClass = + DefaultDelegateFactory.newDeclarationToDeclarationMapping() } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ReflectionSymbols.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ReflectionSymbols.kt index 93ca7cfd44287..baa74215ae08e 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ReflectionSymbols.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ReflectionSymbols.kt @@ -7,12 +7,10 @@ package org.jetbrains.kotlin.ir.backend.js import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol -import org.jetbrains.kotlin.ir.types.IrType interface ReflectionSymbols { val getKClassFromExpression: IrSimpleFunctionSymbol val getKClass: IrSimpleFunctionSymbol - val getClassData: IrSimpleFunctionSymbol val createKType: IrSimpleFunctionSymbol? val createDynamicKType: IrSimpleFunctionSymbol? val createKTypeParameter: IrSimpleFunctionSymbol? diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ClassReferenceLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ClassReferenceLowering.kt index 8f63d9558dc6a..3c4560e770b41 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ClassReferenceLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ClassReferenceLowering.kt @@ -25,9 +25,36 @@ import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.name.Name 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) { reflectionSymbols.primitiveClassesObject.owner.declarations.filterIsInstance() @@ -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) { if (typePredicate(typeArgument)) 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) { if (typePredicate(typeArgument)) return getPrimitiveClass(v, returnType) @@ -123,28 +150,10 @@ class ClassReferenceLowering(val context: JsCommonBackendContext) : BodyLowering return null } - private fun callGetKClass( + abstract fun callGetKClass( returnType: IrType = reflectionSymbols.getKClass.owner.returnType, 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( - reflectionSymbols.getClassData, - typeArguments = listOf(type), - origin = JsStatementOrigins.CLASS_REFERENCE - ) + ): IrCall private fun buildCall(name: IrSimpleFunctionSymbol, vararg args: IrExpression): IrExpression = JsIrBuilder.buildCall(name).apply { @@ -154,7 +163,6 @@ class ClassReferenceLowering(val context: JsCommonBackendContext) : BodyLowering } private fun createKType(type: IrType, visitedTypeParams: MutableSet): IrExpression { - if (type is IrSimpleType) return createSimpleKType(type, visitedTypeParams) if (type is IrDynamicType) diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt index b80e6375cfe76..bf1d0ffd2cc81 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt @@ -489,8 +489,8 @@ private val staticMembersLoweringPhase = makeWasmModulePhase( ) private val classReferenceLoweringPhase = makeWasmModulePhase( - ::ClassReferenceLowering, - name = "ClassReferenceLowering", + ::WasmClassReferenceLowering, + name = "WasmClassReferenceLowering", description = "Handle class references" ) @@ -681,6 +681,8 @@ val wasmPhases = SameTypeNamedCompilerPhase( wasmStringSwitchOptimizerLowering then + associatedObjectsLowering then + complexExternalDeclarationsToTopLevelFunctionsLowering then complexExternalDeclarationsUsagesLowering then @@ -727,8 +729,6 @@ val wasmPhases = SameTypeNamedCompilerPhase( eraseVirtualDispatchReceiverParametersTypes then bridgesConstructionPhase then - associatedObjectsLowering then - objectDeclarationLoweringPhase then genericReturnTypeLowering then unitToVoidLowering then diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt index a185e7d818cca..098a0517b23d5 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt @@ -52,10 +52,9 @@ class WasmSymbols( internal inner class WasmReflectionSymbols : ReflectionSymbols { override val createKType: IrSimpleFunctionSymbol = getInternalFunction("createKType") - override val getClassData: IrSimpleFunctionSymbol = getInternalFunction("wasmGetTypeInfoData") override val getKClass: IrSimpleFunctionSymbol = getInternalFunction("getKClass") 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 getStarKTypeProjection = getInternalFunction("getStarKTypeProjection") override val createCovariantKTypeProjection = getInternalFunction("createCovariantKTypeProjection") @@ -67,6 +66,7 @@ class WasmSymbols( val getTypeInfoTypeDataByPtr: IrSimpleFunctionSymbol = getInternalFunction("getTypeInfoTypeDataByPtr") val wasmTypeInfoData: IrClassSymbol = getInternalClass("TypeInfoData") + val kClassImpl: IrClassSymbol = getInternalClass("KClassImpl") } internal val reflectionSymbols: WasmReflectionSymbols = WasmReflectionSymbols() @@ -359,6 +359,8 @@ class WasmSymbols( internal val throwAsJsException: IrSimpleFunctionSymbol = getInternalFunction("throwAsJsException") + + val kExternalClassImpl: IrClassSymbol = getInternalClass("KExternalClassImpl") } private val wasmExportClass = getIrClass(FqName("kotlin.wasm.WasmExport")) diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/AssociatedObjectsLowering.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/AssociatedObjectsLowering.kt index ab360ecb353f2..48cea65768221 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/AssociatedObjectsLowering.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/AssociatedObjectsLowering.kt @@ -60,6 +60,7 @@ class AssociatedObjectsLowering(val context: WasmBackendContext) : FileLoweringP for (klassAnnotation in declaration.annotations) { val annotationClass = klassAnnotation.symbol.owner.parentClassOrNull ?: continue if (klassAnnotation.valueArgumentsCount != 1) continue + if (declaration.isEffectivelyExternal()) continue val associatedObject = klassAnnotation.associatedObject() ?: continue val builder = cachedBuilder ?: context.createIrBuilder(context.wasmSymbols.initAssociatedObjects) @@ -104,7 +105,7 @@ private fun IrBuilderWithScope.createAssociatedObjectAdd( ) addCall.putValueArgument( 3, - irGetObjectValue(irBuiltIns.anyType, associatedObject) + irGetObjectValue(associatedObject.defaultType, associatedObject) ) } } \ No newline at end of file diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/BuiltInsLowering.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/BuiltInsLowering.kt index 558a09ac5f54b..a597a9f57f0d1 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/BuiltInsLowering.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/BuiltInsLowering.kt @@ -12,14 +12,19 @@ import org.jetbrains.kotlin.backend.common.lower.createIrBuilder import org.jetbrains.kotlin.backend.wasm.WasmBackendContext import org.jetbrains.kotlin.config.AnalysisFlags 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.utils.erasedUpperBound import org.jetbrains.kotlin.ir.backend.js.utils.isEqualsInheritedFromAny 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.IrSimpleFunction import org.jetbrains.kotlin.ir.expressions.IrCall 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.symbols.IrSimpleFunctionSymbol import org.jetbrains.kotlin.ir.types.* @@ -157,35 +162,35 @@ class BuiltInsLowering(val context: WasmBackendContext) : FileLoweringPass { val newSymbol = irBuiltins.suspendFunctionN(arity).getSimpleFunction("invoke")!! return irCall(call, newSymbol, argumentsAsReceivers = true) } - symbols.reflectionSymbols.getClassData -> { + context.reflectionSymbols.getKClass -> { val type = call.getTypeArgument(0)!! val klass = type.classOrNull?.owner ?: error("Invalid type") - val typeId = builder.irCall(symbols.wasmTypeId).also { - it.putTypeArgument(0, type) + val constructorArgument: IrExpression + val kclassConstructor: IrConstructor + if (klass.isEffectivelyExternal()) { + check(context.configuration.get(JSConfigurationKeys.WASM_TARGET, WasmTarget.JS) == WasmTarget.JS) { "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 builder.irCall(context.wasmSymbols.reflectionSymbols.getTypeInfoTypeDataByPtr).also { - it.putValueArgument(0, typeId) - } - } else { - val infoDataCtor = symbols.reflectionSymbols.wasmTypeInfoData.constructors.first() - 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 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)) - } - } + return IrConstructorCallImpl( + startOffset = UNDEFINED_OFFSET, + endOffset = UNDEFINED_OFFSET, + type = kclassConstructor.returnType, + symbol = kclassConstructor.symbol, + typeArgumentsCount = 1, + valueArgumentsCount = 1, + constructorTypeArgumentsCount = 0 + ).also { + it.putClassTypeArgument(0, type) + it.putValueArgument(0, constructorArgument) } } + symbols.enumValueOfIntrinsic -> return EnumIntrinsicsUtils.transformEnumValueOfIntrinsic(call) symbols.enumValuesIntrinsic -> @@ -197,6 +202,40 @@ class BuiltInsLowering(val context: WasmBackendContext) : FileLoweringPass { 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) { val builder = context.createIrBuilder(irFile.symbol) irFile.transformChildrenVoid(object : IrElementTransformerVoidWithContext() { diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/ComplexExternalDeclarationsToTopLevelFunctionsLowering.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/ComplexExternalDeclarationsToTopLevelFunctionsLowering.kt index 8d4b9539b0625..2b8c94abf83ba 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/ComplexExternalDeclarationsToTopLevelFunctionsLowering.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/ComplexExternalDeclarationsToTopLevelFunctionsLowering.kt @@ -15,10 +15,7 @@ import org.jetbrains.kotlin.backend.wasm.utils.getWasmImportDescriptor import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET -import org.jetbrains.kotlin.ir.backend.js.utils.getJsModule -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.backend.js.utils.* import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter import org.jetbrains.kotlin.ir.builders.declarations.buildFun 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.IrConstImpl 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.visitors.* import org.jetbrains.kotlin.name.Name @@ -96,8 +94,10 @@ class ComplexExternalDeclarationsToTopLevelFunctionsLowering(val context: WasmBa if (klass.kind == ClassKind.OBJECT) generateExternalObjectInstanceGetter(klass) - if (klass.kind != ClassKind.INTERFACE) + if (klass.kind != ClassKind.INTERFACE) { generateInstanceCheckForExternalClass(klass) + generateGetClassForExternalClass(klass) + } } 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( originalName: Name, suffix: String, diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/WasmClassReferenceLowering.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/WasmClassReferenceLowering.kt new file mode 100644 index 0000000000000..1a349a6ea0960 --- /dev/null +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/WasmClassReferenceLowering.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/compiler/testData/codegen/boxWasmJsInterop/associatedExternalObject.kt b/compiler/testData/codegen/boxWasmJsInterop/associatedExternalObject.kt new file mode 100644 index 0000000000000..2c9f739cfd3fb --- /dev/null +++ b/compiler/testData/codegen/boxWasmJsInterop/associatedExternalObject.kt @@ -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() !== JS_OBJECT) return "FAIL2" + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/boxWasmJsInterop/reflectionOnExternals.kt b/compiler/testData/codegen/boxWasmJsInterop/reflectionOnExternals.kt new file mode 100644 index 0000000000000..03106cf24744a --- /dev/null +++ b/compiler/testData/codegen/boxWasmJsInterop/reflectionOnExternals.kt @@ -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 { 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>().classifier + if (typeOfPromiseClassifier !is KClass<*>) return "FAIL6" + checkPromise(typeOfPromiseClassifier, promiseObj, someObject)?.let { return "7_" + it } + + val typeOfPromiseWithArguments = typeOf>>() + 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" +} diff --git a/js/js.translator/testData/box/native/useClassFromInlineFun.kt b/js/js.translator/testData/box/native/useClassFromInlineFun.kt index 55cac8782a93b..5a12fbbe4b42f 100644 --- a/js/js.translator/testData/box/native/useClassFromInlineFun.kt +++ b/js/js.translator/testData/box/native/useClassFromInlineFun.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: WASM // EXPECTED_REACHABLE_NODES: 1414 // FILE: main.kt diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/TypeInfo.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/TypeInfo.kt index a093b14da89aa..9c939e4e92107 100644 --- a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/TypeInfo.kt +++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/TypeInfo.kt @@ -78,6 +78,7 @@ internal fun wasmIsInterface(obj: Any): Boolean = internal fun wasmTypeId(): Int = implementedAsIntrinsic +//TODO("Remove after bootstrap KT-65322") @ExcludedFromCodegen internal fun wasmGetTypeInfoData(): TypeInfoData = - implementedAsIntrinsic + implementedAsIntrinsic \ No newline at end of file diff --git a/libraries/stdlib/wasm/js/src/kotlin/reflect/KExternalClassImpl.kt b/libraries/stdlib/wasm/js/src/kotlin/reflect/KExternalClassImpl.kt new file mode 100644 index 0000000000000..eb7ac01235a41 --- /dev/null +++ b/libraries/stdlib/wasm/js/src/kotlin/reflect/KExternalClassImpl.kt @@ -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 @WasmPrimitiveConstructor constructor(private val jsConstructor: JsAny) : KClass { + 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" +} \ No newline at end of file diff --git a/libraries/stdlib/wasm/js/src/kotlin/reflect/reflection.kt b/libraries/stdlib/wasm/js/src/kotlin/reflect/reflection.kt new file mode 100644 index 0000000000000..9bace40a8012c --- /dev/null +++ b/libraries/stdlib/wasm/js/src/kotlin/reflect/reflection.kt @@ -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 getKClassForObject(obj: Any): KClass { + if (obj !is JsExternalBox) return KClassImpl(getTypeInfoTypeDataByPtr(obj.typeInfo)) + val jsConstructor = getConstructor(obj.ref) ?: error("JavaScript constructor is not defined") + return KExternalClassImpl(jsConstructor) +} \ No newline at end of file diff --git a/libraries/stdlib/wasm/src/kotlin/reflect/KClassImpl.kt b/libraries/stdlib/wasm/src/kotlin/reflect/KClassImpl.kt index 593800ef7208b..519e8df7c3c8a 100644 --- a/libraries/stdlib/wasm/src/kotlin/reflect/KClassImpl.kt +++ b/libraries/stdlib/wasm/src/kotlin/reflect/KClassImpl.kt @@ -2,13 +2,9 @@ * 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. */ -package kotlin.reflect.wasm.internal +package kotlin.wasm.internal -import kotlin.reflect.* -import kotlin.wasm.internal.TypeInfoData -import kotlin.wasm.internal.getSuperTypeId -import kotlin.wasm.internal.isInterfaceById -import kotlin.wasm.internal.isInterfaceType +import kotlin.reflect.KClass internal object NothingKClassImpl : KClass { override val simpleName: String = "Nothing" @@ -30,10 +26,10 @@ internal object ErrorKClass : KClass { override fun hashCode(): Int = super.hashCode() // KT-24971 } -internal class KClassImpl(internal val typeData: TypeInfoData) : KClass { +internal class KClassImpl @WasmPrimitiveConstructor constructor(internal val typeData: TypeInfoData) : KClass { override val simpleName: String get() = typeData.typeName - override val qualifiedName: String = - if (typeData.packageName.isEmpty()) typeData.typeName else "${typeData.packageName}.${typeData.typeName}" + override val qualifiedName: String + get() = if (typeData.packageName.isEmpty()) typeData.typeName else "${typeData.packageName}.${typeData.typeName}" private fun checkSuperTypeInstance(obj: Any): Boolean { var typeId = obj.typeInfo diff --git a/libraries/stdlib/wasm/src/kotlin/reflect/associatedObjectsImpl.kt b/libraries/stdlib/wasm/src/kotlin/reflect/associatedObjectsImpl.kt index 2a34f36b03332..239c8cf2b0b4d 100644 --- a/libraries/stdlib/wasm/src/kotlin/reflect/associatedObjectsImpl.kt +++ b/libraries/stdlib/wasm/src/kotlin/reflect/associatedObjectsImpl.kt @@ -5,7 +5,6 @@ package kotlin.wasm.internal -import kotlin.reflect.wasm.internal.KClassImpl import kotlin.reflect.KClass internal var associatedObjects: Map? = null diff --git a/libraries/stdlib/wasm/src/kotlin/reflect/primitives.kt b/libraries/stdlib/wasm/src/kotlin/reflect/primitives.kt index 94a33511c5943..4e63e15293e96 100644 --- a/libraries/stdlib/wasm/src/kotlin/reflect/primitives.kt +++ b/libraries/stdlib/wasm/src/kotlin/reflect/primitives.kt @@ -7,7 +7,6 @@ package kotlin.wasm.internal import kotlin.reflect.KClass import kotlin.reflect.KFunction -import kotlin.reflect.wasm.internal.* internal object PrimitiveClasses { diff --git a/libraries/stdlib/wasm/src/kotlin/reflect/reflection.kt b/libraries/stdlib/wasm/src/kotlin/reflect/reflection.kt index 5d1ebd6791a07..d8611853087c8 100644 --- a/libraries/stdlib/wasm/src/kotlin/reflect/reflection.kt +++ b/libraries/stdlib/wasm/src/kotlin/reflect/reflection.kt @@ -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. */ -// a package is omitted to get declarations directly under the module package kotlin.wasm.internal import kotlin.reflect.* -import kotlin.reflect.wasm.internal.* +internal expect fun getKClassForObject(obj: Any): KClass + +//TODO(Replace getKClass to intrinsic argument-less implementation after bootstrap KT-65322") +//@ExcludedFromCodegen +//internal fun getKClass(): KClass = +// implementedAsIntrinsic internal fun getKClass(typeInfoData: TypeInfoData): KClass = KClassImpl(typeInfoData) @@ -34,12 +38,12 @@ internal fun getKClassFromExpression(e: T): KClass = is DoubleArray -> PrimitiveClasses.doubleArrayClass is KClass<*> -> KClass::class is Array<*> -> PrimitiveClasses.arrayClass - else -> getKClass(getTypeInfoTypeDataByPtr(e.typeInfo)) + else -> getKClassForObject(e) } as KClass @Suppress("REIFIED_TYPE_PARAMETER_NO_INLINE") internal inline fun wasmGetKClass(): KClass = - KClassImpl(wasmGetTypeInfoData()) + KClassImpl(getTypeInfoTypeDataByPtr(wasmTypeId())) internal fun createKType(classifier: KClassifier, arguments: Array, isMarkedNullable: Boolean): KType = KTypeImpl(classifier, arguments.asList(), isMarkedNullable) diff --git a/libraries/stdlib/wasm/wasi/src/kotlin/reflect/reflection.kt b/libraries/stdlib/wasm/wasi/src/kotlin/reflect/reflection.kt new file mode 100644 index 0000000000000..59a8e3b36e9e6 --- /dev/null +++ b/libraries/stdlib/wasm/wasi/src/kotlin/reflect/reflection.kt @@ -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 getKClassForObject(obj: Any): KClass = + KClassImpl(getTypeInfoTypeDataByPtr(obj.typeInfo)) \ No newline at end of file diff --git a/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/FirWasmCodegenWasmJsInteropTestGenerated.java b/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/FirWasmCodegenWasmJsInteropTestGenerated.java index 18348e29e1c1d..51773806a3170 100644 --- a/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/FirWasmCodegenWasmJsInteropTestGenerated.java +++ b/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/FirWasmCodegenWasmJsInteropTestGenerated.java @@ -25,6 +25,12 @@ public void testAllFilesPresentInBoxWasmJsInterop() throws Exception { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/boxWasmJsInterop"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.WASM, true); } + @Test + @TestMetadata("associatedExternalObject.kt") + public void testAssociatedExternalObject() throws Exception { + runTest("compiler/testData/codegen/boxWasmJsInterop/associatedExternalObject.kt"); + } + @Test @TestMetadata("callingWasmDirectly.kt") public void testCallingWasmDirectly() throws Exception { @@ -151,6 +157,12 @@ public void testNullableExternRefs() throws Exception { runTest("compiler/testData/codegen/boxWasmJsInterop/nullableExternRefs.kt"); } + @Test + @TestMetadata("reflectionOnExternals.kt") + public void testReflectionOnExternals() throws Exception { + runTest("compiler/testData/codegen/boxWasmJsInterop/reflectionOnExternals.kt"); + } + @Test @TestMetadata("types.kt") public void testTypes() throws Exception { diff --git a/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/K1WasmCodegenWasmJsInteropTestGenerated.java b/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/K1WasmCodegenWasmJsInteropTestGenerated.java index 376ff24c2a697..1147d666afdee 100644 --- a/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/K1WasmCodegenWasmJsInteropTestGenerated.java +++ b/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/K1WasmCodegenWasmJsInteropTestGenerated.java @@ -25,6 +25,12 @@ public void testAllFilesPresentInBoxWasmJsInterop() throws Exception { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/boxWasmJsInterop"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.WASM, true); } + @Test + @TestMetadata("associatedExternalObject.kt") + public void testAssociatedExternalObject() throws Exception { + runTest("compiler/testData/codegen/boxWasmJsInterop/associatedExternalObject.kt"); + } + @Test @TestMetadata("callingWasmDirectly.kt") public void testCallingWasmDirectly() throws Exception { @@ -151,6 +157,12 @@ public void testNullableExternRefs() throws Exception { runTest("compiler/testData/codegen/boxWasmJsInterop/nullableExternRefs.kt"); } + @Test + @TestMetadata("reflectionOnExternals.kt") + public void testReflectionOnExternals() throws Exception { + runTest("compiler/testData/codegen/boxWasmJsInterop/reflectionOnExternals.kt"); + } + @Test @TestMetadata("types.kt") public void testTypes() throws Exception {