~ [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 + } } }