FIR: Fix overridability rule for Java declarations with different return type kinds

See the class at org/jmock/Expectations
public <T> T with(Matcher<T> matcher);
public boolean with(Matcher<Boolean> matcher);

When we extending such class it we start assuming
that fake generic override overrides both of the overridden that is wrong
from POV of Java and it fails at FIR ultimate build

NB: It's hard to write a test because such Expectation-like
class is impossible to write in pure Java
This commit is contained in:
Denis.Zharkov
2021-10-28 11:38:20 +03:00
committed by TeamCityServer
parent 2e37ec6f0c
commit 360d67410d
10 changed files with 95 additions and 10 deletions
@@ -102,7 +102,9 @@ object JavaScopeProvider : FirScopeProvider() {
useSiteSession,
JavaOverrideChecker(
useSiteSession,
regularClass.javaTypeParameterStack
regularClass.javaTypeParameterStack,
baseScope = null,
considerReturnTypeKinds = false,
),
superTypeScopes,
regularClass.defaultType(),
@@ -23,7 +23,7 @@ class JavaClassStaticUseSiteScope internal constructor(
) : FirContainingNamesAwareScope() {
private val functions = hashMapOf<Name, Collection<FirNamedFunctionSymbol>>()
private val properties = hashMapOf<Name, Collection<FirVariableSymbol<*>>>()
private val overrideChecker = JavaOverrideChecker(session, javaTypeParameterStack)
private val overrideChecker = JavaOverrideChecker(session, javaTypeParameterStack, baseScope = null, considerReturnTypeKinds = false)
override fun processFunctionsByName(name: Name, processor: (FirNamedFunctionSymbol) -> Unit) {
functions.getOrPut(name) {
@@ -45,7 +45,7 @@ class JavaClassUseSiteMemberScope(
declaredMemberScope: FirContainingNamesAwareScope
) : AbstractFirUseSiteMemberScope(
session,
JavaOverrideChecker(session, klass.javaTypeParameterStack),
JavaOverrideChecker(session, klass.javaTypeParameterStack, superTypesScope, considerReturnTypeKinds = true),
superTypesScope,
declaredMemberScope
) {
@@ -16,15 +16,20 @@ import org.jetbrains.kotlin.fir.java.toConeKotlinTypeProbablyFlexible
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.resolve.substitution.substitutorByMap
import org.jetbrains.kotlin.fir.scopes.FirTypeScope
import org.jetbrains.kotlin.fir.scopes.ProcessorAction
import org.jetbrains.kotlin.fir.scopes.impl.FirAbstractOverrideChecker
import org.jetbrains.kotlin.fir.scopes.jvm.computeJvmDescriptorRepresentation
import org.jetbrains.kotlin.fir.scopes.processOverriddenFunctions
import org.jetbrains.kotlin.fir.symbols.ensureResolved
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.StandardClassIds
class JavaOverrideChecker internal constructor(
private val session: FirSession,
private val javaTypeParameterStack: JavaTypeParameterStack
private val javaTypeParameterStack: JavaTypeParameterStack,
private val baseScope: FirTypeScope?,
private val considerReturnTypeKinds: Boolean,
) : FirAbstractOverrideChecker() {
private val context: ConeTypeContext = session.typeContext
@@ -74,7 +79,7 @@ class JavaOverrideChecker internal constructor(
val candidateType = candidateTypeRef.toConeKotlinTypeProbablyFlexible(session, javaTypeParameterStack)
val baseType = baseTypeRef.toConeKotlinTypeProbablyFlexible(session, javaTypeParameterStack)
if (candidateType.isPrimitiveInJava() != baseType.isPrimitiveInJava()) return false
if (candidateType.isPrimitiveInJava(isReturnType = false) != baseType.isPrimitiveInJava(isReturnType = false)) return false
return isEqualTypes(
candidateType,
@@ -83,8 +88,50 @@ class JavaOverrideChecker internal constructor(
)
}
private fun ConeKotlinType.isPrimitiveInJava(): Boolean = with(context) {
!isNullableType() && CompilerConeAttributes.EnhancedNullability !in attributes && isPrimitiveOrNullablePrimitive
// In most cases checking erasure of value parameters should be enough, but in some cases there might be semi-valid Java hierarchies
// with same value parameters, but different return type kinds, so it's worth distinguishing them as different non-overridable members
private fun doesReturnTypesHaveSameKind(
candidate: FirSimpleFunction,
base: FirSimpleFunction,
): Boolean {
val candidateTypeRef = candidate.returnTypeRef
val baseTypeRef = base.returnTypeRef
val candidateType = candidateTypeRef.toConeKotlinTypeProbablyFlexible(session, javaTypeParameterStack)
val baseType = baseTypeRef.toConeKotlinTypeProbablyFlexible(session, javaTypeParameterStack)
val candidateHasPrimitiveReturnType = candidate.hasPrimitiveReturnTypeInJvm(candidateType)
if (candidateHasPrimitiveReturnType != base.hasPrimitiveReturnTypeInJvm(baseType)) return false
// Both candidate and base are not primitive
if (!candidateHasPrimitiveReturnType) return true
return (candidateType as? ConeClassLikeType)?.lookupTag == (baseType as? ConeClassLikeType)?.lookupTag
}
private fun ConeKotlinType.isPrimitiveInJava(isReturnType: Boolean): Boolean = with(context) {
if (isNullableType() || CompilerConeAttributes.EnhancedNullability in attributes) return false
val isVoid = isReturnType && isUnit
return isPrimitiveOrNullablePrimitive || isVoid
}
private fun FirSimpleFunction.hasPrimitiveReturnTypeInJvm(returnType: ConeKotlinType): Boolean {
if (!returnType.isPrimitiveInJava(isReturnType = true)) return false
var foundNonPrimitiveOverridden = false
baseScope?.processOverriddenFunctions(symbol) {
val type = it.fir.returnTypeRef.toConeKotlinTypeProbablyFlexible(session, javaTypeParameterStack)
if (!type.isPrimitiveInJava(isReturnType = true)) {
foundNonPrimitiveOverridden = true
ProcessorAction.STOP
} else {
ProcessorAction.NEXT
}
}
return !foundNonPrimitiveOverridden
}
private fun isEqualArrayElementTypeProjections(
@@ -176,9 +223,16 @@ class JavaOverrideChecker internal constructor(
if (overrideCandidate.valueParameters.size != baseParameterTypes.size) return false
val substitutor = buildTypeParametersSubstitutorIfCompatible(overrideCandidate, baseDeclaration)
return overrideCandidate.valueParameters.zip(baseParameterTypes).all { (paramFromJava, baseType) ->
isEqualTypes(paramFromJava.returnTypeRef, baseType, substitutor)
if (!overrideCandidate.valueParameters.zip(baseParameterTypes).all { (paramFromJava, baseType) ->
isEqualTypes(paramFromJava.returnTypeRef, baseType, substitutor)
}) {
return false
}
if (considerReturnTypeKinds && !doesReturnTypesHaveSameKind(overrideCandidate, baseDeclaration)) return false
return true
}
override fun isOverriddenProperty(overrideCandidate: FirCallableDeclaration, baseDeclaration: FirProperty): Boolean {