~ [j] fix infinite loop
diff --git a/compiler/cli/cli-jvm/src/org/jetbrains/kotlin/cli/pipeline/jvm/JvmFrontendPipelinePhase.kt b/compiler/cli/cli-jvm/src/org/jetbrains/kotlin/cli/pipeline/jvm/JvmFrontendPipelinePhase.kt
index 158b5ac..6ebd7fe 100644
--- a/compiler/cli/cli-jvm/src/org/jetbrains/kotlin/cli/pipeline/jvm/JvmFrontendPipelinePhase.kt
+++ b/compiler/cli/cli-jvm/src/org/jetbrains/kotlin/cli/pipeline/jvm/JvmFrontendPipelinePhase.kt
@@ -46,6 +46,7 @@
 import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar
 import org.jetbrains.kotlin.fir.pipeline.*
 import org.jetbrains.kotlin.fir.session.*
+import org.jetbrains.kotlin.fir.session.environment.AbstractProjectEnvironment
 import org.jetbrains.kotlin.fir.session.environment.AbstractProjectFileSearchScope
 import org.jetbrains.kotlin.java.direct.createJavaDirectBinaryClassFinderInputsBuilder
 import org.jetbrains.kotlin.java.direct.createJavaDirectSourceJavaFacadeBuilder
diff --git a/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/JavaClassFinderOverAstImpl.kt b/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/JavaClassFinderOverAstImpl.kt
index 25274cb..35bdff7 100644
--- a/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/JavaClassFinderOverAstImpl.kt
+++ b/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/JavaClassFinderOverAstImpl.kt
@@ -11,6 +11,7 @@
 import org.jetbrains.kotlin.java.direct.model.JavaPackageOverAst
 import org.jetbrains.kotlin.java.direct.resolution.JavaResolutionContext
 import org.jetbrains.kotlin.java.direct.resolution.LeanJavaClassFinder
+import org.jetbrains.kotlin.java.direct.resolution.registerJavaModelDirectSupertypeCacheIfAbsent
 import org.jetbrains.kotlin.java.direct.resolution.registerJavaModelInFlightResolutionsIfAbsent
 import org.jetbrains.kotlin.java.direct.resolution.registerJavaModelSupertypeWalkGuardIfAbsent
 import org.jetbrains.kotlin.java.direct.resolution.registerJavaModelTypeUseCacheIfAbsent
@@ -51,6 +52,10 @@
         // Attach the per-session supertype-walk guard used by [cycleGuardedSupertypeWalk] to bound
         // Java inheritance cycles during supertype walks. Same idempotency guarantees as above.
         session.registerJavaModelSupertypeWalkGuardIfAbsent()
+        // Attach the per-session direct-supertype cache used by [directSupertypeClassIds] to
+        // memoize each class's direct supertypes, so transitive supertype-closure walks do not
+        // re-resolve the same ancestors. Same idempotency guarantees as above.
+        session.registerJavaModelDirectSupertypeCacheIfAbsent()
         // Attach the per-session TYPE_USE annotation-class cache used by
         // [isTypeUseAnnotationClass] when filtering TYPE_USE annotations on java-direct
         // `JavaTypeOverAst.annotations`. Same idempotency guarantees as above.
diff --git a/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/resolution/JavaModelSessionAccess.kt b/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/resolution/JavaModelSessionAccess.kt
index a348bc2..1227d15 100644
--- a/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/resolution/JavaModelSessionAccess.kt
+++ b/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/resolution/JavaModelSessionAccess.kt
@@ -147,6 +147,58 @@
 }
 
 /**
+ * Per-session `ClassId -> List<ClassId>` cache of resolved direct-supertype `ClassId`s for
+ * [directSupertypeClassIds].
+ *
+ * A class's direct supertypes are a pure function of the class and the session, so memoizing them
+ * turns the transitive supertype-closure walk that inherited-inner-class resolution performs from
+ * repeated re-resolution — each source-arm `.classifier` read re-descends the hierarchy, so an
+ * un-cached walk is exponential in the hierarchy depth — into a single computation per class.
+ * Without it, resolving inherited inner classes over deep Java source hierarchies re-walks the
+ * whole closure for every name at every level, which makes large mixed Kotlin/Java compilations
+ * effectively hang.
+ *
+ * Registered by [registerJavaModelDirectSupertypeCacheIfAbsent]; sessions without it fall back to
+ * the un-cached walk inside [directSupertypeClassIds].
+ */
+internal class JavaModelDirectSupertypeCache : FirSessionComponent {
+    val classIdToSupertypes: ConcurrentHashMap<ClassId, List<ClassId>> = ConcurrentHashMap()
+}
+
+private val FirSession.javaModelDirectSupertypeCache: JavaModelDirectSupertypeCache?
+        by FirSession.nullableSessionComponentAccessor()
+
+/** Registers a [JavaModelDirectSupertypeCache] on this session if one is not already present. */
+@OptIn(SessionConfiguration::class)
+internal fun FirSession.registerJavaModelDirectSupertypeCacheIfAbsent() {
+    if (javaModelDirectSupertypeCache == null) {
+        register(JavaModelDirectSupertypeCache::class, JavaModelDirectSupertypeCache())
+    }
+}
+
+/**
+ * Memoizes [compute] (the per-origin direct-supertype walk of [directSupertypeClassIds]) per
+ * [classId] on the session, so each class's direct supertypes are resolved at most once.
+ *
+ * Only non-empty results are cached. An empty result is never authoritative here: it is what both
+ * [cycleGuardedSupertypeWalk] and [cycleSafeClassLikeSymbol] return for a re-entrant / in-flight
+ * [classId] (cycle break), so caching it could pin a transient empty over a class that does have
+ * supertypes. Recomputing an empty result is cheap — a class with no cached supertypes either has
+ * none (a root interface) or is mid-cycle — and the exponential re-resolution this cache exists to
+ * prevent is driven entirely by classes that *do* have supertypes, which are cached.
+ */
+internal fun FirSession.memoizedDirectSupertypeClassIds(
+    classId: ClassId,
+    compute: () -> List<ClassId>,
+): List<ClassId> {
+    val cache = javaModelDirectSupertypeCache?.classIdToSupertypes ?: return compute()
+    cache[classId]?.let { return it }
+    val result = compute()
+    if (result.isNotEmpty()) cache[classId] = result
+    return result
+}
+
+/**
  * Per-session `ClassId -> Boolean` cache of TYPE_USE-ness for annotation classes.
  *
  * Populated by [isTypeUseAnnotationClass]; the predicate is a static property of the annotation
diff --git a/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/resolution/JavaTypeResolver.kt b/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/resolution/JavaTypeResolver.kt
index c181eec..7d94bd6 100644
--- a/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/resolution/JavaTypeResolver.kt
+++ b/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/resolution/JavaTypeResolver.kt
@@ -593,7 +593,12 @@
 
 /**
  * Per-origin direct-supertype-`ClassId` dispatcher, guarded by [cycleGuardedSupertypeWalk] so
- * direct (`A extends A`) and indirect (`A → B → A`) Java-side cycles terminate cleanly.
+ * direct (`A extends A`) and indirect (`A → B → A`) Java-side cycles terminate cleanly, and
+ * memoized per session via [memoizedDirectSupertypeClassIds] so each class's direct supertypes
+ * are resolved once. The memoization is essential, not merely an optimization: the source arm
+ * reads each supertype's `.classifier`, which re-enters resolution and re-descends the hierarchy,
+ * so the transitive-closure walks that inherited-inner-class resolution performs would otherwise
+ * re-resolve every ancestor exponentially and stall on deep hierarchies.
  *
  *  1. **Source Java arm** — `classFinder.findClass(classId)` hits: walk `JavaClass.supertypes`
  *     directly (no FIR phase involved).
@@ -607,32 +612,34 @@
 @OptIn(SymbolInternals::class)
 context(c: JavaResolutionContext)
 internal fun directSupertypeClassIds(classId: ClassId): List<ClassId> =
-    c.fileContext.session.cycleGuardedSupertypeWalk(classId, default = emptyList()) {
-        // 1. Source Java arm — walk our own AST. Supertype names are syntactically
-        // knowable; no FIR phase is involved.
-        val finder = c.fileContext.classFinder
-        if (finder != null && finder.isClassInIndex(classId)) {
-            val javaClass = finder.findClass(JavaClassFinder.Request(classId))
-            if (javaClass != null) {
-                return@cycleGuardedSupertypeWalk resolveSupertypeNames(javaClass)
+    c.fileContext.session.memoizedDirectSupertypeClassIds(classId) {
+        c.fileContext.session.cycleGuardedSupertypeWalk(classId, default = emptyList()) {
+            // 1. Source Java arm — walk our own AST. Supertype names are syntactically
+            // knowable; no FIR phase is involved.
+            val finder = c.fileContext.classFinder
+            if (finder != null && finder.isClassInIndex(classId)) {
+                val javaClass = finder.findClass(JavaClassFinder.Request(classId))
+                if (javaClass != null) {
+                    return@cycleGuardedSupertypeWalk resolveSupertypeNames(javaClass)
+                }
             }
-        }
 
-        // 2. & 3. Look up the FIR symbol — the model's only handle for non-source-Java
-        // classes (binary Java, Kotlin, deserialized).
-        val symbol = c.fileContext.session.cycleSafeClassLikeSymbol(classId) ?: return@cycleGuardedSupertypeWalk emptyList()
-        val firClass = symbol.fir as? FirRegularClass ?: return@cycleGuardedSupertypeWalk emptyList()
+            // 2. & 3. Look up the FIR symbol — the model's only handle for non-source-Java
+            // classes (binary Java, Kotlin, deserialized).
+            val symbol = c.fileContext.session.cycleSafeClassLikeSymbol(classId) ?: return@cycleGuardedSupertypeWalk emptyList()
+            val firClass = symbol.fir as? FirRegularClass ?: return@cycleGuardedSupertypeWalk emptyList()
 
-        // 2. Binary Java arm — read the pre-resolved cache on FirJavaClass; never
-        // touches the lazy `superTypeRefs` enhancement.
-        if (firClass is FirJavaClass) {
-            return@cycleGuardedSupertypeWalk firClass.directSupertypeClassIds()
-        }
+            // 2. Binary Java arm — read the pre-resolved cache on FirJavaClass; never
+            // touches the lazy `superTypeRefs` enhancement.
+            if (firClass is FirJavaClass) {
+                return@cycleGuardedSupertypeWalk firClass.directSupertypeClassIds()
+            }
 
-        // 3. Kotlin / built-in / deserialized arm — lazyResolveToPhase is honest here.
-        symbol.lazyResolveToPhase(FirResolvePhase.SUPER_TYPES)
-        firClass.superTypeRefs.mapNotNull { ref ->
-            ((ref as? FirResolvedTypeRef)?.coneType as? ConeClassLikeType)?.lookupTag?.classId
+            // 3. Kotlin / built-in / deserialized arm — lazyResolveToPhase is honest here.
+            symbol.lazyResolveToPhase(FirResolvePhase.SUPER_TYPES)
+            firClass.superTypeRefs.mapNotNull { ref ->
+                ((ref as? FirResolvedTypeRef)?.coneType as? ConeClassLikeType)?.lookupTag?.classId
+            }
         }
     }