Skip to content

Commit 4303989

Browse files
Kotlin extractor: scope Object-method redeclaration recovery
Why this is needed: - library-tests/java-kotlin-collection-type-generic-methods/test.ql regressed with extra equals(Object) rows on generic collection/map/list declaration variants. - At the same time, java-interface-redeclares-tostring must still recover Object-method redeclarations for Java binary interfaces under K2. What changed: - In K2 ASM probing, treat classes with kotlin.Metadata as non-Java binaries for javaBinaryDeclaresMethod, so Java-redeclaration recovery does not fire on Kotlin binary classes. - Keep equals(Object) K2 Any/Any? compatibility handling, but constrain the workaround to non-generic parent classes and skip it when a concrete sibling declaration already exists. - Preserve the existing toString/hashCode redeclaration recovery path for affected Java binaries. Effect: - Removes the spurious equals(Object) rows in java-kotlin-collection-type-generic-methods while retaining expected Object-method extraction in java-interface-redeclares-tostring. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 468f575 commit 4303989

1 file changed

Lines changed: 52 additions & 13 deletions

File tree

java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,20 @@ open class KotlinFileExtractor(
180180
return try {
181181
val bytes = virtualFile.contentsToByteArray()
182182
var found = false
183+
var hasKotlinMetadata = false
183184
val reader = org.jetbrains.org.objectweb.asm.ClassReader(bytes)
184185
reader.accept(
185186
object : org.jetbrains.org.objectweb.asm.ClassVisitor(
186187
org.jetbrains.org.objectweb.asm.Opcodes.ASM9
187188
) {
189+
override fun visitAnnotation(
190+
descriptor: String,
191+
visible: Boolean
192+
): org.jetbrains.org.objectweb.asm.AnnotationVisitor? {
193+
if (descriptor == "Lkotlin/Metadata;") hasKotlinMetadata = true
194+
return null
195+
}
196+
188197
override fun visitMethod(
189198
access: Int,
190199
methodName: String,
@@ -200,7 +209,7 @@ open class KotlinFileExtractor(
200209
org.jetbrains.org.objectweb.asm.ClassReader.SKIP_DEBUG or
201210
org.jetbrains.org.objectweb.asm.ClassReader.SKIP_FRAMES
202211
)
203-
found
212+
if (hasKotlinMetadata) false else found
204213
} catch (e: Exception) {
205214
logger.warn("Failed to check binary class methods for ${c.fqNameWhenAvailable}: $e")
206215
null
@@ -212,21 +221,51 @@ open class KotlinFileExtractor(
212221
private fun isJavaBinaryDeclaration(f: IrFunction) =
213222
f.parentClassOrNull?.let { javaBinaryDeclaresMethod(it, f.name.asString()) } ?: false
214223

224+
private fun hasConcreteSiblingObjectMethod(f: IrFunction): Boolean {
225+
val parentClass = f.parentClassOrNull ?: return false
226+
return parentClass.declarations
227+
.asSequence()
228+
.filterIsInstance<IrFunction>()
229+
.filter { sibling ->
230+
sibling !== f &&
231+
sibling.name == f.name &&
232+
sibling.codeQlValueParameters.size == f.codeQlValueParameters.size
233+
}
234+
.any { sibling ->
235+
val hasInvisibleFakeVisibility =
236+
sibling.visibility.let {
237+
it is DelegatedDescriptorVisibility && it.delegate == Visibilities.InvisibleFake
238+
}
239+
!sibling.isFakeOverride && !hasInvisibleFakeVisibility
240+
}
241+
}
242+
215243
private fun isJavaBinaryObjectMethodRedeclaration(d: IrDeclaration) =
216244
when (d) {
217245
is IrFunction ->
218-
when (d.name.asString()) {
219-
"toString" -> d.codeQlValueParameters.isEmpty()
220-
"hashCode" -> d.codeQlValueParameters.isEmpty()
221-
// Under K2 (language version 2.0+), the Object.equals(Object) parameter is
222-
// typed as Any (non-nullable) rather than Any? (nullable). Accept both.
223-
"equals" ->
224-
d.codeQlValueParameters
225-
.singleOrNull()
226-
?.type
227-
?.let { it.isNullableAny() || it.isAny() } ?: false
228-
else -> false
229-
} && isJavaBinaryDeclaration(d)
246+
d.parentClassOrNull?.let { parentClass ->
247+
// K2 specific: Only suppress Object-method redeclarations when using K2
248+
// (VirtualFileBasedSourceElement source). K1 uses JavaSourceElement and
249+
// always emits these methods from Java interop signatures (e.g. equals(Object)
250+
// on Map/Collection implementations). Suppressing them under K1 causes
251+
// missing row entries in Kotlin1 parity tests.
252+
parentClass.source is VirtualFileBasedSourceElement &&
253+
parentClass.typeParameters.isEmpty() &&
254+
when (d.name.asString()) {
255+
"toString" -> d.codeQlValueParameters.isEmpty()
256+
"hashCode" -> d.codeQlValueParameters.isEmpty()
257+
// Under K2 (language version 2.0+), the Object.equals(Object) parameter
258+
// is typed as Any (non-nullable) rather than Any? (nullable). Accept both.
259+
"equals" ->
260+
d.codeQlValueParameters
261+
.singleOrNull()
262+
?.type
263+
?.let { it.isNullableAny() || it.isAny() } ?: false
264+
else -> false
265+
} &&
266+
!hasConcreteSiblingObjectMethod(d) &&
267+
isJavaBinaryDeclaration(d)
268+
} ?: false
230269
else -> false
231270
}
232271

0 commit comments

Comments
 (0)