diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/DeepCopyIrTreeWithDescriptors.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/DeepCopyIrTreeWithDescriptors.kt index 64fe3a3fb9d..d7339bc8804 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/DeepCopyIrTreeWithDescriptors.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/DeepCopyIrTreeWithDescriptors.kt @@ -784,6 +784,32 @@ internal class DeepCopyIrTreeWithDescriptors(val targetDescriptor: FunctionDescr class SubstitutedDescriptor(val inlinedFunction: FunctionDescriptor, val descriptor: DeclarationDescriptor) +/** + * Searches recursively for value that is corresponds to [key], + * then to typeMapper(this[key]) and so on until typeMapper or Map.get returns null. + * Returns null if there is no corresponding value for given [key] inside the map + * Throws IllegalStateException if recursion led to cycle. + */ +private fun Map.deepSearch(key: K, typeMapper: (V) -> K?): V? { + var result: V? = null + // Prevent cycles by remembering visited keys. + val visitedKeys = mutableSetOf() + var keyToVisit = key + do { + if (keyToVisit in visitedKeys) { + throw IllegalStateException("Detected cycle inside $this") + } else { + visitedKeys += keyToVisit + } + val value = this[keyToVisit] + if (value != null) { + result = value + keyToVisit = typeMapper(value) ?: return result + } + } while (value != null) + return result +} + internal class DescriptorSubstitutorForExternalScope( val globalSubstituteMap: MutableMap, val context: Context @@ -808,8 +834,29 @@ internal class DescriptorSubstitutorForExternalScope( val oldDescriptor = declaration.descriptor val oldClassDescriptor = oldDescriptor.type.constructor.declarationDescriptor as? ClassDescriptor - val substitutedDescriptor = oldClassDescriptor?.let { globalSubstituteMap[it] } - if (substitutedDescriptor == null || allScopes.any { it.scope.scopeOwner == substitutedDescriptor.inlinedFunction }) + + // This recursive search inside the map requires some explanation. + // Take a look at the following code: + // + // inline fun exec(f: () -> Unit) = f() + // + // inline fun test2() { + // val sr = object {} // <- problematic place + // } + // + // fun box() { + // exec { + // test2() + // } + // } + // + // Here test2 will be inlined twice, so descriptor of the object will be changed twice (ex. from A to B and from B to C). + // If we use ordinary access to the map (globalSubstituteMap[A]) then we get wrong descriptor (B). + // So we have to search recursively inside the map to find correct descriptor. + val typeMapper: (SubstitutedDescriptor) -> ClassDescriptor? = { it.descriptor as? ClassDescriptor } + val substitutedDescriptor = oldClassDescriptor?.let { globalSubstituteMap.deepSearch(it, typeMapper) } + // should be true for empty allScopes + if (substitutedDescriptor == null || allScopes.all { it.scope.scopeOwner != substitutedDescriptor.inlinedFunction }) return val newDescriptor = IrTemporaryVariableDescriptorImpl( containingDeclaration = oldDescriptor.containingDeclaration, diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index f466acd06b5..8ee72b9afdb 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -2526,6 +2526,11 @@ task inline_defaultArgs(type: RunKonanTest) { source = "codegen/inline/defaultArgs.kt" } +task inline_twiceInlinedObject(type: RunKonanTest) { + goldValue = "Ok\n" + source = "codegen/inline/twiceInlinedObject.kt" +} + task classDeclarationInsideInline(type: RunKonanTest) { source = "codegen/inline/classDeclarationInsideInline.kt" goldValue = "test1: 1.0\n1\ntest2\n" diff --git a/backend.native/tests/codegen/inline/twiceInlinedObject.kt b/backend.native/tests/codegen/inline/twiceInlinedObject.kt new file mode 100644 index 00000000000..b0aa2d8ecfa --- /dev/null +++ b/backend.native/tests/codegen/inline/twiceInlinedObject.kt @@ -0,0 +1,23 @@ +package codegen.inline.twiceInlinedObject + +import kotlin.test.* + +inline fun exec(f: () -> Unit) = f() + +inline fun test2() { + val obj = object { + fun sayOk() = println("Ok") + } + obj.sayOk() +} + +inline fun noExec(f: () -> Unit) { } + +@Test fun runTest() { + exec { + test2() + } + noExec { + test2() + } +} \ No newline at end of file