From cf949e8760df6e0d28ae41db74b957539cff2eec Mon Sep 17 00:00:00 2001 From: Artem Kobzar Date: Thu, 6 Jul 2023 18:18:14 +0000 Subject: [PATCH] [K/JS] Support KClass<*>.createInstance reflection method ^KT-58684 Fixed --- .../kotlin/ir/backend/js/JsIntrinsics.kt | 3 + .../kotlin/ir/backend/js/JsLoweringPhases.kt | 7 ++ .../kotlin/ir/backend/js/JsMapping.kt | 2 + .../js/dce/JsUsefulDeclarationProcessor.kt | 17 +++- .../js/dce/UsefulDeclarationProcessor.kt | 6 +- .../js/dce/UselessDeclarationsRemover.kt | 6 ++ ...CollectClassDefaultConstructorsLowering.kt | 35 ++++++++ .../js/lower/ES6ConstructorLowering.kt | 5 ++ ...6PrimaryConstructorOptimizationLowering.kt | 14 ++-- .../transformers/irToJs/JsClassGenerator.kt | 62 +++++++++----- .../irToJs/JsIntrinsicTransformers.kt | 2 + .../kotlin/ir/backend/js/utils/IrJsUtils.kt | 20 +++-- .../js/test/fir/FirJsBoxTestGenerated.java | 12 +++ .../js/test/ir/IrBoxJsES6TestGenerated.java | 12 +++ .../js/test/ir/IrBoxJsTestGenerated.java | 12 +++ .../testData/box/reflection/createInstance.kt | 84 +++++++++++++++++++ .../reflection/createInstanceByInstance.kt | 81 ++++++++++++++++++ libraries/stdlib/api/js/kotlin.js.kt | 9 ++ libraries/stdlib/api/js/kotlin.reflect.kt | 4 + .../common/src/kotlin/JsAnnotationsH.kt | 28 +++++++ libraries/stdlib/js-ir/runtime/BitMask.kt | 2 +- .../stdlib/js-ir/runtime/jsIntrinsics.kt | 5 +- .../stdlib/js-ir/runtime/metadataUtils.kt | 38 +++++++-- .../stdlib/js-ir/runtime/reflectRuntime.kt | 2 +- .../src/kotlin/reflect/createInstance.kt | 28 +++++++ .../kotlin-stdlib-runtime-merged.txt | 3 + 26 files changed, 453 insertions(+), 46 deletions(-) create mode 100644 compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/CollectClassDefaultConstructorsLowering.kt create mode 100644 js/js.translator/testData/box/reflection/createInstance.kt create mode 100644 js/js.translator/testData/box/reflection/createInstanceByInstance.kt create mode 100644 libraries/stdlib/js-ir/src/kotlin/reflect/createInstance.kt 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 aefdc87f627..9d51d22421a 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 @@ -30,6 +30,9 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC // TODO: Should we drop operator intrinsics in favor of IrDynamicOperatorExpression? + // Modes + val jsIsEs6 = getInternalFunction("jsIsEs6") + // Global variables val void = getInternalProperty("VOID") val globalThis = getInternalProperty("globalThis") 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 b9c42272d1c..59e88f7375a 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 @@ -140,6 +140,12 @@ private val validateIrAfterLowering = makeCustomJsModulePhase( description = "Validate IR after lowering" ).toModuleLowering() +private val collectClassDefaultConstructorsPhase = makeDeclarationTransformerPhase( + ::CollectClassDefaultConstructorsLowering, + name = "CollectClassDefaultConstructorsLowering", + description = "Collect classes default constructors to add it to metadata on code generating phase" +) + private val preventExportOfSyntheticDeclarationsLowering = makeDeclarationTransformerPhase( ::ExcludeSyntheticDeclarationsFromExportLowering, name = "ExcludeSyntheticDeclarationsFromExportLowering", @@ -898,6 +904,7 @@ val loweringList = listOf( initializersLoweringPhase, initializersCleanupLoweringPhase, kotlinNothingValueExceptionPhase, + collectClassDefaultConstructorsPhase, // Common prefix ends enumWhenPhase, enumEntryInstancesLoweringPhase, 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 83f4ad592f2..d959d7cd8e7 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 @@ -12,6 +12,8 @@ import org.jetbrains.kotlin.ir.declarations.* import java.util.WeakHashMap class JsMapping : DefaultMapping() { + val classToItsDefaultConstructor = WeakHashMap() + val esClassWhichNeedBoxParameters = DefaultDelegateFactory.newDeclarationToValueMapping() val esClassToPossibilityForOptimization = DefaultDelegateFactory.newDeclarationToValueMapping>() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/JsUsefulDeclarationProcessor.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/JsUsefulDeclarationProcessor.kt index 02511255b40..8d4185b1b9c 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/JsUsefulDeclarationProcessor.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/JsUsefulDeclarationProcessor.kt @@ -70,10 +70,14 @@ internal class JsUsefulDeclarationProcessor( referencedJsClassesFromExpressions += ref.owner } + context.reflectionSymbols.getKClass -> { + addConstructedClass(expression.getTypeArgument(0)!!.classifierOrFail.owner as IrClass) + } + context.intrinsics.jsObjectCreateSymbol -> { val classToCreate = expression.getTypeArgument(0)!!.classifierOrFail.owner as IrClass classToCreate.enqueue(data, "intrinsic: jsObjectCreateSymbol") - constructedClasses += classToCreate + addConstructedClass(classToCreate) } context.intrinsics.jsCreateThisSymbol -> { @@ -88,7 +92,7 @@ internal class JsUsefulDeclarationProcessor( val classToCreate = classTypeToCreate.classifierOrFail.owner as IrClass classToCreate.enqueue(data, "intrinsic: jsCreateThis") - constructedClasses += classToCreate + addConstructedClass(classToCreate) } context.intrinsics.jsEquals -> { @@ -117,7 +121,14 @@ internal class JsUsefulDeclarationProcessor( } } } + } + override fun addConstructedClass(irClass: IrClass) { + super.addConstructedClass(irClass) + + if (irClass.isClass) { + context.findDefaultConstructorFor(irClass)?.enqueue(irClass, "intrinsic: KClass<*>.createInstance") + } } override fun processSuperTypes(irClass: IrClass) { @@ -178,7 +189,7 @@ internal class JsUsefulDeclarationProcessor( super.processSimpleFunction(irFunction) if (irFunction.isEs6ConstructorReplacement) { - constructedClasses += irFunction.dispatchReceiverParameter?.type?.classOrNull?.owner!! + addConstructedClass(irFunction.dispatchReceiverParameter?.type?.classOrNull?.owner!!) } if (irFunction.isReal && irFunction.body != null) { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/UsefulDeclarationProcessor.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/UsefulDeclarationProcessor.kt index b723364f144..4231cbc3a90 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/UsefulDeclarationProcessor.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/UsefulDeclarationProcessor.kt @@ -139,6 +139,10 @@ abstract class UsefulDeclarationProcessor( val usefulPolyfilledDeclarations = hashSetOf() + protected open fun addConstructedClass(irClass: IrClass) { + constructedClasses += irClass + } + protected open fun processField(irField: IrField): Unit = Unit protected open fun processClass(irClass: IrClass) { @@ -176,7 +180,7 @@ abstract class UsefulDeclarationProcessor( // Collect instantiated classes. irConstructor.constructedClass.let { it.enqueue(irConstructor, "constructed class") - constructedClasses += it + addConstructedClass(it) } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/UselessDeclarationsRemover.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/UselessDeclarationsRemover.kt index 5c3cb695dc0..0b272f0c155 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/UselessDeclarationsRemover.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/UselessDeclarationsRemover.kt @@ -60,6 +60,12 @@ class UselessDeclarationsRemover( .flatMap { it.classOrNull?.collectUsedSuperTypes() ?: emptyList() } .distinct() .memoryOptimizedMap { it.defaultType } + + // Remove default constructor if the class was never constructed + val defaultConstructor = context.findDefaultConstructorFor(declaration) + if (defaultConstructor != null && defaultConstructor !in usefulDeclarations) { + context.mapping.classToItsDefaultConstructor.remove(declaration) + } } private fun IrClassSymbol.collectUsedSuperTypes(): Set { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/CollectClassDefaultConstructorsLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/CollectClassDefaultConstructorsLowering.kt new file mode 100644 index 00000000000..c2b37ae2dfc --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/CollectClassDefaultConstructorsLowering.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2010-2023 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.ir.backend.js.lower + +import org.jetbrains.kotlin.backend.common.DeclarationTransformer +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.util.constructors +import org.jetbrains.kotlin.ir.util.isClass + +class CollectClassDefaultConstructorsLowering(private val context: JsIrBackendContext) : DeclarationTransformer { + override fun transformFlat(declaration: IrDeclaration): List? { + if (declaration is IrClass && declaration.couldContainDefaultConstructor()) { + context.mapping.classToItsDefaultConstructor[declaration] = declaration.defaultConstructor ?: return null + } + + return null + } + + private fun IrClass.couldContainDefaultConstructor(): Boolean { + return isClass && !isValue && !isExpect && modality != Modality.ABSTRACT && modality != Modality.SEALED + } + + private val IrClass.defaultConstructor: IrConstructor? + get() = constructors.singleOrNull { it.visibility == DescriptorVisibilities.PUBLIC && it.isDefaultConstructor() } + + private fun IrFunction.isDefaultConstructor(): Boolean { + return valueParameters.isEmpty() || valueParameters.all { it.defaultValue != null } + } +} \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ES6ConstructorLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ES6ConstructorLowering.kt index 98e8a4e0be3..6898904d222 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ES6ConstructorLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ES6ConstructorLowering.kt @@ -31,6 +31,7 @@ import org.jetbrains.kotlin.utils.newHashMapWithExpectedSize object ES6_INIT_CALL : IrStatementOriginImpl("ES6_INIT_CALL") object ES6_CONSTRUCTOR_REPLACEMENT : IrDeclarationOriginImpl("ES6_CONSTRUCTOR_REPLACEMENT") +object ES6_SYNTHETIC_EXPORT_CONSTRUCTOR: IrDeclarationOriginImpl("ES6_SYNTHETIC_EXPORT_CONSTRUCTOR") object ES6_PRIMARY_CONSTRUCTOR_REPLACEMENT : IrDeclarationOriginImpl("ES6_PRIMARY_CONSTRUCTOR_REPLACEMENT") object ES6_INIT_FUNCTION : IrDeclarationOriginImpl("ES6_INIT_FUNCTION") object ES6_DELEGATING_CONSTRUCTOR_REPLACEMENT : IrStatementOriginImpl("ES6_DELEGATING_CONSTRUCTOR_REPLACEMENT") @@ -50,6 +51,9 @@ val IrDeclaration.isInitFunction: Boolean val IrFunctionAccessExpression.isInitCall: Boolean get() = origin == ES6_INIT_CALL +val IrDeclaration.isSyntheticConstructorForExport: Boolean + get() = origin == ES6_SYNTHETIC_EXPORT_CONSTRUCTOR + class ES6ConstructorLowering(val context: JsIrBackendContext) : DeclarationTransformer { private var IrConstructor.constructorFactory by context.mapping.secondaryConstructorToFactory @@ -77,6 +81,7 @@ class ES6ConstructorLowering(val context: JsIrBackendContext) : DeclarationTrans statements.add(JsIrBuilder.buildReturn(symbol, selfReplacedConstructorCall, returnType)) } } + origin = ES6_SYNTHETIC_EXPORT_CONSTRUCTOR } } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ES6PrimaryConstructorOptimizationLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ES6PrimaryConstructorOptimizationLowering.kt index acb1f01a9af..7f5ccb293b4 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ES6PrimaryConstructorOptimizationLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ES6PrimaryConstructorOptimizationLowering.kt @@ -12,10 +12,7 @@ import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext import org.jetbrains.kotlin.ir.backend.js.export.isExported import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder -import org.jetbrains.kotlin.ir.backend.js.utils.JsAnnotations -import org.jetbrains.kotlin.ir.backend.js.utils.MutableReference -import org.jetbrains.kotlin.ir.backend.js.utils.irEmpty -import org.jetbrains.kotlin.ir.backend.js.utils.mutableReferenceOf +import org.jetbrains.kotlin.ir.backend.js.utils.* import org.jetbrains.kotlin.ir.builders.declarations.buildConstructor import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* @@ -35,12 +32,19 @@ class ES6PrimaryConstructorOptimizationLowering(private val context: JsIrBackend } val irClass = declaration.parentAsClass + val defaultConstructor = context.findDefaultConstructorFor(irClass) if (irClass.isExported(context)) { irClass.removeConstructorForExport() } - return listOf(declaration.convertToRegularConstructor(irClass)) + val constructorReplacement = declaration.convertToRegularConstructor(irClass) + + if (declaration == defaultConstructor) { + context.mapping.classToItsDefaultConstructor[irClass] = constructorReplacement + } + + return listOf(constructorReplacement) } private fun IrFunction.convertToRegularConstructor(irClass: IrClass): IrConstructor { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt index cc4acec8ec5..0e961e5e117 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt @@ -9,6 +9,7 @@ import org.jetbrains.kotlin.backend.common.compilationException import org.jetbrains.kotlin.descriptors.DescriptorVisibilities import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext +import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin import org.jetbrains.kotlin.ir.backend.js.export.isAllowedFakeOverriddenDeclaration import org.jetbrains.kotlin.ir.backend.js.export.isExported import org.jetbrains.kotlin.ir.backend.js.export.isOverriddenExported @@ -24,12 +25,15 @@ import org.jetbrains.kotlin.js.backend.ast.JsVars.JsVar import org.jetbrains.kotlin.js.common.isValidES5Identifier import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addToStdlib.butIf +import org.jetbrains.kotlin.utils.addToStdlib.runIf import org.jetbrains.kotlin.utils.memoryOptimizedMap import org.jetbrains.kotlin.utils.toSmartList class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationContext) { + private val backendContext = context.staticContext.backendContext + private val perFile = context.staticContext.isPerFile - private val es6mode = context.staticContext.backendContext.es6mode + private val es6mode = backendContext.es6mode private val className = context.getNameForClass(irClass) private val baseClass: IrType? = irClass.superTypes.firstOrNull { !it.classifierOrFail.isInterface } @@ -151,7 +155,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo if ( property.isFakeOverride && - !property.isAllowedFakeOverriddenDeclaration(context.staticContext.backendContext) + !property.isAllowedFakeOverriddenDeclaration(backendContext) ) continue @@ -170,8 +174,6 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo val overriddenSymbols = property.getter?.overriddenSymbols.orEmpty() - val backendContext = context.staticContext.backendContext - // Don't generate `defineProperty` if the property overrides a property from an exported class, // because we've already generated `defineProperty` for the base class property. // In other words, we only want to generate `defineProperty` once for each property. @@ -184,14 +186,14 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo val getterOverridesExternal = property.getter?.overridesExternal() == true val overriddenExportedGetter = !property.getter?.overriddenSymbols.isNullOrEmpty() && - property.getter?.isOverriddenExported(context.staticContext.backendContext) == true + property.getter?.isOverriddenExported(backendContext) == true - val noOverriddenExportedSetter = property.setter?.isOverriddenExported(context.staticContext.backendContext) == false + val noOverriddenExportedSetter = property.setter?.isOverriddenExported(backendContext) == false val needsOverride = (overriddenExportedGetter && noOverriddenExportedSetter) || - property.isAllowedFakeOverriddenDeclaration(context.staticContext.backendContext) + property.isAllowedFakeOverriddenDeclaration(backendContext) - if (irClass.isExported(context.staticContext.backendContext) && + if (irClass.isExported(backendContext) && (overriddenSymbols.isEmpty() || needsOverride) || hasOverriddenExportedInterfaceProperties || getterOverridesExternal || @@ -213,7 +215,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo // }); val getterForwarder = property.getter - .takeIf { it.shouldExportAccessor(context.staticContext.backendContext) } + .takeIf { it.shouldExportAccessor(backendContext) } .getOrGenerateIfFinalOrEs6Mode { propertyAccessorForwarder("getter forwarder") { JsReturn(JsInvocation(it)) @@ -221,7 +223,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo } val setterForwarder = property.setter - .takeIf { it.shouldExportAccessor(context.staticContext.backendContext) } + .takeIf { it.shouldExportAccessor(backendContext) } .getOrGenerateIfFinalOrEs6Mode { val setterArgName = JsName("value", false) propertyAccessorForwarder("setter forwarder") { @@ -266,7 +268,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo private fun IrSimpleFunction.isDefinedInsideExportedInterface(): Boolean { if (isJsExportIgnore() || correspondingPropertySymbol?.owner?.isJsExportIgnore() == true) return false - return (!isFakeOverride && parentClassOrNull.isExportedInterface(context.staticContext.backendContext)) || + return (!isFakeOverride && parentClassOrNull.isExportedInterface(backendContext)) || overriddenSymbols.any { it.owner.isDefinedInsideExportedInterface() } } @@ -281,7 +283,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo private fun IrSimpleFunction.generateAssignmentIfMangled(memberName: JsName) { if ( - irClass.isExported(context.staticContext.backendContext) && + irClass.isExported(backendContext) && visibility.isPublicAPI && hasMangledName() && correspondingPropertySymbol == null ) { @@ -370,24 +372,33 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo } private fun generateSetMetadataCall(): JsStatement { - val setMetadataFor = context.staticContext.backendContext.intrinsics.setMetadataForSymbol.owner + val setMetadataFor = backendContext.intrinsics.setMetadataForSymbol.owner val ctor = classNameRef val parent = baseClassRef?.takeIf { !es6mode } val name = generateSimpleName() val interfaces = generateInterfacesList() val metadataConstructor = getMetadataConstructor() + val defaultConstructor = runIf(irClass.isClass, ::findDefaultConstructor) val associatedObjectKey = generateAssociatedObjectKey() val associatedObjects = generateAssociatedObjects() val suspendArity = generateSuspendArity() - val undefined = context.staticContext.backendContext.getVoid().accept(IrElementToJsExpressionTransformer(), context) - return JsInvocation( JsNameRef(context.getNameForStaticFunction(setMetadataFor)), - listOf(ctor, name, metadataConstructor, parent, interfaces, associatedObjectKey, associatedObjects, suspendArity) + listOf( + ctor, + name, + metadataConstructor, + parent, + interfaces, + defaultConstructor, + associatedObjectKey, + associatedObjects, + suspendArity + ) .dropLastWhile { it == null } - .memoryOptimizedMap { it ?: undefined } + .memoryOptimizedMap { it ?: jsUndefined } ).makeStmt() } @@ -408,7 +419,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo } private fun getMetadataConstructor(): JsNameRef { - val metadataConstructorSymbol = with(context.staticContext.backendContext.intrinsics) { + val metadataConstructorSymbol = with(backendContext.intrinsics) { when { irClass.isInterface -> metadataInterfaceConstructorSymbol irClass.isObject -> metadataObjectConstructorSymbol @@ -428,8 +439,19 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo return JsArrayLiteral(listRef.toSmartList()) } + private fun findDefaultConstructor(): JsNameRef? { + return when (val defaultConstructor = backendContext.findDefaultConstructorFor(irClass)) { + is IrConstructor -> context.getNameForConstructor(defaultConstructor).makeRef() + is IrSimpleFunction -> when { + es6mode -> JsNameRef(context.getNameForMemberFunction(defaultConstructor), classNameRef) + else -> context.getNameForStaticFunction(defaultConstructor).makeRef() + } + else -> null + } + } + private fun generateSuspendArity(): JsArrayLiteral? { - val invokeFunctions = context.staticContext.backendContext.mapping.suspendArityStore[irClass] ?: return null + val invokeFunctions = backendContext.mapping.suspendArityStore[irClass] ?: return null val arity = invokeFunctions .map { it.valueParameters.size } .distinct() @@ -447,7 +469,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo val annotationClass = annotation.symbol.owner.constructedClass context.getAssociatedObjectKey(annotationClass)?.let { key -> annotation.associatedObject()?.let { obj -> - context.staticContext.backendContext.mapping.objectToGetInstanceFunction[obj]?.let { factory -> + backendContext.mapping.objectToGetInstanceFunction[obj]?.let { factory -> JsPropertyInitializer(JsIntLiteral(key), context.staticContext.getNameForStaticFunction(factory).makeRef()) } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIntrinsicTransformers.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIntrinsicTransformers.kt index b5527116965..98c7f597843 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIntrinsicTransformers.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIntrinsicTransformers.kt @@ -86,6 +86,8 @@ class JsIntrinsicTransformers(backendContext: JsIrBackendContext) { prefixOp(intrinsics.jsTypeOf, JsUnaryOperator.TYPEOF) + add(intrinsics.jsIsEs6) { _, _ -> JsBooleanLiteral(backendContext.es6mode) } + add(intrinsics.jsObjectCreateSymbol) { call, context -> val classToCreate = call.getTypeArgument(0)!!.classifierOrFail.owner as IrClass val className = classToCreate.getClassRef(context.staticContext) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/IrJsUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/IrJsUtils.kt index 4cea0986348..80e83b2f734 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/IrJsUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/IrJsUtils.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.ir.backend.js.utils import org.jetbrains.kotlin.backend.common.lower.parents +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities import org.jetbrains.kotlin.descriptors.isClass import org.jetbrains.kotlin.descriptors.isInterface import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET @@ -14,6 +15,10 @@ import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin import org.jetbrains.kotlin.ir.backend.js.JsStatementOrigins import org.jetbrains.kotlin.ir.backend.js.export.isExported import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder +import org.jetbrains.kotlin.ir.backend.js.lower.isBoxParameter +import org.jetbrains.kotlin.ir.backend.js.lower.isEs6ConstructorReplacement +import org.jetbrains.kotlin.ir.backend.js.lower.isSyntheticConstructorForExport +import org.jetbrains.kotlin.ir.backend.js.lower.isSyntheticPrimaryConstructor import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression @@ -25,13 +30,10 @@ import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol import org.jetbrains.kotlin.ir.symbols.IrReturnableBlockSymbol import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.classifierOrNull -import org.jetbrains.kotlin.ir.util.defaultType -import org.jetbrains.kotlin.ir.util.isAnnotationClass -import org.jetbrains.kotlin.ir.util.parentAsClass -import org.jetbrains.kotlin.ir.util.isEffectivelyExternal -import org.jetbrains.kotlin.ir.util.isEnumClass -import org.jetbrains.kotlin.ir.util.parentClassOrNull +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.js.backend.ast.JsNameRef import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.utils.filterIsInstanceAnd fun IrClass.jsConstructorReference(context: JsIrBackendContext): IrExpression { return JsIrBuilder.buildCall(context.intrinsics.jsClass, origin = JsStatementOrigins.CLASS_REFERENCE) @@ -127,3 +129,9 @@ fun IrDeclaration.isObjectInstanceField(): Boolean { fun IrField.isObjectInstanceField(): Boolean { return origin == IrDeclarationOrigin.FIELD_FOR_OBJECT_INSTANCE } + +fun JsIrBackendContext.findDefaultConstructorFor(irClass: IrClass): IrFunction? { + return mapping.classToItsDefaultConstructor[irClass]?.let { + mapping.secondaryConstructorToFactory[it] ?: it + } +} \ No newline at end of file diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java index c7dabe06bb5..927f8fa9131 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java @@ -9914,6 +9914,18 @@ public class FirJsBoxTestGenerated extends AbstractFirJsBoxTest { runTest("js/js.translator/testData/box/reflection/classJsName.kt"); } + @Test + @TestMetadata("createInstance.kt") + public void testCreateInstance() throws Exception { + runTest("js/js.translator/testData/box/reflection/createInstance.kt"); + } + + @Test + @TestMetadata("createInstanceByInstance.kt") + public void testCreateInstanceByInstance() throws Exception { + runTest("js/js.translator/testData/box/reflection/createInstanceByInstance.kt"); + } + @Test @TestMetadata("external.kt") public void testExternal() throws Exception { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java index 1c5505b7eea..d3d943c6e8d 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java @@ -10020,6 +10020,18 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test { runTest("js/js.translator/testData/box/reflection/classJsName.kt"); } + @Test + @TestMetadata("createInstance.kt") + public void testCreateInstance() throws Exception { + runTest("js/js.translator/testData/box/reflection/createInstance.kt"); + } + + @Test + @TestMetadata("createInstanceByInstance.kt") + public void testCreateInstanceByInstance() throws Exception { + runTest("js/js.translator/testData/box/reflection/createInstanceByInstance.kt"); + } + @Test @TestMetadata("external.kt") public void testExternal() throws Exception { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java index 29ed83d6d28..69504024299 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java @@ -9914,6 +9914,18 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { runTest("js/js.translator/testData/box/reflection/classJsName.kt"); } + @Test + @TestMetadata("createInstance.kt") + public void testCreateInstance() throws Exception { + runTest("js/js.translator/testData/box/reflection/createInstance.kt"); + } + + @Test + @TestMetadata("createInstanceByInstance.kt") + public void testCreateInstanceByInstance() throws Exception { + runTest("js/js.translator/testData/box/reflection/createInstanceByInstance.kt"); + } + @Test @TestMetadata("external.kt") public void testExternal() throws Exception { diff --git a/js/js.translator/testData/box/reflection/createInstance.kt b/js/js.translator/testData/box/reflection/createInstance.kt new file mode 100644 index 00000000000..67dfe61a9c2 --- /dev/null +++ b/js/js.translator/testData/box/reflection/createInstance.kt @@ -0,0 +1,84 @@ +// IGNORE_BACKEND: JS +// KJS_WITH_FULL_RUNTIME + +import kotlin.reflect.createInstance +import kotlin.test.assertTrue +import kotlin.test.fail + +// Good classes + +class Simple +class PrimaryWithDefaults(val d1: String = "d1", val d2: Int = 2) +class Secondary(val s: String) { + constructor() : this("s") +} +class SecondaryWithDefaults(val s: String) { + constructor(x: Int = 0) : this(x.toString()) +} +class SecondaryWithDefaultsNoPrimary { + constructor(x: Int) {} + constructor(s: String = "") {} +} + +// Bad classes + +class NoNoArgConstructor(val s: String) { + constructor(x: Int) : this(x.toString()) +} +class NoArgAndDefault() { + constructor(x: Int = 0) : this() +} +class DefaultPrimaryAndDefaultSecondary(val s: String = "") { + constructor(x: Int = 0) : this(x.toString()) +} +class SeveralDefaultSecondaries { + constructor(x: Int = 0) {} + constructor(s: String = "") {} + constructor(d: Double = 3.14) {} +} +class PrivateConstructor private constructor() +object Object +interface Interface +enum class EnumFoo { A, B } + +abstract class AbstractClass + +sealed class SealedClass + +// ----------- + +inline fun test() { + val instance = T::class.createInstance() + assertTrue(instance is T) +} + +inline fun testFail() { + try { + T::class.createInstance() + fail("createInstance should have failed on ${T::class}") + } catch (e: Exception) { + // OK + } +} + +fun box(): String { + test() + test() + test() + test() + test() + test() + + testFail() + testFail() + testFail() + testFail() + testFail() + testFail() + testFail() + testFail() + testFail() + testFail() + + return "OK" +} diff --git a/js/js.translator/testData/box/reflection/createInstanceByInstance.kt b/js/js.translator/testData/box/reflection/createInstanceByInstance.kt new file mode 100644 index 00000000000..1b6c89fa27a --- /dev/null +++ b/js/js.translator/testData/box/reflection/createInstanceByInstance.kt @@ -0,0 +1,81 @@ +// IGNORE_BACKEND: JS +// KJS_WITH_FULL_RUNTIME + +import kotlin.reflect.createInstance +import kotlin.test.assertTrue +import kotlin.test.fail + +// Good classes + +class Simple +class PrimaryWithDefaults(val d1: String = "d1", val d2: Int = 2) +class Secondary(val s: String) { + constructor() : this("s") +} +class SecondaryWithDefaults(val s: String) { + constructor(x: Int = 0) : this(x.toString()) +} +class SecondaryWithDefaultsNoPrimary { + constructor(x: Int) {} + constructor(s: String = "") {} +} + +// Bad classes + +class NoNoArgConstructor(val s: String) { + constructor(x: Int) : this(x.toString()) +} +class NoArgAndDefault() { + constructor(x: Int = 0) : this() +} +class DefaultPrimaryAndDefaultSecondary(val s: String = "") { + constructor(x: Int = 0) : this(x.toString()) +} +class SeveralDefaultSecondaries { + constructor(x: Int = 0) {} + constructor(s: String = "") {} + constructor(d: Double = 3.14) {} +} +class PrivateConstructor private constructor() { + companion object { + fun create() = PrivateConstructor() + } +} +object Object +enum class EnumFoo { A, B } + +// ----------- + +inline fun testInstance(x: T) { + val kclass = x::class + val anotherInstance = kclass.createInstance() + assertTrue(kclass.isInstance(x) && kclass.isInstance(anotherInstance)) +} + +inline fun testInstanceFail(x: T) { + try { + val kclass = x::class + kclass.createInstance() + fail("createInstance should have failed on $kclass") + } catch (e: Exception) { + // OK + } +} + +fun box(): String { + testInstance(Simple()) + testInstance(PrimaryWithDefaults("d2")) + testInstance(Secondary("test")) + testInstance(SecondaryWithDefaults("test")) + testInstance(SecondaryWithDefaultsNoPrimary(2)) + + testInstanceFail(NoNoArgConstructor(4)) + testInstanceFail(NoArgAndDefault()) + testInstanceFail(DefaultPrimaryAndDefaultSecondary(4)) + testInstanceFail(SeveralDefaultSecondaries(4)) + testInstanceFail(PrivateConstructor.create()) + testInstanceFail(Object) + testInstanceFail(EnumFoo.A) + + return "OK" +} diff --git a/libraries/stdlib/api/js/kotlin.js.kt b/libraries/stdlib/api/js/kotlin.js.kt index c76ae26c381..3d69458c9c8 100644 --- a/libraries/stdlib/api/js/kotlin.js.kt +++ b/libraries/stdlib/api/js/kotlin.js.kt @@ -216,6 +216,15 @@ public final annotation class ExperimentalJsFileName : kotlin.Annotation { public constructor ExperimentalJsFileName() } +@kotlin.RequiresOptIn(level = Level.WARNING) +@kotlin.annotation.Retention(value = AnnotationRetention.BINARY) +@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.CLASS, AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FIELD, AnnotationTarget.LOCAL_VARIABLE, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.TYPEALIAS}) +@kotlin.annotation.MustBeDocumented +@kotlin.SinceKotlin(version = "1.9") +public final annotation class ExperimentalJsReflectionCreateInstance : kotlin.Annotation { + public constructor ExperimentalJsReflectionCreateInstance() +} + public external object JSON { public final fun parse(text: kotlin.String): T diff --git a/libraries/stdlib/api/js/kotlin.reflect.kt b/libraries/stdlib/api/js/kotlin.reflect.kt index 7d7e69b14af..76a8a8c9947 100644 --- a/libraries/stdlib/api/js/kotlin.reflect.kt +++ b/libraries/stdlib/api/js/kotlin.reflect.kt @@ -7,6 +7,10 @@ public inline fun typeOf(): kotlin.reflect.KType @kotlin.internal.LowPriorityInOverloadResolution public fun kotlin.reflect.KClass.cast(value: kotlin.Any?): T +@kotlin.SinceKotlin(version = "1.9") +@kotlin.js.ExperimentalJsReflectionCreateInstance +public fun kotlin.reflect.KClass.createInstance(): T + @kotlin.reflect.ExperimentalAssociatedObjects public inline fun kotlin.reflect.KClass<*>.findAssociatedObject(): kotlin.Any? diff --git a/libraries/stdlib/common/src/kotlin/JsAnnotationsH.kt b/libraries/stdlib/common/src/kotlin/JsAnnotationsH.kt index 56f843a2090..3d1afbbf38a 100644 --- a/libraries/stdlib/common/src/kotlin/JsAnnotationsH.kt +++ b/libraries/stdlib/common/src/kotlin/JsAnnotationsH.kt @@ -6,6 +6,7 @@ package kotlin.js import kotlin.annotation.AnnotationTarget.* +import kotlin.reflect.KClass /** * Gives a declaration (a function, a property or a class) specific name in JavaScript. @@ -100,3 +101,30 @@ public expect annotation class JsExport() { public annotation class Ignore() } + +/** + * This annotation marks the experimental Kotlin/JS reflection API that allows to create an instance of provided [KClass] + * The API can be removed completely in any further release. + * + * Any usage of a declaration annotated with `@ExperimentalJsReflectionCreateInstance` should be accepted either by + * annotating that usage with the [OptIn] annotation, e.g. `@OptIn(ExperimentalJsReflectionCreateInstance::class)`, + * or by using the compiler argument `-opt-in=kotlin.js.ExperimentalJsReflectionCreateInstance`. + */ +@RequiresOptIn(level = RequiresOptIn.Level.WARNING) +@Retention(AnnotationRetention.BINARY) +@Target( + AnnotationTarget.CLASS, + AnnotationTarget.ANNOTATION_CLASS, + AnnotationTarget.PROPERTY, + AnnotationTarget.FIELD, + AnnotationTarget.LOCAL_VARIABLE, + AnnotationTarget.VALUE_PARAMETER, + AnnotationTarget.CONSTRUCTOR, + AnnotationTarget.FUNCTION, + AnnotationTarget.PROPERTY_GETTER, + AnnotationTarget.PROPERTY_SETTER, + AnnotationTarget.TYPEALIAS +) +@MustBeDocumented +@SinceKotlin("1.9") +public annotation class ExperimentalJsReflectionCreateInstance \ No newline at end of file diff --git a/libraries/stdlib/js-ir/runtime/BitMask.kt b/libraries/stdlib/js-ir/runtime/BitMask.kt index ada573ee6ff..c46adf11570 100644 --- a/libraries/stdlib/js-ir/runtime/BitMask.kt +++ b/libraries/stdlib/js-ir/runtime/BitMask.kt @@ -8,8 +8,8 @@ package kotlin.js internal typealias BitMask = IntArray private fun bitMaskWith(activeBit: Int): BitMask { - val intArray = IntArray((activeBit shr 5) + 1) val numberIndex = activeBit shr 5 + val intArray = IntArray(numberIndex + 1) val positionInNumber = activeBit and 31 val numberWithSettledBit = 1 shl positionInNumber intArray[numberIndex] = intArray[numberIndex] or numberWithSettledBit diff --git a/libraries/stdlib/js-ir/runtime/jsIntrinsics.kt b/libraries/stdlib/js-ir/runtime/jsIntrinsics.kt index 3dc2bb3af34..c3d11ef37c0 100644 --- a/libraries/stdlib/js-ir/runtime/jsIntrinsics.kt +++ b/libraries/stdlib/js-ir/runtime/jsIntrinsics.kt @@ -227,4 +227,7 @@ internal fun jsInIntrinsic(lhs: Any?, rhs: Any): Boolean internal fun jsDelete(e: Any?) @JsIntrinsic -internal fun jsContextfulRef(context: dynamic, fn: dynamic): dynamic \ No newline at end of file +internal fun jsContextfulRef(context: dynamic, fn: dynamic): dynamic + +@JsIntrinsic +internal fun jsIsEs6(): Boolean diff --git a/libraries/stdlib/js-ir/runtime/metadataUtils.kt b/libraries/stdlib/js-ir/runtime/metadataUtils.kt index 2fb0258362c..5748d3e6a8d 100644 --- a/libraries/stdlib/js-ir/runtime/metadataUtils.kt +++ b/libraries/stdlib/js-ir/runtime/metadataUtils.kt @@ -7,9 +7,10 @@ package kotlin.js internal fun setMetadataFor( ctor: Ctor, name: String?, - metadataConstructor: (name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array?) -> Metadata, + metadataConstructor: (name: String?, defaultConstructor: dynamic, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array?) -> Metadata, parent: Ctor?, interfaces: Array?, + defaultConstructor: dynamic, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array? @@ -21,7 +22,7 @@ internal fun setMetadataFor( """) } - val metadata = metadataConstructor(name, associatedObjectKey, associatedObjects, suspendArity ?: js("[]")) + val metadata = metadataConstructor(name, defaultConstructor, associatedObjectKey, associatedObjects, suspendArity ?: js("[]")) ctor.`$metadata$` = metadata if (interfaces != null) { @@ -45,16 +46,34 @@ private fun generateInterfaceId(): Int { } -internal fun interfaceMeta(name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array?): Metadata { - return createMetadata("interface", name, associatedObjectKey, associatedObjects, suspendArity, generateInterfaceId()) +internal fun interfaceMeta( + name: String?, + defaultConstructor: dynamic, + associatedObjectKey: Number?, + associatedObjects: dynamic, + suspendArity: Array? +): Metadata { + return createMetadata("interface", name, defaultConstructor, associatedObjectKey, associatedObjects, suspendArity, generateInterfaceId()) } -internal fun objectMeta(name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array?): Metadata { - return createMetadata("object", name, associatedObjectKey, associatedObjects, suspendArity, null) +internal fun objectMeta( + name: String?, + defaultConstructor: dynamic, + associatedObjectKey: Number?, + associatedObjects: dynamic, + suspendArity: Array? +): Metadata { + return createMetadata("object", name, defaultConstructor, associatedObjectKey, associatedObjects, suspendArity, null) } -internal fun classMeta(name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array?): Metadata { - return createMetadata("class", name, associatedObjectKey, associatedObjects, suspendArity, null) +internal fun classMeta( + name: String?, + defaultConstructor: dynamic, + associatedObjectKey: Number?, + associatedObjects: dynamic, + suspendArity: Array? +): Metadata { + return createMetadata("class", name, defaultConstructor, associatedObjectKey, associatedObjects, suspendArity, null) } // Seems like we need to disable this check if variables are used inside js annotation @@ -62,6 +81,7 @@ internal fun classMeta(name: String?, associatedObjectKey: Number?, associatedOb private fun createMetadata( kind: String, name: String?, + defaultConstructor: dynamic, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array?, @@ -75,6 +95,7 @@ private fun createMetadata( associatedObjects: associatedObjects, suspendArity: suspendArity, ${'$'}kClass$: undef, + defaultConstructor: defaultConstructor, iid: iid })""") } @@ -90,6 +111,7 @@ internal external interface Metadata { val iid: Int? var `$kClass$`: dynamic + val defaultConstructor: dynamic var errorInfo: Int? // Bits set for overridden properties: "message" => 0x1, "cause" => 0x2 } \ No newline at end of file diff --git a/libraries/stdlib/js-ir/runtime/reflectRuntime.kt b/libraries/stdlib/js-ir/runtime/reflectRuntime.kt index c2d01a8416f..e06a00b7a75 100644 --- a/libraries/stdlib/js-ir/runtime/reflectRuntime.kt +++ b/libraries/stdlib/js-ir/runtime/reflectRuntime.kt @@ -44,7 +44,7 @@ private fun getKPropMetadata(paramCount: Int, setter: Any?): dynamic { } private fun metadataObject(): Metadata { - return classMeta(VOID, VOID, VOID, VOID) + return classMeta(VOID, VOID, VOID, VOID, VOID) } private val propertyRefClassMetadataCache: Array> = arrayOf>( diff --git a/libraries/stdlib/js-ir/src/kotlin/reflect/createInstance.kt b/libraries/stdlib/js-ir/src/kotlin/reflect/createInstance.kt new file mode 100644 index 00000000000..ed340960c30 --- /dev/null +++ b/libraries/stdlib/js-ir/src/kotlin/reflect/createInstance.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2010-2023 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 + +/** + * Creates a new instance of the class, calling a constructor which either has no parameters or all parameters of which have + * a default value. If there are no or many such constructors, an exception is thrown. + */ +@OptIn(JsIntrinsic::class) +@SinceKotlin("1.9") +@ExperimentalJsReflectionCreateInstance +public fun KClass.createInstance(): T { + val jsClass = js.asDynamic() + + if (jsClass === js("Object")) return js("{}") + + val noArgsConstructor = jsClass.`$metadata$`.unsafeCast()?.defaultConstructor + ?: throw IllegalArgumentException("Class \"$simpleName\" should have a single no-arg constructor") + + return if (jsIsEs6() && noArgsConstructor !== jsClass) { + js("noArgsConstructor.call(jsClass)") + } else { + js("new noArgsConstructor()") + } +} diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index 9eee8ad04fb..29d7dfc97ac 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -3387,6 +3387,9 @@ public abstract interface annotation class kotlin/js/ExperimentalJsExport : java public abstract interface annotation class kotlin/js/ExperimentalJsFileName : java/lang/annotation/Annotation { } +public abstract interface annotation class kotlin/js/ExperimentalJsReflectionCreateInstance : java/lang/annotation/Annotation { +} + public final class kotlin/jvm/JvmClassMappingKt { public static final fun getAnnotationClass (Ljava/lang/annotation/Annotation;)Lkotlin/reflect/KClass; public static final fun getJavaClass (Ljava/lang/Object;)Ljava/lang/Class;