[Native][IR] Move verbose RTTI for local classes and anonymous objects

^KT-45304
This commit is contained in:
Dmitriy Dolovov
2021-05-13 17:57:59 +03:00
parent 6bac016b3a
commit d42ff069f6
10 changed files with 680 additions and 77 deletions
@@ -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)
}
@@ -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(
@@ -192,6 +192,7 @@ internal val allLoweringsPhase = NamedCompilerPhase(
arrayConstructorPhase then
lateinitPhase then
sharedVariablesPhase then
inventNamesForLocalClasses then
extractLocalClassesFromInlineBodies then
inlinePhase then
provisionalFunctionExpressionPhase then
@@ -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
@@ -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!!
@@ -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.
+14 -4
View File
@@ -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