diff --git a/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/java/KotlinMeta.java b/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/java/KotlinMeta.java index cc2f82946842..5b3d4255a978 100644 --- a/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/java/KotlinMeta.java +++ b/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/java/KotlinMeta.java @@ -165,6 +165,11 @@ public final class KotlinMeta implements JvmMetadata getSupertypes() { + KmDeclarationContainer container = getDeclarationContainer(); + return container instanceof KmClass? ((KmClass)container).getSupertypes() : Collections.emptyList(); + } + public Visibility getContainerVisibility() { KmDeclarationContainer container = getDeclarationContainer(); return container instanceof KmClass? Attributes.getVisibility((KmClass)container) : Visibility.PUBLIC; diff --git a/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/kotlin/KotlinSubclassesIndex.java b/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/kotlin/KotlinSubclassesIndex.java new file mode 100644 index 000000000000..d106a322d0d8 --- /dev/null +++ b/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/kotlin/KotlinSubclassesIndex.java @@ -0,0 +1,65 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jps.dependency.kotlin; + +import kotlin.metadata.KmClassifier; +import kotlin.metadata.KmType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.dependency.MapletFactory; +import org.jetbrains.jps.dependency.Node; +import org.jetbrains.jps.dependency.ReferenceID; +import org.jetbrains.jps.dependency.impl.BackDependencyIndexImpl; +import org.jetbrains.jps.dependency.java.JvmClass; +import org.jetbrains.jps.dependency.java.JvmNodeReferenceID; +import org.jetbrains.jps.dependency.java.KotlinMeta; +import org.jetbrains.jps.util.Iterators; + +import java.util.Collections; +import java.util.Objects; + +/** + * KotlinSubclassesIndex back-dependency index for Kotlin classes that tracks direct subclass relationships. + * This index is used by the KotlinCompilerReferenceIndex. + * + *

The index is built based on Kotlin metadata and applies several filters: + *

+ */ +public class KotlinSubclassesIndex extends BackDependencyIndexImpl { + public static final String NAME = "kotlin-direct-subclasses"; + + public KotlinSubclassesIndex(@NotNull MapletFactory cFactory) { + super(NAME, cFactory); + } + + @Override + public Iterable getIndexedDependencies(@NotNull Node node) { + if (!(node instanceof JvmClass)) { + return Collections.emptyList(); + } + JvmClass classNode = (JvmClass)node; + + KotlinMeta kotlinMeta = KJvmUtils.getKotlinMeta(classNode); + if (kotlinMeta == null || kotlinMeta.getKind() != 1 // CLASS + || classNode.isLocal()) { + return Collections.emptyList(); + } + + Iterable fqNames = + Iterators.filter( + Iterators.map( + Iterators.filter( + Iterators.map(kotlinMeta.getSupertypes(), KmType::getClassifier), + kmClassifier -> kmClassifier instanceof KmClassifier.Class + ), + kmClassifier -> ((KmClassifier.Class)kmClassifier).getName() + ), fqName -> !Objects.equals(fqName, "kotlin/Any") + ); + + return Iterators.map(fqNames, name -> new JvmNodeReferenceID(name)); + } +} diff --git a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/StorageManager.java b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/StorageManager.java index 9c865d60ef68..e278dc61c8c3 100644 --- a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/StorageManager.java +++ b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/StorageManager.java @@ -13,6 +13,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.dependency.DependencyGraph; import org.jetbrains.jps.dependency.GraphConfiguration; import org.jetbrains.jps.dependency.impl.DependencyGraphImpl; +import org.jetbrains.jps.dependency.kotlin.KotlinSubclassesIndex; import org.jetbrains.jps.dependency.kotlin.LookupsIndex; import java.io.*; @@ -119,7 +120,7 @@ public class StorageManager implements CloseableExt { if (isKotlinCriDataGenerationEnabled) { return new DependencyGraphImpl( containerFactory, - DependencyGraphImpl.IndexFactory.create(LookupsIndex::new) + DependencyGraphImpl.IndexFactory.create(LookupsIndex::new, KotlinSubclassesIndex::new) ); } else { diff --git a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/KotlinCriUtil.kt b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/KotlinCriUtil.kt index 85c4ac11c739..b9cbb9984d4f 100644 --- a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/KotlinCriUtil.kt +++ b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/KotlinCriUtil.kt @@ -8,10 +8,10 @@ import org.jetbrains.jps.dependency.BackDependencyIndex import org.jetbrains.jps.dependency.DependencyGraph import org.jetbrains.jps.dependency.ReferenceID import org.jetbrains.jps.dependency.java.JvmNodeReferenceID -import org.jetbrains.jps.dependency.java.SubclassesIndex +import org.jetbrains.jps.dependency.kotlin.KotlinSubclassesIndex import org.jetbrains.jps.dependency.kotlin.LookupsIndex -const val VERSION = 3 +const val VERSION = 4 internal fun prepareSerializedData(graph: DependencyGraph): ByteArray { val serializedDataResult = ByteArrayOutputStream().use { byteStream -> @@ -19,10 +19,12 @@ internal fun prepareSerializedData(graph: DependencyGraph): ByteArray { dataOut.writeVersion() // process subtypes - val subclassesIndex: BackDependencyIndex? = graph.getIndex(SubclassesIndex.NAME) - val subclassesCount = subclassesIndex?.keys?.count() ?: 0 + val subclassesIndex: BackDependencyIndex? = graph.getIndex(KotlinSubclassesIndex.NAME) + val subclassesFilteredKeys = subclassesIndex?.keys + ?.filter { key -> subclassesIndex.getDependencies(key).iterator().hasNext() } + val subclassesCount = subclassesFilteredKeys?.count() ?: 0 dataOut.writeInt(subclassesCount) - subclassesIndex?.keys + subclassesFilteredKeys ?.sortedBy { it.toFqName() } ?.forEach { key -> val dependencies = subclassesIndex.getDependencies(key).map { it.toFqName() }.sorted() @@ -63,23 +65,27 @@ internal fun prepareSerializedData(graph: DependencyGraph): ByteArray { } /** - * Converts the current `ReferenceID` to its fully qualified name (FqName) representation. + * Converts a JVM reference ID to a dot-separated fully qualified name. * - * Example transformation: - * Input: `com/intellij/AppKt$foo$3$1` - * Output: `com.intellij.AppKt.foo` - * - * @return The fully qualified name as a `String`. + * Examples: + * - `com/example/Foo` → `com.example.Foo` + * - `com/example/Foo$Bar` → `com.example.Foo.Bar` + * - `com/example/Foo$1` → `com.example.Foo` + * - `com/example/Foo$$Lambda$3` → `com.example.Foo.$Lambda` */ private fun ReferenceID.toFqName(): String { - return (this as? JvmNodeReferenceID)?.nodeName - ?.replace('/', '.') - ?.replace(DOLLAR_DIGITS_SUFFIX_REGEX, "") - ?.replace('$', '.') - ?: this.toString() + val jvmName = (this as? JvmNodeReferenceID)?.nodeName ?: return toString() + + return jvmName + .replace('/', '.') + .replace("$$", DOUBLE_DOLLAR_PLACEHOLDER) + .replace(DOLLAR_DIGITS_SUFFIX_REGEX, "") + .replace('$', '.') + .replace(DOUBLE_DOLLAR_PLACEHOLDER, ".$") } private val DOLLAR_DIGITS_SUFFIX_REGEX = Regex("(?:\\$\\d+)+$") +private const val DOUBLE_DOLLAR_PLACEHOLDER = "\u0000DOUBLE_DOLLAR\u0000" private fun String.addFilePathIfNeeded(fileIdToPathEntryAccumulator: MutableMap): Int { return fileIdToPathEntryAccumulator.computeIfAbsent(this) { fileIdToPathEntryAccumulator.size + 1 }