[K/JS] Make interface subtyping faster and lighter.
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2010-2022 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.js
|
||||
|
||||
internal fun implement(vararg interfaces: dynamic): BitMask {
|
||||
var maxSize = 1
|
||||
val masks = js("[]")
|
||||
|
||||
for (i in interfaces) {
|
||||
var currentSize = maxSize
|
||||
val imask: BitMask? = i.prototype.`$imask$` ?: i.`$imask$`
|
||||
|
||||
if (imask != null) {
|
||||
masks.push(imask)
|
||||
currentSize = imask.intArray.size
|
||||
}
|
||||
|
||||
val iid: Int? = i.`$metadata$`.iid
|
||||
val iidImask: BitMask? = iid?.let { BitMask(arrayOf(it)) }
|
||||
|
||||
if (iidImask != null) {
|
||||
masks.push(iidImask)
|
||||
currentSize = JsMath.max(currentSize, iidImask.intArray.size)
|
||||
}
|
||||
|
||||
if (currentSize > maxSize) {
|
||||
maxSize = currentSize
|
||||
}
|
||||
}
|
||||
|
||||
val resultIntArray = IntArray(maxSize) { i ->
|
||||
masks.reduce({ acc: Int, it: BitMask ->
|
||||
if (i >= it.intArray.size)
|
||||
acc
|
||||
else
|
||||
acc or it.intArray[i]
|
||||
}, 0)
|
||||
}
|
||||
|
||||
val result = BitMask(emptyArray())
|
||||
result.intArray = resultIntArray
|
||||
return result
|
||||
}
|
||||
|
||||
internal class BitMask(activeBits: Array<Int>) {
|
||||
var intArray: IntArray = run {
|
||||
if (activeBits.size == 0) {
|
||||
IntArray(0)
|
||||
} else {
|
||||
val max: Int = JsMath.asDynamic().max.apply(null, activeBits)
|
||||
val intArray = IntArray((max shr 5) + 1)
|
||||
for (activeBit in activeBits) {
|
||||
val numberIndex = activeBit shr 5
|
||||
val positionInNumber = activeBit and 31
|
||||
val numberWithSettledBit = 1 shl positionInNumber
|
||||
intArray[numberIndex] = intArray[numberIndex] or numberWithSettledBit
|
||||
}
|
||||
intArray
|
||||
}
|
||||
}
|
||||
|
||||
fun isBitSet(possibleActiveBit: Int): Boolean {
|
||||
val numberIndex = possibleActiveBit shr 5
|
||||
if (numberIndex > intArray.size) return false
|
||||
val positionInNumber = possibleActiveBit and 31
|
||||
val numberWithSettledBit = 1 shl positionInNumber
|
||||
return intArray[numberIndex] and numberWithSettledBit != 0
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
package kotlin.js
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@PublishedApi
|
||||
internal fun <T : Enum<T>> enumValuesIntrinsic(): Array<T> =
|
||||
throw IllegalStateException("Should be replaced by compiler")
|
||||
@@ -51,3 +53,6 @@ internal annotation class JsFun(val code: String)
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
internal annotation class JsImplicitExport()
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
internal annotation class JsSubtypeCheckable(vararg val implements: KClass<*>)
|
||||
@@ -7,44 +7,45 @@ package kotlin.js
|
||||
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
internal fun getPropertyCallableRef(name: String, paramCount: Int, type: dynamic, getter: dynamic, setter: dynamic): KProperty<*> {
|
||||
internal fun getPropertyCallableRef(
|
||||
name: String,
|
||||
paramCount: Int,
|
||||
superType: dynamic,
|
||||
getter: dynamic,
|
||||
setter: dynamic
|
||||
): KProperty<*> {
|
||||
getter.get = getter
|
||||
getter.set = setter
|
||||
getter.callableName = name
|
||||
return getPropertyRefClass(getter, getKPropMetadata(paramCount, setter, type)).unsafeCast<KProperty<*>>()
|
||||
return getPropertyRefClass(
|
||||
getter,
|
||||
getKPropMetadata(paramCount, setter),
|
||||
getInterfaceMaskFor(getter, superType)
|
||||
).unsafeCast<KProperty<*>>()
|
||||
}
|
||||
|
||||
internal fun getLocalDelegateReference(name: String, type: dynamic, mutable: Boolean, lambda: dynamic): KProperty<*> {
|
||||
return getPropertyCallableRef(name, 0, type, lambda, if (mutable) lambda else null)
|
||||
internal fun getLocalDelegateReference(name: String, superType: dynamic, mutable: Boolean, lambda: dynamic): KProperty<*> {
|
||||
return getPropertyCallableRef(name, 0, superType, lambda, if (mutable) lambda else null)
|
||||
}
|
||||
|
||||
private fun getPropertyRefClass(obj: Ctor, metadata: Metadata): dynamic {
|
||||
obj.`$metadata$` = metadata;
|
||||
obj.constructor = obj;
|
||||
private fun getPropertyRefClass(obj: Ctor, metadata: Metadata, imask: BitMask): dynamic {
|
||||
obj.`$metadata$` = metadata
|
||||
obj.constructor = obj
|
||||
obj.`$imask$` = imask
|
||||
return obj;
|
||||
}
|
||||
|
||||
private fun getKPropMetadata(paramCount: Int, setter: Any?, type: dynamic): dynamic {
|
||||
val mdata: Metadata = propertyRefClassMetadataCache[paramCount][if (setter == null) 0 else 1]
|
||||
private fun getInterfaceMaskFor(obj: Ctor, superType: dynamic): BitMask =
|
||||
obj.`$imask$` ?: implement(superType)
|
||||
|
||||
if (mdata.interfaces.size == 0) {
|
||||
mdata.interfaces.asDynamic().push(type)
|
||||
|
||||
if (mdata.interfacesCache == null) {
|
||||
mdata.interfacesCache = generateInterfaceCache()
|
||||
} else {
|
||||
mdata.interfacesCache!!.isComplete = false
|
||||
}
|
||||
|
||||
mdata.interfacesCache!!.extendCacheWithSingle(type)
|
||||
}
|
||||
|
||||
return mdata
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun getKPropMetadata(paramCount: Int, setter: Any?): dynamic {
|
||||
return propertyRefClassMetadataCache[paramCount][if (setter == null) 0 else 1]
|
||||
}
|
||||
|
||||
private fun metadataObject(): Metadata {
|
||||
val undef = js("undefined")
|
||||
return classMeta(undef, undef, undef, undef, undef, undef)
|
||||
return classMeta(undef, undef, undef, undef)
|
||||
}
|
||||
|
||||
private val propertyRefClassMetadataCache: Array<Array<dynamic>> = arrayOf<Array<dynamic>>(
|
||||
|
||||
@@ -5,36 +5,42 @@
|
||||
|
||||
package kotlin.js
|
||||
|
||||
internal fun interfaceMeta(
|
||||
internal fun setMetadataFor(
|
||||
ctor: Ctor,
|
||||
name: String?,
|
||||
interfaces: Array<Ctor>?,
|
||||
metadataConstructor: (name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array<Int>?) -> Metadata,
|
||||
parent: Ctor?,
|
||||
interfaces: Array<dynamic>?,
|
||||
associatedObjectKey: Number?,
|
||||
associatedObjects: dynamic,
|
||||
suspendArity: Array<Int>?,
|
||||
): Metadata {
|
||||
return createMetadata("interface", name, interfaces, associatedObjectKey, associatedObjects, suspendArity, js("undefined"))
|
||||
suspendArity: Array<Int>?
|
||||
) {
|
||||
if (parent != null) {
|
||||
js("""
|
||||
ctor.prototype = Object.create(parent.prototype)
|
||||
ctor.prototype.constructor = ctor;
|
||||
""")
|
||||
}
|
||||
|
||||
val metadata = metadataConstructor(name, associatedObjectKey, associatedObjects, suspendArity)
|
||||
ctor.`$metadata$` = metadata
|
||||
|
||||
if (interfaces != null) {
|
||||
val receiver = if (metadata.iid != null) ctor else ctor.prototype
|
||||
receiver.`$imask$` = implement(*interfaces)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun objectMeta(
|
||||
name: String?,
|
||||
interfaces: Array<Ctor>?,
|
||||
associatedObjectKey: Number?,
|
||||
associatedObjects: dynamic,
|
||||
suspendArity: Array<Int>?,
|
||||
fastPrototype: Prototype?,
|
||||
): Metadata {
|
||||
return createMetadata("object", name, interfaces, associatedObjectKey, associatedObjects, suspendArity, fastPrototype)
|
||||
internal fun interfaceMeta(name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array<Int>?): Metadata {
|
||||
return createMetadata("interface", name, associatedObjectKey, associatedObjects, suspendArity, generateInterfaceId())
|
||||
}
|
||||
|
||||
internal fun classMeta(
|
||||
name: String?,
|
||||
interfaces: Array<Ctor>?,
|
||||
associatedObjectKey: Number?,
|
||||
associatedObjects: dynamic,
|
||||
suspendArity: Array<Int>?,
|
||||
fastPrototype: Prototype?,
|
||||
): Metadata {
|
||||
return createMetadata("class", name, interfaces, associatedObjectKey, associatedObjects, suspendArity, fastPrototype)
|
||||
internal fun objectMeta(name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array<Int>?): Metadata {
|
||||
return createMetadata("object", name, 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)
|
||||
}
|
||||
|
||||
// Seems like we need to disable this check if variables are used inside js annotation
|
||||
@@ -42,192 +48,103 @@ internal fun classMeta(
|
||||
private fun createMetadata(
|
||||
kind: String,
|
||||
name: String?,
|
||||
interfaces: Array<Ctor>?,
|
||||
associatedObjectKey: Number?,
|
||||
associatedObjects: dynamic,
|
||||
suspendArity: Array<Int>?,
|
||||
fastPrototype: Prototype?
|
||||
iid: Int?
|
||||
): Metadata {
|
||||
return js("""({
|
||||
kind: kind,
|
||||
simpleName: name,
|
||||
interfaceId: kind === "interface" ? -1 : undefined,
|
||||
interfaces: interfaces || [],
|
||||
associatedObjectKey: associatedObjectKey,
|
||||
associatedObjects: associatedObjects,
|
||||
suspendArity: suspendArity,
|
||||
fastPrototype: fastPrototype,
|
||||
${'$'}kClass$: undefined,
|
||||
interfacesCache: {
|
||||
isComplete: fastPrototype === undefined && (interfaces === undefined || interfaces.length === 0),
|
||||
implementInterfaceMemo: {}
|
||||
}
|
||||
iid: iid
|
||||
})""")
|
||||
}
|
||||
|
||||
internal external interface Metadata {
|
||||
val kind: String
|
||||
val interfaces: Array<Ctor>
|
||||
// This field gives fast access to the prototype of metadata owner (Object.getPrototypeOf())
|
||||
// Can be pre-initialized or lazy initialized and then should be immutable
|
||||
val simpleName: String?
|
||||
val associatedObjectKey: Number?
|
||||
val associatedObjects: dynamic
|
||||
var interfaceId: Number?
|
||||
var fastPrototype: Prototype?
|
||||
val suspendArity: Array<Int>?
|
||||
val iid: Int?
|
||||
|
||||
var `$kClass$`: dynamic
|
||||
// This is an object for memoization of a isInterfaceImpl function
|
||||
// Can be mutated quite often
|
||||
var interfacesCache: IsImplementsCache?
|
||||
}
|
||||
|
||||
// This is a flag for memoization of a isInterfaceImpl function
|
||||
internal external interface IsImplementsCache {
|
||||
var isComplete: Boolean
|
||||
val implementInterfaceMemo: dynamic
|
||||
}
|
||||
|
||||
internal external interface Ctor {
|
||||
var `$metadata$`: Metadata?
|
||||
var `$imask$`: BitMask?
|
||||
var `$metadata$`: Metadata
|
||||
var constructor: Ctor?
|
||||
val prototype: Prototype?
|
||||
val prototype: dynamic
|
||||
}
|
||||
|
||||
internal external interface Prototype {
|
||||
val constructor: Ctor?
|
||||
}
|
||||
private var iid: Int? = null
|
||||
|
||||
private var interfacesCounter = 0
|
||||
|
||||
private fun Ctor.getOrDefineInterfaceId(): Number? {
|
||||
val metadata = `$metadata$`.unsafeCast<Metadata>()
|
||||
val interfaceId = metadata.interfaceId ?: -1
|
||||
return if (interfaceId != -1) {
|
||||
interfaceId
|
||||
@Suppress("SMARTCAST_IMPOSSIBLE")
|
||||
internal fun generateInterfaceId(): Int {
|
||||
if (iid == null) {
|
||||
iid = 1
|
||||
} else {
|
||||
val result = interfacesCounter++
|
||||
metadata.interfaceId = result
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
private fun Ctor.getPrototype() = prototype?.let { js("Object").getPrototypeOf(it).unsafeCast<Prototype>() }
|
||||
|
||||
internal fun IsImplementsCache.extendCacheWith(cache: IsImplementsCache?) {
|
||||
val anotherInterfaceMemo = cache?.implementInterfaceMemo ?: return
|
||||
js("Object").assign(implementInterfaceMemo, anotherInterfaceMemo)
|
||||
}
|
||||
|
||||
internal fun IsImplementsCache.extendCacheWithSingle(intr: Ctor) {
|
||||
implementInterfaceMemo[intr.getOrDefineInterfaceId()] = true
|
||||
}
|
||||
|
||||
private fun fastGetPrototype(ctor: Ctor): Prototype? {
|
||||
return ctor.`$metadata$`?.run {
|
||||
if (fastPrototype == null) {
|
||||
fastPrototype = ctor.getPrototype()
|
||||
}
|
||||
fastPrototype
|
||||
} ?: ctor.getPrototype()
|
||||
}
|
||||
|
||||
private fun completeInterfaceCache(ctor: Ctor): IsImplementsCache? {
|
||||
val metadata = ctor.`$metadata$`
|
||||
|
||||
if (metadata != null && metadata.interfacesCache == null) {
|
||||
metadata.interfacesCache = generateInterfaceCache()
|
||||
iid += 1
|
||||
}
|
||||
|
||||
val interfacesCache = metadata?.interfacesCache
|
||||
|
||||
if (interfacesCache != null) {
|
||||
if (interfacesCache.isComplete == true) {
|
||||
return interfacesCache
|
||||
}
|
||||
|
||||
for (i in metadata.interfaces) {
|
||||
interfacesCache.extendCacheWithSingle(i)
|
||||
interfacesCache.extendCacheWith(completeInterfaceCache(i))
|
||||
}
|
||||
}
|
||||
|
||||
val parentInterfacesCache = fastGetPrototype(ctor)?.constructor?.let(::completeInterfaceCache)
|
||||
|
||||
return interfacesCache?.apply {
|
||||
extendCacheWith(parentInterfacesCache)
|
||||
isComplete = true
|
||||
} ?: parentInterfacesCache
|
||||
return iid
|
||||
}
|
||||
|
||||
// Old JS Backend
|
||||
internal fun generateInterfaceCache(): IsImplementsCache {
|
||||
return js("{ isComplete: false, implementInterfaceMemo: {} }")
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun getPrototypeOf(obj: dynamic) =
|
||||
js("Object.getPrototypeOf(obj)")
|
||||
|
||||
private fun searchForMetadata(obj: dynamic): Metadata? {
|
||||
if (obj == null) {
|
||||
return null
|
||||
}
|
||||
var metadata: Metadata? = obj.`$metadata$`
|
||||
var currentObject = getPrototypeOf(obj)
|
||||
|
||||
while (metadata == null && currentObject != null) {
|
||||
val currentConstructor = currentObject.constructor
|
||||
metadata = currentConstructor.`$metadata$`
|
||||
currentObject = getPrototypeOf(currentObject)
|
||||
}
|
||||
|
||||
return metadata
|
||||
}
|
||||
|
||||
private fun isInterfaceImpl(ctor: Ctor, iface: Ctor): Boolean {
|
||||
if (ctor === iface) {
|
||||
return true
|
||||
// TODO: Remove after 1.8 bootstrapping
|
||||
// Needed to pass all nodejs tests, because of stdlib compilation via bootstrapped compiler with old metadata format
|
||||
private fun verySlowIsInterfaceImpl(obj: dynamic, iface: dynamic): Boolean {
|
||||
val metadata = searchForMetadata(obj) ?: return false
|
||||
val interfaces = metadata.asDynamic().associatedObjectKey
|
||||
|
||||
if (
|
||||
interfaces != null &&
|
||||
(interfaces.indexOf(iface) != -1 || interfaces.some { x -> verySlowIsInterfaceImpl(x, iface) })
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
val metadata = ctor.`$metadata$`
|
||||
return verySlowIsInterfaceImpl(getPrototypeOf(obj), iface)
|
||||
}
|
||||
|
||||
if (metadata != null && metadata.interfacesCache == null) {
|
||||
metadata.interfacesCache = generateInterfaceCache()
|
||||
}
|
||||
|
||||
val interfacesCache = metadata?.interfacesCache
|
||||
|
||||
return if (interfacesCache != null) {
|
||||
if (!interfacesCache.isComplete) completeInterfaceCache(ctor)
|
||||
val interfaceId = iface.`$metadata$`?.interfaceId ?: return false
|
||||
!!interfacesCache.implementInterfaceMemo[interfaceId]
|
||||
} else {
|
||||
val constructor = fastGetPrototype(ctor)?.constructor ?: return false
|
||||
isInterfaceImpl(constructor, iface)
|
||||
}
|
||||
private fun isInterfaceImpl(obj: dynamic, iface: Int): Boolean {
|
||||
val mask: BitMask = obj.`$imask$`.unsafeCast<BitMask?>() ?: return false
|
||||
return mask.isBitSet(iface)
|
||||
}
|
||||
|
||||
internal fun isInterface(obj: dynamic, iface: dynamic): Boolean {
|
||||
val ctor = obj.constructor ?: return false
|
||||
return isInterfaceImpl(ctor, iface)
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
internal interface ClassMetadata {
|
||||
val simpleName: String
|
||||
val interfaces: Array<dynamic>
|
||||
}
|
||||
|
||||
// TODO: replace `isInterface` with the following
|
||||
public fun isInterface(ctor: dynamic, IType: dynamic): Boolean {
|
||||
if (ctor === IType) return true
|
||||
|
||||
val metadata = ctor.`$metadata$`.unsafeCast<ClassMetadata?>()
|
||||
|
||||
if (metadata !== null) {
|
||||
val interfaces = metadata.interfaces
|
||||
for (i in interfaces) {
|
||||
if (isInterface(i, IType)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return if (obj.`$imask$` != null) {
|
||||
isInterfaceImpl(obj, iface.`$metadata$`.iid)
|
||||
} else {
|
||||
verySlowIsInterfaceImpl(obj, iface)
|
||||
}
|
||||
|
||||
var superPrototype = ctor.prototype
|
||||
if (superPrototype !== null) {
|
||||
superPrototype = js("Object.getPrototypeOf(superPrototype)")
|
||||
}
|
||||
|
||||
val superConstructor = if (superPrototype !== null) {
|
||||
superPrototype.constructor
|
||||
} else null
|
||||
|
||||
return superConstructor != null && isInterface(superConstructor, IType)
|
||||
}
|
||||
*/
|
||||
|
||||
internal fun isSuspendFunction(obj: dynamic, arity: Int): Boolean {
|
||||
if (jsTypeOf(obj) == "function") {
|
||||
@@ -237,7 +154,7 @@ internal fun isSuspendFunction(obj: dynamic, arity: Int): Boolean {
|
||||
|
||||
if (jsTypeOf(obj) == "object" && jsIn("${'$'}metadata${'$'}", obj.constructor)) {
|
||||
@Suppress("IMPLICIT_BOXING_IN_IDENTITY_EQUALS")
|
||||
return obj.constructor.unsafeCast<Ctor>().`$metadata$`?.suspendArity?.let {
|
||||
return obj.constructor.unsafeCast<Ctor>().`$metadata$`.suspendArity?.let {
|
||||
var result = false
|
||||
for (item in it) {
|
||||
if (arity == item) {
|
||||
@@ -288,7 +205,6 @@ internal fun isFloatArray(a: dynamic): Boolean = jsInstanceOf(a, js("Float32Arra
|
||||
internal fun isDoubleArray(a: dynamic): Boolean = jsInstanceOf(a, js("Float64Array"))
|
||||
internal fun isLongArray(a: dynamic): Boolean = isJsArray(a) && a.`$type$` === "LongArray"
|
||||
|
||||
|
||||
internal fun jsGetPrototypeOf(jsClass: dynamic) = js("Object").getPrototypeOf(jsClass)
|
||||
|
||||
internal fun jsIsType(obj: dynamic, jsClass: dynamic): Boolean {
|
||||
@@ -320,8 +236,9 @@ internal fun jsIsType(obj: dynamic, jsClass: dynamic): Boolean {
|
||||
return jsInstanceOf(obj, jsClass)
|
||||
}
|
||||
|
||||
if (klassMetadata.kind === "interface" && obj.constructor != null) {
|
||||
return isInterfaceImpl(obj.constructor, jsClass)
|
||||
if (klassMetadata.kind === "interface") {
|
||||
val iid = klassMetadata.iid.unsafeCast<Int?>()
|
||||
return iid?.let { isInterfaceImpl(obj, it) } ?: verySlowIsInterfaceImpl(obj, constructor)
|
||||
}
|
||||
|
||||
return false
|
||||
@@ -329,14 +246,16 @@ internal fun jsIsType(obj: dynamic, jsClass: dynamic): Boolean {
|
||||
|
||||
internal fun isNumber(a: dynamic) = jsTypeOf(a) == "number" || a is Long
|
||||
|
||||
@OptIn(JsIntrinsic::class)
|
||||
internal fun isComparable(value: dynamic): Boolean {
|
||||
var type = jsTypeOf(value)
|
||||
|
||||
return type == "string" ||
|
||||
type == "boolean" ||
|
||||
isNumber(value) ||
|
||||
isInterface(value, Comparable::class.js)
|
||||
isInterface(value, jsClassIntrinsic<Comparable<*>>())
|
||||
}
|
||||
|
||||
@OptIn(JsIntrinsic::class)
|
||||
internal fun isCharSequence(value: dynamic): Boolean =
|
||||
jsTypeOf(value) == "string" || isInterface(value, CharSequence::class.js)
|
||||
jsTypeOf(value) == "string" || isInterface(value, jsClassIntrinsic<CharSequence>())
|
||||
|
||||
@@ -9,8 +9,8 @@ import kotlin.reflect.js.internal.*
|
||||
@PublishedApi
|
||||
internal fun <T : Annotation> KClass<*>.findAssociatedObject(annotationClass: KClass<T>): Any? {
|
||||
return if (this is KClassImpl<*> && annotationClass is KClassImpl<T>) {
|
||||
val key = annotationClass.jClass.asDynamic().unsafeCast<Ctor>().`$metadata$`?.associatedObjectKey?.unsafeCast<Int>() ?: return null
|
||||
val map = jClass.asDynamic().unsafeCast<Ctor>().`$metadata$`?.associatedObjects ?: return null
|
||||
val key = annotationClass.jClass.asDynamic().`$metadata$`?.associatedObjectKey?.unsafeCast<Int>() ?: return null
|
||||
val map = jClass.asDynamic().`$metadata$`?.associatedObjects ?: return null
|
||||
val factory = map[key] ?: return null
|
||||
return factory()
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user