[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
}
}