🍒 [K/JS] [2.4.20] Fix infinite recursion of `$suspendBridge` inside anonymous class (#6843)

Original PR: #6749

Issue: KT-86934
diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/PrepareSuspendFunctionsForExportLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/PrepareSuspendFunctionsForExportLowering.kt
index 950f13f..1661c34 100644
--- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/PrepareSuspendFunctionsForExportLowering.kt
+++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/PrepareSuspendFunctionsForExportLowering.kt
@@ -13,6 +13,7 @@
 import org.jetbrains.kotlin.backend.common.phaser.PhasePrerequisites
 import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
 import org.jetbrains.kotlin.descriptors.Modality
+import org.jetbrains.kotlin.ir.IrStatement
 import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
 import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
 import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
@@ -528,12 +529,19 @@
 @PhasePrerequisites(PrepareSuspendFunctionsForExportLowering::class)
 class ReplaceExportedSuspendFunctionsCallsWithTheirBridgeCall(private val context: JsIrBackendContext) : BodyLoweringPass {
     override fun lower(irBody: IrBody, container: IrDeclaration) {
-        if (
-            container is IrSimpleFunction &&
-            (container.origin == EXPORTED_SUSPEND_FUNCTION_BRIDGE || container.origin == PROMISIFIED_MEMBER_WRAPPER)
-        ) return
+        // No need to replace calls inside synthetic $promisified and $suspendBridge function bodies
+        if (container is IrSimpleFunction && container.isSyntheticSuspendCallContainer)
+            return
 
         irBody.transformChildrenVoid(object : IrElementTransformerVoid() {
+            override fun visitSimpleFunction(declaration: IrSimpleFunction): IrStatement {
+                // In case of anonymous classes, we also need to exempt from replacing nested promisified and bridge function declarations
+                if (declaration.isSyntheticSuspendCallContainer)
+                    return declaration
+
+                return super.visitSimpleFunction(declaration)
+            }
+
             override fun visitCall(expression: IrCall): IrExpression {
                 // In the case of super.suspendCall, as an optimization, we can keep the call without bridging it
                 // since at compile time we know that it's always a Kotlin suspend function
@@ -547,6 +555,9 @@
             }
         })
     }
+
+    private val IrSimpleFunction.isSyntheticSuspendCallContainer: Boolean
+        get() = origin == EXPORTED_SUSPEND_FUNCTION_BRIDGE || origin == PROMISIFIED_MEMBER_WRAPPER
 }
 
 /** We need to ignore exporting of the original functions only after all the exported suspend function calls
diff --git a/js/js.translator/testData/box/export/kt86934.kt b/js/js.translator/testData/box/export/kt86934.kt
new file mode 100644
index 0000000..b5f61a9
--- /dev/null
+++ b/js/js.translator/testData/box/export/kt86934.kt
@@ -0,0 +1,51 @@
+// RUN_PLAIN_BOX_FUNCTION
+// ISSUE: KT-86934
+
+// MODULE: lib
+// FILE: lib.kt
+
+@JsExport
+interface Test {
+    suspend fun func(): String
+}
+
+@JsExport
+open class Base {
+    open suspend fun func(): String = "fail"
+}
+
+@JsExport
+suspend fun main(): String {
+    // Anonymous object implementing an exported interface (virtual bridge path)
+    val viaInterface = object : Test {
+        override suspend fun func(): String = "OK"
+    }
+
+    // Anonymous object extending an open exported class (non-interface implementor bridge path)
+    val viaClass = object : Base() {
+        override suspend fun func(): String = "OK"
+    }
+
+    // Anonymous object nested inside another anonymous object's suspend method (deeper nesting)
+    val nested = object : Test {
+        override suspend fun func(): String {
+            val inner = object : Test {
+                override suspend fun func(): String = "OK"
+            }
+            return inner.func()
+        }
+    }
+
+    val r1 = viaInterface.func()
+    val r2 = viaClass.func()
+    val r3 = nested.func()
+
+    return if (r1 == "OK" && r2 == "OK" && r3 == "OK") "OK" else "fail: $r1 $r2 $r3"
+}
+
+// FILE: main.js
+async function box() {
+    var main = this.lib.main;
+
+    return await main()
+}