[Native][IR] Move verbose RTTI for local classes and anonymous objects
^KT-45304
This commit is contained in:
+12
@@ -258,6 +258,18 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
|
||||
lazyValues[member] = newValue
|
||||
}
|
||||
|
||||
private val localClassNames = mutableMapOf<IrAttributeContainer, String>()
|
||||
|
||||
fun getLocalClassName(container: IrAttributeContainer): String? = localClassNames[container.attributeOwnerId]
|
||||
|
||||
fun putLocalClassName(container: IrAttributeContainer, name: String) {
|
||||
localClassNames[container.attributeOwnerId] = name
|
||||
}
|
||||
|
||||
fun copyLocalClassName(source: IrAttributeContainer, destination: IrAttributeContainer) {
|
||||
getLocalClassName(source)?.let { name -> putLocalClassName(destination, name) }
|
||||
}
|
||||
|
||||
val reflectionTypes: KonanReflectionTypes by lazy(PUBLICATION) {
|
||||
KonanReflectionTypes(moduleDescriptor, KonanFqNames.internalPackageName)
|
||||
}
|
||||
|
||||
+8
-2
@@ -121,6 +121,12 @@ internal val sharedVariablesPhase = makeKonanModuleLoweringPhase(
|
||||
prerequisite = setOf(lateinitPhase)
|
||||
)
|
||||
|
||||
internal val inventNamesForLocalClasses = makeKonanModuleLoweringPhase(
|
||||
::NativeInventNamesForLocalClasses,
|
||||
name = "InventNamesForLocalClasses",
|
||||
description = "Invent names for local classes and anonymous objects"
|
||||
)
|
||||
|
||||
internal val extractLocalClassesFromInlineBodies = NamedCompilerPhase(
|
||||
lower = object : SameTypeCompilerPhase<Context, IrModuleFragment> {
|
||||
override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState<IrModuleFragment>, context: Context, input: IrModuleFragment): IrModuleFragment {
|
||||
@@ -138,7 +144,7 @@ internal val extractLocalClassesFromInlineBodies = NamedCompilerPhase(
|
||||
},
|
||||
name = "ExtractLocalClassesFromInlineBodies",
|
||||
description = "Extraction of local classes from inline bodies",
|
||||
prerequisite = setOf(sharedVariablesPhase),
|
||||
prerequisite = setOf(sharedVariablesPhase), // TODO: add "soft" dependency on inventNamesForLocalClasses
|
||||
nlevels = 0,
|
||||
actions = modulePhaseActions
|
||||
)
|
||||
@@ -217,7 +223,7 @@ internal val localFunctionsPhase = makeKonanFileOpPhase(
|
||||
},
|
||||
name = "LocalFunctions",
|
||||
description = "Local function lowering",
|
||||
prerequisite = setOf(sharedVariablesPhase)
|
||||
prerequisite = setOf(sharedVariablesPhase) // TODO: add "soft" dependency on inventNamesForLocalClasses
|
||||
)
|
||||
|
||||
internal val tailrecPhase = makeKonanFileLoweringPhase(
|
||||
|
||||
+1
@@ -192,6 +192,7 @@ internal val allLoweringsPhase = NamedCompilerPhase(
|
||||
arrayConstructorPhase then
|
||||
lateinitPhase then
|
||||
sharedVariablesPhase then
|
||||
inventNamesForLocalClasses then
|
||||
extractLocalClassesFromInlineBodies then
|
||||
inlinePhase then
|
||||
provisionalFunctionExpressionPhase then
|
||||
|
||||
+44
-19
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.backend.konan.Context
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.*
|
||||
import org.jetbrains.kotlin.backend.konan.ir.*
|
||||
import org.jetbrains.kotlin.backend.konan.isExternalObjCClassMethod
|
||||
import org.jetbrains.kotlin.backend.konan.lower.FunctionReferenceLowering.Companion.isLoweredFunctionReference
|
||||
import org.jetbrains.kotlin.builtins.PrimitiveType
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.symbols.isPublicApi
|
||||
@@ -253,7 +254,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
||||
interfaceTableSize, interfaceTablePtr,
|
||||
reflectionInfo.packageName,
|
||||
reflectionInfo.relativeName,
|
||||
flagsFromClass(irClass),
|
||||
flagsFromClass(irClass) or reflectionInfo.reflectionFlags,
|
||||
context.getLayoutBuilder(irClass).classId,
|
||||
llvmDeclarations.writableTypeInfoGlobal?.pointer,
|
||||
associatedObjects = genAssociatedObjects(irClass)
|
||||
@@ -496,8 +497,6 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
||||
val objOffsetsPtr = staticData.placeGlobalConstArray("", int32Type, objOffsets)
|
||||
val objOffsetsCount = objOffsets.size
|
||||
|
||||
val reflectionInfo = ReflectionInfo(null, null)
|
||||
|
||||
val writableTypeInfoType = runtime.writableTypeInfoType
|
||||
val writableTypeInfo = if (writableTypeInfoType == null) {
|
||||
null
|
||||
@@ -541,8 +540,8 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
||||
objOffsets = objOffsetsPtr, objOffsetsCount = objOffsetsCount,
|
||||
interfaces = interfacesPtr, interfacesCount = interfaces.size,
|
||||
interfaceTableSize = interfaceTableSize, interfaceTable = interfaceTablePtr,
|
||||
packageName = reflectionInfo.packageName,
|
||||
relativeName = reflectionInfo.relativeName,
|
||||
packageName = ReflectionInfo.EMPTY.packageName,
|
||||
relativeName = ReflectionInfo.EMPTY.relativeName,
|
||||
flags = flagsFromClass(irClass) or (if (immutable) TF_IMMUTABLE else 0),
|
||||
classId = typeHierarchyInfo.classIdLo,
|
||||
writableTypeInfo = writableTypeInfo,
|
||||
@@ -557,21 +556,46 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
||||
|
||||
private val OverriddenFunctionInfo.implementation get() = getImplementation(context)
|
||||
|
||||
data class ReflectionInfo(val packageName: String?, val relativeName: String?)
|
||||
|
||||
private fun getReflectionInfo(irClass: IrClass): ReflectionInfo = when {
|
||||
irClass.isAnonymousObject -> ReflectionInfo(packageName = null, relativeName = null)
|
||||
|
||||
irClass.isLocal -> ReflectionInfo(packageName = null, relativeName = irClass.name.asString())
|
||||
|
||||
else -> ReflectionInfo(
|
||||
packageName = irClass.findPackage().fqNameForIrSerialization.asString(),
|
||||
relativeName = generateSequence(irClass) { it.parent as? IrClass }
|
||||
.toList().reversed()
|
||||
.joinToString(".") { it.name.asString() }
|
||||
)
|
||||
data class ReflectionInfo(val packageName: String?, val relativeName: String?, val reflectionFlags: Int) {
|
||||
companion object {
|
||||
val EMPTY = ReflectionInfo(null, null, 0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getReflectionInfo(irClass: IrClass): ReflectionInfo {
|
||||
val packageName: String = irClass.findPackage().fqName.asString() // Compute and store package name in TypeInfo anyways.
|
||||
val relativeName: String?
|
||||
val flags: Int
|
||||
|
||||
when {
|
||||
irClass.isAnonymousObject -> {
|
||||
relativeName = context.getLocalClassName(irClass)
|
||||
flags = 0 // Forbid to use package and relative names in KClass.[simpleName|qualifiedName].
|
||||
}
|
||||
irClass.isLocal -> {
|
||||
relativeName = context.getLocalClassName(irClass)
|
||||
flags = TF_REFLECTION_SHOW_REL_NAME // Only allow relative name to be used in KClass.simpleName.
|
||||
}
|
||||
isLoweredFunctionReference(irClass) -> {
|
||||
// TODO: might return null so use fallback here, to be fixed in KT-47194
|
||||
relativeName = context.getLocalClassName(irClass) ?: generateDefaultRelativeName(irClass)
|
||||
flags = 0 // Forbid to use package and relative names in KClass.[simpleName|qualifiedName].
|
||||
}
|
||||
else -> {
|
||||
relativeName = generateDefaultRelativeName(irClass)
|
||||
flags = TF_REFLECTION_SHOW_PKG_NAME or TF_REFLECTION_SHOW_REL_NAME // Allow both package and relative names to be used in
|
||||
// KClass.[simpleName|qualifiedName].
|
||||
}
|
||||
}
|
||||
|
||||
return ReflectionInfo(packageName, relativeName, flags)
|
||||
}
|
||||
|
||||
private fun generateDefaultRelativeName(irClass: IrClass) =
|
||||
generateSequence(irClass) { it.parent as? IrClass }
|
||||
.toList().reversed()
|
||||
.joinToString(".") { it.name.asString() }
|
||||
|
||||
fun dispose() {
|
||||
debugRuntimeOrNull?.let { LLVMDisposeModule(it) }
|
||||
}
|
||||
@@ -586,4 +610,5 @@ private const val TF_LEAK_DETECTOR_CANDIDATE = 16
|
||||
private const val TF_SUSPEND_FUNCTION = 32
|
||||
private const val TF_HAS_FINALIZER = 64
|
||||
private const val TF_HAS_FREEZE_HOOK = 128
|
||||
|
||||
private const val TF_REFLECTION_SHOW_PKG_NAME = 256
|
||||
private const val TF_REFLECTION_SHOW_REL_NAME = 512
|
||||
|
||||
+3
@@ -218,6 +218,9 @@ internal class FunctionReferenceLowering(val context: Context): FileLoweringPass
|
||||
).apply {
|
||||
parent = this@FunctionReferenceBuilder.parent
|
||||
createParameterDeclarations()
|
||||
|
||||
// copy the generated name for IrClass, partially solves KT-47194
|
||||
context.copyLocalClassName(functionReference, this)
|
||||
}
|
||||
|
||||
private val functionReferenceThis = functionReferenceClass.thisReceiver!!
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.backend.konan.lower
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.lower.InventNamesForLocalClasses
|
||||
import org.jetbrains.kotlin.backend.konan.Context
|
||||
import org.jetbrains.kotlin.ir.declarations.IrAttributeContainer
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
|
||||
// TODO: consider replacing '$' by another delimeter that can't be used in class name specified with backticks (``)
|
||||
internal class NativeInventNamesForLocalClasses(val context: Context) : InventNamesForLocalClasses(allowTopLevelCallables = true) {
|
||||
override fun computeTopLevelClassName(clazz: IrClass): String = clazz.name.asString()
|
||||
override fun sanitizeNameIfNeeded(name: String) = name
|
||||
|
||||
override fun putLocalClassName(declaration: IrAttributeContainer, localClassName: String) {
|
||||
context.putLocalClassName(declaration, localClassName)
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
package codegen.kclass.kclass0
|
||||
|
||||
import kotlin.test.*
|
||||
import kotlin.coroutines.*
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@Test fun runTest() {
|
||||
@@ -13,36 +14,556 @@ import kotlin.reflect.KClass
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
checkClass(Any::class, "kotlin.Any", "Any", Any(), null)
|
||||
checkClass(Int::class, "kotlin.Int", "Int", 42, "17")
|
||||
checkClass(String::class, "kotlin.String", "String", "17", 42)
|
||||
checkClass(RootClass::class, "codegen.kclass.kclass0.RootClass", "RootClass", RootClass(), Any())
|
||||
checkClass(RootClass.Nested::class, "codegen.kclass.kclass0.RootClass.Nested", "Nested", RootClass.Nested(), Any())
|
||||
checkBasics()
|
||||
checkLocalClasses(args)
|
||||
checkAnonymousObjects(args)
|
||||
checkAnonymousObjectsAssignedToProperty(args)
|
||||
checkFunctionReferences()
|
||||
checkInterfaces()
|
||||
checkEtc()
|
||||
}
|
||||
|
||||
class Local {
|
||||
private fun checkBasics() {
|
||||
checkClass(
|
||||
clazz = Any::class,
|
||||
expectedQualifiedName = "kotlin.Any",
|
||||
expectedSimpleName = "Any",
|
||||
expectedToStringName = "class kotlin.Any",
|
||||
expectedInstance = Any(),
|
||||
expectedNotInstance = null
|
||||
)
|
||||
checkClass(
|
||||
clazz = Int::class,
|
||||
expectedQualifiedName = "kotlin.Int",
|
||||
expectedSimpleName = "Int",
|
||||
expectedToStringName = "class kotlin.Int",
|
||||
expectedInstance = 42,
|
||||
expectedNotInstance = "17"
|
||||
)
|
||||
checkClass(
|
||||
clazz = String::class,
|
||||
expectedQualifiedName = "kotlin.String",
|
||||
expectedSimpleName = "String",
|
||||
expectedToStringName = "class kotlin.String",
|
||||
expectedInstance = "17",
|
||||
expectedNotInstance = 42
|
||||
)
|
||||
checkClass(
|
||||
clazz = TopLevel::class,
|
||||
expectedQualifiedName = "codegen.kclass.kclass0.TopLevel",
|
||||
expectedSimpleName = "TopLevel",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.TopLevel",
|
||||
expectedInstance = TopLevel(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
checkClass(
|
||||
clazz = TopLevel.Nested::class,
|
||||
expectedQualifiedName = "codegen.kclass.kclass0.TopLevel.Nested",
|
||||
expectedSimpleName = "Nested",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.TopLevel.Nested",
|
||||
expectedInstance = TopLevel.Nested(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
}
|
||||
|
||||
private fun checkLocalClasses(args: Array<String>) {
|
||||
// Local class.
|
||||
class Local0 {
|
||||
val captured = args
|
||||
|
||||
inner class Inner
|
||||
}
|
||||
checkClass(Local::class, null, "Local", Local(), Any())
|
||||
checkClass(Local.Inner::class, null, "Inner", Local().Inner(), Any())
|
||||
checkClass(
|
||||
clazz = Local0::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Local0",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$Local0",
|
||||
expectedInstance = Local0(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
checkClass(
|
||||
clazz = Local0.Inner::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Inner",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$Local0\$Inner",
|
||||
expectedInstance = Local0().Inner(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
|
||||
// Local class inside of lambda.
|
||||
;{
|
||||
class Local {
|
||||
val captured = args
|
||||
inner class Inner
|
||||
}
|
||||
checkClass(
|
||||
clazz = Local::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Local",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$1\$Local",
|
||||
expectedInstance = Local(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
checkClass(
|
||||
clazz = Local.Inner::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Inner",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$1\$Local\$Inner",
|
||||
expectedInstance = Local().Inner(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
}.invoke()
|
||||
|
||||
// Local class inside of non-inlined lambda.
|
||||
runNonInlining {
|
||||
class Local {
|
||||
val captured = args
|
||||
inner class Inner
|
||||
}
|
||||
checkClass(
|
||||
clazz = Local::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Local",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$2\$Local",
|
||||
expectedInstance = Local(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
checkClass(
|
||||
clazz = Local.Inner::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Inner",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$2\$Local\$Inner",
|
||||
expectedInstance = Local().Inner(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
}
|
||||
|
||||
// Local class inside of inlined lambda.
|
||||
run {
|
||||
class Local {
|
||||
val captured = args
|
||||
inner class Inner
|
||||
}
|
||||
checkClass(
|
||||
clazz = Local::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Local",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$3\$Local",
|
||||
expectedInstance = Local(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
checkClass(
|
||||
clazz = Local.Inner::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Inner",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$3\$Local\$Inner",
|
||||
expectedInstance = Local().Inner(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
}
|
||||
|
||||
// Local class inside of suspend lambda.
|
||||
suspend {
|
||||
class Local {
|
||||
val captured = args
|
||||
inner class Inner
|
||||
}
|
||||
checkClass(
|
||||
clazz = Local::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Local",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$4\$Local",
|
||||
expectedInstance = Local(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
checkClass(
|
||||
clazz = Local.Inner::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Inner",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$4\$Local\$Inner",
|
||||
expectedInstance = Local().Inner(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
}.runCoroutine()
|
||||
|
||||
// Local class inside of suspend function.
|
||||
suspend fun suspendFunWithLocalClass() {
|
||||
class Local {
|
||||
val captured = args
|
||||
inner class Inner
|
||||
}
|
||||
checkClass(
|
||||
clazz = Local::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Local",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$suspendFunWithLocalClass\$Local",
|
||||
expectedInstance = Local(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
checkClass(
|
||||
clazz = Local.Inner::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Inner",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$suspendFunWithLocalClass\$Local\$Inner",
|
||||
expectedInstance = Local().Inner(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
}
|
||||
::suspendFunWithLocalClass.runCoroutine()
|
||||
}
|
||||
|
||||
private fun checkAnonymousObjects(args: Array<String>) {
|
||||
// Anonymous object.
|
||||
with(object : Any() {
|
||||
val captured = args
|
||||
inner class Inner
|
||||
val innerKClass = Inner::class
|
||||
}) {
|
||||
checkClass(
|
||||
clazz = this::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = null,
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$1",
|
||||
expectedInstance = this,
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
checkClass(
|
||||
clazz = this.innerKClass,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Inner",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$1\$Inner",
|
||||
expectedInstance = this.Inner(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
}
|
||||
|
||||
// Anonymous object inside of lambda.
|
||||
;{
|
||||
// $2 goes for this lambda itself.
|
||||
|
||||
with(object : Any() {
|
||||
val captured = args
|
||||
inner class Inner
|
||||
val innerKClass = Inner::class
|
||||
}) {
|
||||
checkClass(
|
||||
clazz = this::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = null,
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$3\$1",
|
||||
expectedInstance = this,
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
checkClass(
|
||||
clazz = this.innerKClass,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Inner",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$3\$1\$Inner",
|
||||
expectedInstance = this.Inner(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
}
|
||||
}.invoke()
|
||||
|
||||
// Anonymous object inside of non-inlined lambda.
|
||||
runNonInlining {
|
||||
with(object : Any() {
|
||||
val captured = args
|
||||
inner class Inner
|
||||
val innerKClass = Inner::class
|
||||
}) {
|
||||
checkClass(
|
||||
clazz = this::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = null,
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$4\$1",
|
||||
expectedInstance = this,
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
checkClass(
|
||||
clazz = this.innerKClass,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Inner",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$4\$1\$Inner",
|
||||
expectedInstance = this.Inner(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Anonymous object inside of inlined lambda.
|
||||
run {
|
||||
with(object : Any() {
|
||||
val captured = args
|
||||
inner class Inner
|
||||
val innerKClass = Inner::class
|
||||
}) {
|
||||
checkClass(
|
||||
clazz = this::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = null,
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$5\$1",
|
||||
expectedInstance = this,
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
checkClass(
|
||||
clazz = this.innerKClass,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Inner",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$5\$1\$Inner",
|
||||
expectedInstance = this.Inner(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Anonymous object inside of suspend lambda.
|
||||
suspend {
|
||||
with(object : Any() {
|
||||
val captured = args
|
||||
inner class Inner
|
||||
val innerKClass = Inner::class
|
||||
}) {
|
||||
checkClass(
|
||||
clazz = this::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = null,
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$6\$1",
|
||||
expectedInstance = this,
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
checkClass(
|
||||
clazz = this.innerKClass,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Inner",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$6\$1\$Inner",
|
||||
expectedInstance = this.Inner(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
}
|
||||
}.runCoroutine()
|
||||
|
||||
// Anonymous object inside of suspend function.
|
||||
suspend fun suspendFunWithAnonymousObject() {
|
||||
// $1 goes for lambda created in `::suspendFunWithAnonymousObject.runCoroutine()` call.
|
||||
|
||||
with(object : Any() {
|
||||
val captured = args
|
||||
inner class Inner
|
||||
val innerKClass = Inner::class
|
||||
}) {
|
||||
checkClass(
|
||||
clazz = this::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = null,
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$suspendFunWithAnonymousObject\$2",
|
||||
expectedInstance = this,
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
checkClass(
|
||||
clazz = this.innerKClass,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Inner",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$suspendFunWithAnonymousObject\$2\$Inner",
|
||||
expectedInstance = this.Inner(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
}
|
||||
}
|
||||
::suspendFunWithAnonymousObject.runCoroutine()
|
||||
}
|
||||
|
||||
private fun checkAnonymousObjectsAssignedToProperty(args: Array<String>) {
|
||||
// Anonymous object.
|
||||
val obj = object : Any() {
|
||||
val captured = args
|
||||
|
||||
inner class Inner
|
||||
val innerKClass = Inner::class
|
||||
}
|
||||
checkClass(obj::class, null, null, obj, Any())
|
||||
checkClass(obj.innerKClass, null, "Inner", obj.Inner(), Any())
|
||||
checkClass(
|
||||
clazz = obj::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = null,
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$obj\$1",
|
||||
expectedInstance = obj,
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
checkClass(
|
||||
clazz = obj.innerKClass,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Inner",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$obj\$1\$Inner",
|
||||
expectedInstance = obj.Inner(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
|
||||
// Interfaces:
|
||||
checkClass(Comparable::class, "kotlin.Comparable", "Comparable", 42, Any())
|
||||
checkClass(Interface::class, "codegen.kclass.kclass0.Interface", "Interface", object : Interface {}, Any())
|
||||
// Anonymous object inside of lambda.
|
||||
;{
|
||||
val obj = object : Any() {
|
||||
val captured = args
|
||||
inner class Inner
|
||||
val innerKClass = Inner::class
|
||||
}
|
||||
checkClass(
|
||||
clazz = obj::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = null,
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$1\$obj\$1",
|
||||
expectedInstance = obj,
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
checkClass(
|
||||
clazz = obj.innerKClass,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Inner",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$1\$obj\$1\$Inner",
|
||||
expectedInstance = obj.Inner(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
}.invoke()
|
||||
|
||||
// Anonymous object inside of non-inlined lambda.
|
||||
runNonInlining {
|
||||
val obj = object : Any() {
|
||||
val captured = args
|
||||
inner class Inner
|
||||
val innerKClass = Inner::class
|
||||
}
|
||||
checkClass(
|
||||
clazz = obj::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = null,
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$2\$obj\$1",
|
||||
expectedInstance = obj,
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
checkClass(
|
||||
clazz = obj.innerKClass,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Inner",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$2\$obj\$1\$Inner",
|
||||
expectedInstance = obj.Inner(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
}
|
||||
|
||||
// Anonymous object inside of inlined lambda.
|
||||
run {
|
||||
val obj = object : Any() {
|
||||
val captured = args
|
||||
inner class Inner
|
||||
val innerKClass = Inner::class
|
||||
}
|
||||
checkClass(
|
||||
clazz = obj::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = null,
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$3\$obj\$1",
|
||||
expectedInstance = obj,
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
checkClass(
|
||||
clazz = obj.innerKClass,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Inner",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$3\$obj\$1\$Inner",
|
||||
expectedInstance = obj.Inner(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
}
|
||||
|
||||
// Anonymous object inside of suspend lambda.
|
||||
suspend {
|
||||
val obj = object : Any() {
|
||||
val captured = args
|
||||
inner class Inner
|
||||
val innerKClass = Inner::class
|
||||
}
|
||||
checkClass(
|
||||
clazz = obj::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = null,
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$4\$obj\$1",
|
||||
expectedInstance = obj,
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
checkClass(
|
||||
clazz = obj.innerKClass,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Inner",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$4\$obj\$1\$Inner",
|
||||
expectedInstance = obj.Inner(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
}.runCoroutine()
|
||||
|
||||
// Anonymous object inside of suspend function.
|
||||
suspend fun suspendFunWithAnonymousObject() {
|
||||
// $1 goes for lambda created in `::suspendFunWithAnonymousObject.runCoroutine()` call.
|
||||
|
||||
val obj = object : Any() {
|
||||
val captured = args
|
||||
inner class Inner
|
||||
val innerKClass = Inner::class
|
||||
}
|
||||
checkClass(
|
||||
clazz = obj::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = null,
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$suspendFunWithAnonymousObject\$obj\$1",
|
||||
expectedInstance = obj,
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
checkClass(
|
||||
clazz = obj.innerKClass,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = "Inner",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$suspendFunWithAnonymousObject\$obj\$1\$Inner",
|
||||
expectedInstance = obj.Inner(),
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
}
|
||||
::suspendFunWithAnonymousObject.runCoroutine()
|
||||
}
|
||||
|
||||
private fun checkFunctionReferences() {
|
||||
// TODO add tests for lambdas and anonymous functions, see KT-47194
|
||||
fun foo() = Unit
|
||||
val ref = ::foo
|
||||
checkClass(
|
||||
clazz = ref::class,
|
||||
expectedQualifiedName = null,
|
||||
expectedSimpleName = null,
|
||||
expectedToStringName = "class codegen.kclass.kclass0.checkFunctionReferences\$ref\$1",
|
||||
expectedInstance = ref,
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
}
|
||||
|
||||
private fun checkInterfaces() {
|
||||
checkClass(
|
||||
clazz = Comparable::class,
|
||||
expectedQualifiedName = "kotlin.Comparable",
|
||||
expectedSimpleName = "Comparable",
|
||||
expectedToStringName = "class kotlin.Comparable",
|
||||
expectedInstance = 42,
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
checkClass(
|
||||
clazz = Interface::class,
|
||||
expectedQualifiedName = "codegen.kclass.kclass0.Interface",
|
||||
expectedSimpleName = "Interface",
|
||||
expectedToStringName = "class codegen.kclass.kclass0.Interface",
|
||||
expectedInstance = object : Interface {},
|
||||
expectedNotInstance = Any()
|
||||
)
|
||||
}
|
||||
|
||||
private fun checkEtc() {
|
||||
checkInstanceClass(Any(), Any::class)
|
||||
checkInstanceClass(42, Int::class)
|
||||
assert(42::class == Int::class)
|
||||
assertEquals(42::class, Int::class)
|
||||
|
||||
class Local
|
||||
|
||||
checkReifiedClass<Int>(Int::class)
|
||||
checkReifiedClass<Int?>(Int::class)
|
||||
@@ -53,36 +574,43 @@ fun main(args: Array<String>) {
|
||||
checkReifiedClass2<Any?>(Any::class)
|
||||
checkReifiedClass<Local>(Local::class)
|
||||
checkReifiedClass2<Local>(Local::class)
|
||||
checkReifiedClass<RootClass>(RootClass::class)
|
||||
checkReifiedClass2<RootClass>(RootClass::class)
|
||||
checkReifiedClass<TopLevel>(TopLevel::class)
|
||||
checkReifiedClass2<TopLevel>(TopLevel::class)
|
||||
}
|
||||
|
||||
class RootClass {
|
||||
|
||||
class TopLevel {
|
||||
class Nested
|
||||
}
|
||||
interface Interface
|
||||
|
||||
fun checkClass(
|
||||
private fun checkClass(
|
||||
clazz: KClass<*>,
|
||||
expectedQualifiedName: String?, expectedSimpleName: String?,
|
||||
expectedQualifiedName: String?, expectedSimpleName: String?, expectedToStringName: String,
|
||||
expectedInstance: Any, expectedNotInstance: Any?
|
||||
) {
|
||||
assert(clazz.qualifiedName == expectedQualifiedName)
|
||||
assert(clazz.simpleName == expectedSimpleName)
|
||||
assertEquals(clazz.qualifiedName, expectedQualifiedName)
|
||||
assertEquals(clazz.simpleName, expectedSimpleName)
|
||||
assertEquals(clazz.toString(), expectedToStringName)
|
||||
|
||||
assert(clazz.isInstance(expectedInstance))
|
||||
if (expectedNotInstance != null) assert(!clazz.isInstance(expectedNotInstance))
|
||||
assertTrue(clazz.isInstance(expectedInstance))
|
||||
if (expectedNotInstance != null) assertTrue(!clazz.isInstance(expectedNotInstance))
|
||||
}
|
||||
|
||||
fun checkInstanceClass(instance: Any, clazz: KClass<*>) {
|
||||
assert(instance::class == clazz)
|
||||
private fun checkInstanceClass(instance: Any, clazz: KClass<*>) {
|
||||
assertEquals(instance::class, clazz)
|
||||
}
|
||||
|
||||
inline fun <reified T> checkReifiedClass(expectedClass: KClass<*>) {
|
||||
assert(T::class == expectedClass)
|
||||
private inline fun <reified T> checkReifiedClass(expectedClass: KClass<*>) {
|
||||
assertEquals(T::class, expectedClass)
|
||||
}
|
||||
|
||||
inline fun <reified T> checkReifiedClass2(expectedClass: KClass<*>) {
|
||||
private inline fun <reified T> checkReifiedClass2(expectedClass: KClass<*>) {
|
||||
checkReifiedClass<T>(expectedClass)
|
||||
checkReifiedClass<T?>(expectedClass)
|
||||
}
|
||||
|
||||
// Like `run` but without inlining.
|
||||
private fun runNonInlining(block: () -> Unit) = block()
|
||||
|
||||
private fun <T> (suspend () -> T).runCoroutine() = startCoroutine(Continuation(EmptyCoroutineContext) { it.getOrThrow() })
|
||||
|
||||
@@ -56,6 +56,8 @@ enum Konan_TypeFlags {
|
||||
TF_SUSPEND_FUNCTION = 1 << 5,
|
||||
TF_HAS_FINALIZER = 1 << 6,
|
||||
TF_HAS_FREEZE_HOOK = 1 << 7,
|
||||
TF_REFLECTION_SHOW_PKG_NAME = 1 << 8, // If package name is available in reflection, e.g. in `KClass.qualifiedName`.
|
||||
TF_REFLECTION_SHOW_REL_NAME = 1 << 9 // If relative name is available in reflection, e.g. in `KClass.simpleName`.
|
||||
};
|
||||
|
||||
// Flags per object instance.
|
||||
@@ -115,13 +117,12 @@ struct TypeInfo {
|
||||
int32_t interfaceTableSize_;
|
||||
InterfaceTableRecord const* interfaceTable_;
|
||||
|
||||
// String for the fully qualified dot-separated name of the package containing class,
|
||||
// or `null` if the class is local or anonymous.
|
||||
// String for the fully qualified dot-separated name of the package containing class.
|
||||
ObjHeader* packageName_;
|
||||
|
||||
// String for the qualified class name relative to the containing package
|
||||
// (e.g. TopLevel.Nested1.Nested2), or simple class name if it is local,
|
||||
// or `null` if the class is anonymous.
|
||||
// (e.g. TopLevel.Nested1.Nested2) or the effective class name computed for
|
||||
// local class or anonymous object (e.g. listOf$1).
|
||||
ObjHeader* relativeName_;
|
||||
|
||||
// Various flags.
|
||||
|
||||
@@ -55,12 +55,22 @@ KBoolean Kotlin_TypeInfo_isInstance(KConstRef obj, KNativePtr typeInfo) {
|
||||
return IsInstance(obj, reinterpret_cast<const TypeInfo*>(typeInfo));
|
||||
}
|
||||
|
||||
OBJ_GETTER(Kotlin_TypeInfo_getPackageName, KNativePtr typeInfo) {
|
||||
RETURN_OBJ(reinterpret_cast<const TypeInfo*>(typeInfo)->packageName_);
|
||||
OBJ_GETTER(Kotlin_TypeInfo_getPackageName, KNativePtr typeInfo, KBoolean checkFlags) {
|
||||
const TypeInfo* type_info = reinterpret_cast<const TypeInfo*>(typeInfo);
|
||||
if (!checkFlags || type_info->flags_ & TF_REFLECTION_SHOW_PKG_NAME) {
|
||||
RETURN_OBJ(type_info->packageName_);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
OBJ_GETTER(Kotlin_TypeInfo_getRelativeName, KNativePtr typeInfo) {
|
||||
RETURN_OBJ(reinterpret_cast<const TypeInfo*>(typeInfo)->relativeName_);
|
||||
OBJ_GETTER(Kotlin_TypeInfo_getRelativeName, KNativePtr typeInfo, KBoolean checkFlags) {
|
||||
const TypeInfo* type_info = reinterpret_cast<const TypeInfo*>(typeInfo);
|
||||
if (!checkFlags || type_info->flags_ & TF_REFLECTION_SHOW_REL_NAME) {
|
||||
RETURN_OBJ(type_info->relativeName_);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
struct AssociatedObjectTableRecord {
|
||||
|
||||
@@ -9,25 +9,15 @@ import kotlin.reflect.KClass
|
||||
|
||||
@ExportForCompiler
|
||||
internal class KClassImpl<T : Any>(private val typeInfo: NativePtr) : KClass<T> {
|
||||
// TODO: consider replacing '$' by another delimeter that can't be used in class name specified with backticks (``)
|
||||
override val simpleName: String?
|
||||
get() {
|
||||
val relativeName = getRelativeName(typeInfo)
|
||||
?: return null
|
||||
|
||||
return relativeName.substringAfterLast(".")
|
||||
}
|
||||
get() = getRelativeName(typeInfo, true)?.substringAfterLast('.')?.substringAfterLast('$')
|
||||
|
||||
override val qualifiedName: String?
|
||||
get() {
|
||||
val packageName = getPackageName(typeInfo)
|
||||
?: return null
|
||||
|
||||
val relativeName = getRelativeName(typeInfo)!!
|
||||
return if (packageName.isEmpty()) {
|
||||
relativeName
|
||||
} else {
|
||||
"$packageName.$relativeName"
|
||||
}
|
||||
val packageName = getPackageName(typeInfo, true) ?: return null
|
||||
val relativeName = getRelativeName(typeInfo, true) ?: return null
|
||||
return if (packageName.isEmpty()) relativeName else "$packageName.$relativeName"
|
||||
}
|
||||
|
||||
override fun isInstance(value: Any?): Boolean = value != null && isInstance(value, this.typeInfo)
|
||||
@@ -38,7 +28,13 @@ internal class KClassImpl<T : Any>(private val typeInfo: NativePtr) : KClass<T>
|
||||
override fun hashCode(): Int = typeInfo.hashCode()
|
||||
|
||||
override fun toString(): String {
|
||||
return "class " + (qualifiedName ?: simpleName ?: "<anonymous>")
|
||||
val relativeName = getRelativeName(typeInfo, false)
|
||||
val visibleName = if (relativeName != null) {
|
||||
val packageName = getPackageName(typeInfo, false)!!
|
||||
if (packageName.isEmpty()) relativeName else "$packageName.$relativeName"
|
||||
} else "<anonymous>"
|
||||
|
||||
return "class $visibleName"
|
||||
}
|
||||
|
||||
internal fun findAssociatedObjectImpl(key: KClassImpl<*>): Any? =
|
||||
@@ -80,10 +76,10 @@ internal external fun getObjectTypeInfo(obj: Any): NativePtr
|
||||
internal external inline fun <reified T : Any> getClassTypeInfo(): NativePtr
|
||||
|
||||
@GCUnsafeCall("Kotlin_TypeInfo_getPackageName")
|
||||
private external fun getPackageName(typeInfo: NativePtr): String?
|
||||
private external fun getPackageName(typeInfo: NativePtr, checkFlags: Boolean): String?
|
||||
|
||||
@GCUnsafeCall("Kotlin_TypeInfo_getRelativeName")
|
||||
private external fun getRelativeName(typeInfo: NativePtr): String?
|
||||
private external fun getRelativeName(typeInfo: NativePtr, checkFlags: Boolean): String?
|
||||
|
||||
@GCUnsafeCall("Kotlin_TypeInfo_isInstance")
|
||||
private external fun isInstance(obj: Any, typeInfo: NativePtr): Boolean
|
||||
|
||||
Reference in New Issue
Block a user