K2: resolve remove(Int) clash in JavaOverrideChecker

In Kotlin subclasses of `MutableCollection<Int>`, the method
`remove(Int)` has its argument boxed, so that it wouldn't clash with the
method from `java.util.List`. So `JavaOverrideChecker` should understand
that a Java method `boolean remove(java.lang.Integer)` overrides it,
otherwise platform declaration clash was reported.

The code is adapted from `forceSingleValueParameterBoxing` in K1's
`methodSignatureMapping.kt`.

The test has been moved and adapted from diagnostic to codegen box
tests, to check correct backend execution + runtime.

 #KT-62316 Fixed
This commit is contained in:
Alexander Udalov
2023-10-06 12:33:55 +02:00
committed by Space Team
parent 8e023edc4f
commit 2788dcb5ff
17 changed files with 203 additions and 268 deletions
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.java.scopes
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.containingClassLookupTag
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.isStatic
import org.jetbrains.kotlin.fir.declarations.utils.modality
@@ -76,18 +77,17 @@ class JavaOverrideChecker internal constructor(
private fun isEqualTypes(
candidateTypeRef: FirTypeRef,
baseTypeRef: FirTypeRef,
substitutor: ConeSubstitutor
substitutor: ConeSubstitutor,
forceBoxCandidateType: Boolean,
forceBoxBaseType: Boolean,
): Boolean {
val candidateType = candidateTypeRef.toConeKotlinTypeProbablyFlexible(session, javaTypeParameterStack)
val baseType = baseTypeRef.toConeKotlinTypeProbablyFlexible(session, javaTypeParameterStack)
if (candidateType.isPrimitiveInJava(isReturnType = false) != baseType.isPrimitiveInJava(isReturnType = false)) return false
val candidateTypeIsPrimitive = !forceBoxCandidateType && candidateType.isPrimitiveInJava(isReturnType = false)
val baseTypeIsPrimitive = !forceBoxBaseType && baseType.isPrimitiveInJava(isReturnType = false)
return isEqualTypes(
candidateType,
baseType,
substitutor
)
return candidateTypeIsPrimitive == baseTypeIsPrimitive && isEqualTypes(candidateType, baseType, substitutor)
}
// In most cases checking erasure of value parameters should be enough, but in some cases there might be semi-valid Java hierarchies
@@ -241,8 +241,11 @@ class JavaOverrideChecker internal constructor(
if (overrideCandidate.valueParameters.size != baseParameterTypes.size) return false
val substitutor = buildTypeParametersSubstitutorIfCompatible(overrideCandidate, baseDeclaration)
val forceBoxOverrideParameterType = forceSingleValueParameterBoxing(overrideCandidate)
val forceBoxBaseParameterType = forceSingleValueParameterBoxing(baseDeclaration)
if (!overrideCandidate.valueParameters.zip(baseParameterTypes).all { (paramFromJava, baseType) ->
isEqualTypes(paramFromJava.returnTypeRef, baseType, substitutor)
isEqualTypes(paramFromJava.returnTypeRef, baseType, substitutor, forceBoxOverrideParameterType, forceBoxBaseParameterType)
}) {
return false
}
@@ -266,7 +269,10 @@ class JavaOverrideChecker internal constructor(
return overrideCandidate.valueParameters.isEmpty()
} else {
if (overrideCandidate.valueParameters.size != 1) return false
return isEqualTypes(receiverTypeRef, overrideCandidate.valueParameters.single().returnTypeRef, ConeSubstitutor.Empty)
return isEqualTypes(
receiverTypeRef, overrideCandidate.valueParameters.single().returnTypeRef, ConeSubstitutor.Empty,
forceBoxCandidateType = false, forceBoxBaseType = false
)
}
}
is FirProperty -> {
@@ -274,11 +280,40 @@ class JavaOverrideChecker internal constructor(
return when {
receiverTypeRef == null -> overrideReceiverTypeRef == null
overrideReceiverTypeRef == null -> false
else -> isEqualTypes(receiverTypeRef, overrideReceiverTypeRef, ConeSubstitutor.Empty)
else -> isEqualTypes(
receiverTypeRef, overrideReceiverTypeRef, ConeSubstitutor.Empty,
forceBoxCandidateType = false, forceBoxBaseType = false
)
}
}
else -> false
}
}
// Boxing is only necessary for 'remove(E): Boolean' of a MutableCollection<Int> implementation.
// Otherwise this method might clash with 'remove(I): E' defined in the java.util.List JDK interface (mapped to kotlin 'removeAt').
// As in the K1 implementation in `methodSignatureMapping.kt`, we're checking if the method has `MutableCollection.remove`
// in its overridden symbols.
private fun forceSingleValueParameterBoxing(function: FirSimpleFunction): Boolean {
if (function.name.asString() != "remove" || function.receiverParameter != null || function.contextReceivers.isNotEmpty())
return false
val parameter = function.valueParameters.singleOrNull() ?: return false
val parameterConeType = parameter.returnTypeRef.toConeKotlinTypeProbablyFlexible(session, javaTypeParameterStack)
if (!parameterConeType.fullyExpandedType(session).lowerBoundIfFlexible().isInt) return false
var overridesMutableCollectionRemove = false
baseScopes?.processOverriddenFunctions(function.symbol) {
if (it.fir.containingClassLookupTag() == StandardClassIds.MutableCollection.toLookupTag()) {
overridesMutableCollectionRemove = true
ProcessorAction.STOP
} else {
ProcessorAction.NEXT
}
}
return overridesMutableCollectionRemove
}
}