[K/JS] Support KClass<*>.createInstance reflection method ^KT-58684 Fixed

This commit is contained in:
Artem Kobzar
2023-07-06 18:18:14 +00:00
committed by Space Team
parent ca0b061675
commit cf949e8760
26 changed files with 453 additions and 46 deletions
@@ -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")
@@ -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<Lowering>(
initializersLoweringPhase,
initializersCleanupLoweringPhase,
kotlinNothingValueExceptionPhase,
collectClassDefaultConstructorsPhase,
// Common prefix ends
enumWhenPhase,
enumEntryInstancesLoweringPhase,
@@ -12,6 +12,8 @@ import org.jetbrains.kotlin.ir.declarations.*
import java.util.WeakHashMap
class JsMapping : DefaultMapping() {
val classToItsDefaultConstructor = WeakHashMap<IrClass, IrConstructor>()
val esClassWhichNeedBoxParameters = DefaultDelegateFactory.newDeclarationToValueMapping<IrClass, Boolean>()
val esClassToPossibilityForOptimization = DefaultDelegateFactory.newDeclarationToValueMapping<IrClass, MutableReference<Boolean>>()
@@ -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) {
@@ -139,6 +139,10 @@ abstract class UsefulDeclarationProcessor(
val usefulPolyfilledDeclarations = hashSetOf<IrDeclaration>()
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)
}
}
@@ -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<IrClassSymbol> {
@@ -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<IrDeclaration>? {
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 }
}
}
@@ -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
}
}
}
@@ -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 {
@@ -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())
}
}
@@ -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)
@@ -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
}
}
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 <reified T : Any> test() {
val instance = T::class.createInstance()
assertTrue(instance is T)
}
inline fun <reified T : Any> testFail() {
try {
T::class.createInstance()
fail("createInstance should have failed on ${T::class}")
} catch (e: Exception) {
// OK
}
}
fun box(): String {
test<Any>()
test<Simple>()
test<PrimaryWithDefaults>()
test<Secondary>()
test<SecondaryWithDefaults>()
test<SecondaryWithDefaultsNoPrimary>()
testFail<NoNoArgConstructor>()
testFail<NoArgAndDefault>()
testFail<DefaultPrimaryAndDefaultSecondary>()
testFail<SeveralDefaultSecondaries>()
testFail<PrivateConstructor>()
testFail<Object>()
testFail<Interface>()
testFail<EnumFoo>()
testFail<AbstractClass>()
testFail<SealedClass>()
return "OK"
}
@@ -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 <T : Any> testInstance(x: T) {
val kclass = x::class
val anotherInstance = kclass.createInstance()
assertTrue(kclass.isInstance(x) && kclass.isInstance(anotherInstance))
}
inline fun <T : Any> 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"
}
+9
View File
@@ -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 <T> parse(text: kotlin.String): T
@@ -7,6 +7,10 @@ public inline fun <reified T> typeOf(): kotlin.reflect.KType
@kotlin.internal.LowPriorityInOverloadResolution
public fun <T : kotlin.Any> kotlin.reflect.KClass<T>.cast(value: kotlin.Any?): T
@kotlin.SinceKotlin(version = "1.9")
@kotlin.js.ExperimentalJsReflectionCreateInstance
public fun <T : kotlin.Any> kotlin.reflect.KClass<T>.createInstance(): T
@kotlin.reflect.ExperimentalAssociatedObjects
public inline fun <reified T : kotlin.Annotation> kotlin.reflect.KClass<*>.findAssociatedObject(): kotlin.Any?
@@ -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
+1 -1
View File
@@ -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
@@ -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
internal fun jsContextfulRef(context: dynamic, fn: dynamic): dynamic
@JsIntrinsic
internal fun jsIsEs6(): Boolean
@@ -7,9 +7,10 @@ package kotlin.js
internal fun setMetadataFor(
ctor: Ctor,
name: String?,
metadataConstructor: (name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array<Int>?) -> Metadata,
metadataConstructor: (name: String?, defaultConstructor: dynamic, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array<Int>?) -> Metadata,
parent: Ctor?,
interfaces: Array<dynamic>?,
defaultConstructor: dynamic,
associatedObjectKey: Number?,
associatedObjects: dynamic,
suspendArity: Array<Int>?
@@ -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<Int>?): Metadata {
return createMetadata("interface", name, associatedObjectKey, associatedObjects, suspendArity, generateInterfaceId())
internal fun interfaceMeta(
name: String?,
defaultConstructor: dynamic,
associatedObjectKey: Number?,
associatedObjects: dynamic,
suspendArity: Array<Int>?
): Metadata {
return createMetadata("interface", name, defaultConstructor, associatedObjectKey, associatedObjects, suspendArity, generateInterfaceId())
}
internal fun objectMeta(name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array<Int>?): Metadata {
return createMetadata("object", name, associatedObjectKey, associatedObjects, suspendArity, null)
internal fun objectMeta(
name: String?,
defaultConstructor: dynamic,
associatedObjectKey: Number?,
associatedObjects: dynamic,
suspendArity: Array<Int>?
): Metadata {
return createMetadata("object", name, defaultConstructor, associatedObjectKey, associatedObjects, suspendArity, null)
}
internal fun classMeta(name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array<Int>?): Metadata {
return createMetadata("class", name, associatedObjectKey, associatedObjects, suspendArity, null)
internal fun classMeta(
name: String?,
defaultConstructor: dynamic,
associatedObjectKey: Number?,
associatedObjects: dynamic,
suspendArity: Array<Int>?
): 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<Int>?,
@@ -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
}
@@ -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<Array<dynamic>> = arrayOf<Array<dynamic>>(
@@ -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 <T : Any> KClass<T>.createInstance(): T {
val jsClass = js.asDynamic()
if (jsClass === js("Object")) return js("{}")
val noArgsConstructor = jsClass.`$metadata$`.unsafeCast<Metadata?>()?.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()")
}
}
@@ -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;