diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt index b3577858f99..aa18e2e0947 100644 --- a/java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt @@ -996,7 +996,19 @@ open class KotlinUsesExtractor( ) return null } - return extractFileClass(fqName) + val fileClassId = extractFileClass(fqName) + // Under K2, external file class members sit directly under IrExternalPackageFragment + // rather than under their IrClass parent. In that case the file class entity won't + // get a location set through the normal extractClassSource path. + if (d is IrMemberWithContainerSource && tw.lm.externalFileClassLocationsExtracted.add(fqName)) { + val binaryPath = + getContainerSourceBinaryPath(d.containerSource) + ?.let { normalizeExternalFileClassBinaryPath(it, fqName) } + ?: "/!unknown-binary-location/${fqName.asString().replace(".", "/")}.class" + val fileId = tw.mkFileId(binaryPath, true) + tw.writeHasLocation(fileClassId, tw.getWholeFileLocation(fileId)) + } + return fileClassId } return useDeclarationParent(parent, canBeTopLevel, classTypeArguments, inReceiverContext) } diff --git a/java/kotlin-extractor/src/main/kotlin/TrapWriter.kt b/java/kotlin-extractor/src/main/kotlin/TrapWriter.kt index 3ff4adb2eee..8b7b31a6628 100644 --- a/java/kotlin-extractor/src/main/kotlin/TrapWriter.kt +++ b/java/kotlin-extractor/src/main/kotlin/TrapWriter.kt @@ -51,6 +51,13 @@ class TrapLabelManager { * to avoid duplication. */ val fileClassLocationsExtracted = HashSet() + + /** + * Tracks external file classes (by FqName) whose location has been set from a binary path. + * Used to avoid writing duplicate hasLocation facts for external file class entities extracted + * through the K2 code path where declarations sit directly under IrExternalPackageFragment. + */ + val externalFileClassLocationsExtracted = HashSet() } /** diff --git a/java/kotlin-extractor/src/main/kotlin/utils/ClassNames.kt b/java/kotlin-extractor/src/main/kotlin/utils/ClassNames.kt index 97eb6d0bca4..d2e77c741f6 100644 --- a/java/kotlin-extractor/src/main/kotlin/utils/ClassNames.kt +++ b/java/kotlin-extractor/src/main/kotlin/utils/ClassNames.kt @@ -176,15 +176,56 @@ fun getIrDeclarationBinaryPath(d: IrDeclaration): String? { // This is in a file class. val fqName = getFileClassFqName(d) if (fqName != null) { + if (d is IrMemberWithContainerSource) { + val containerBinaryPath = getContainerSourceBinaryPath(d.containerSource) + if (containerBinaryPath != null) { + return normalizeExternalFileClassBinaryPath(containerBinaryPath, fqName) + } + } return getUnknownBinaryLocation(fqName.asString()) } } return null } +/** + * Attempts to get the binary file path from a container source (typically a + * [JvmPackagePartSource]). Returns null if the path is unavailable. + */ +fun getContainerSourceBinaryPath(containerSource: org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedContainerSource?): String? { + if (containerSource !is JvmPackagePartSource) return null + val binaryClass = containerSource.knownJvmBinaryClass ?: return null + return when (binaryClass) { + is VirtualFileKotlinClass -> { + val vf = binaryClass.file + val path = vf.path + if (vf.fileSystem.protocol == StandardFileSystems.JRT_PROTOCOL) + "/${path.split("!/", limit = 2)[1]}" + else path + } + else -> binaryClass.location.takeIf { it.isNotEmpty() } + } +} + private fun getUnknownBinaryLocation(s: String): String { return "/!unknown-binary-location/${s.replace(".", "/")}.class" } +fun normalizeExternalFileClassBinaryPath(path: String, fqName: FqName): String { + if (path.contains(".kotlinc_installed")) { + return getUnknownBinaryLocation(fqName.asString()) + } + val normalizedPath = path.replace('\\', '/') + val classInternalPath = "${fqName.asString().replace(".", "/")}.class" + val classSuffix = "/$classInternalPath" + if (normalizedPath.endsWith(classSuffix)) { + val classpathRoot = normalizedPath.removeSuffix(classSuffix).substringAfterLast('/') + if (classpathRoot.isNotEmpty()) { + return "$classpathRoot/$classInternalPath" + } + } + return path +} + fun getJavaEquivalentClassId(c: IrClass) = c.fqNameWhenAvailable?.toUnsafe()?.let { JavaToKotlinClassMap.mapKotlinToJava(it) }