diff --git a/compiler/fir/analysis-tests/testData/resolve/offOrderMultiBoundGenericOverride.kt b/compiler/fir/analysis-tests/testData/resolve/offOrderMultiBoundGenericOverride.kt new file mode 100644 index 00000000000..e0de3d4d18c --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/offOrderMultiBoundGenericOverride.kt @@ -0,0 +1,47 @@ +// FILE: Test.java +public class Test { + + public interface I1 {} + public interface I2 {} + public interface I3 {} + + public interface I123 extends I1, I2, I3 {} + + public static class Base { + public

void foo(P p) {} + } + + public static class Derived extends Base { + @Override + public

void foo(P p) {} + } + + public static class DerivedRaw extends Base { + public void foo(I1 p) {} + } +} + +// FILE: main.kt +interface KI1 +interface KI2 +interface KI12 : KI1, KI2 + +open class KBase { + open fun

foo() + where P : KI1, P : KI2 {} +} + +class KDerived : KBase() { + override fun

foo() + where P : KI2, P : KI1 {} +} + + +fun callJava(derived: Test.Derived, derivedRaw: Test.DerivedRaw, v: Test.I123) { + derived.foo(v) + derivedRaw.foo(v) +} + +fun callKotlin(derived: KDerived) { + derived.foo() +} \ No newline at end of file diff --git a/compiler/fir/analysis-tests/testData/resolve/offOrderMultiBoundGenericOverride.txt b/compiler/fir/analysis-tests/testData/resolve/offOrderMultiBoundGenericOverride.txt new file mode 100644 index 00000000000..bd5c39875af --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/offOrderMultiBoundGenericOverride.txt @@ -0,0 +1,32 @@ +FILE: main.kt + public abstract interface KI1 : R|kotlin/Any| { + } + public abstract interface KI2 : R|kotlin/Any| { + } + public abstract interface KI12 : R|KI1|, R|KI2| { + } + public open class KBase : R|kotlin/Any| { + public constructor(): R|KBase| { + super() + } + + public open fun

foo(): R|kotlin/Unit| { + } + + } + public final class KDerived : R|KBase| { + public constructor(): R|KDerived| { + super() + } + + public final override fun

foo(): R|kotlin/Unit| { + } + + } + public final fun callJava(derived: R|Test.Derived|, derivedRaw: R|Test.DerivedRaw|, v: R|Test.I123|): R|kotlin/Unit| { + R|/derived|.R|/Test.Derived.foo|!|>(R|/v|) + R|/derivedRaw|.R|/Test.DerivedRaw.foo|(R|/v|) + } + public final fun callKotlin(derived: R|KDerived|): R|kotlin/Unit| { + R|/derived|.R|/KDerived.foo|() + } diff --git a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java index 5f9cb3d2746..6658ede18e8 100644 --- a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java @@ -293,6 +293,11 @@ public class FirDiagnosticsTestGenerated extends AbstractFirDiagnosticsTest { runTest("compiler/fir/analysis-tests/testData/resolve/objectInnerClass.kt"); } + @TestMetadata("offOrderMultiBoundGenericOverride.kt") + public void testOffOrderMultiBoundGenericOverride() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/offOrderMultiBoundGenericOverride.kt"); + } + @TestMetadata("problems2.kt") public void testProblems2() throws Exception { runTest("compiler/fir/analysis-tests/testData/resolve/problems2.kt"); diff --git a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java index 23cf86a9cbc..e180d48d23f 100644 --- a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java @@ -293,6 +293,11 @@ public class FirDiagnosticsWithLightTreeTestGenerated extends AbstractFirDiagnos runTest("compiler/fir/analysis-tests/testData/resolve/objectInnerClass.kt"); } + @TestMetadata("offOrderMultiBoundGenericOverride.kt") + public void testOffOrderMultiBoundGenericOverride() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/offOrderMultiBoundGenericOverride.kt"); + } + @TestMetadata("problems2.kt") public void testProblems2() throws Exception { runTest("compiler/fir/analysis-tests/testData/resolve/problems2.kt"); diff --git a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/scopes/JavaOverrideChecker.kt b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/scopes/JavaOverrideChecker.kt index f0f4c27f58c..55a40ced65f 100644 --- a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/scopes/JavaOverrideChecker.kt +++ b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/scopes/JavaOverrideChecker.kt @@ -7,14 +7,12 @@ package org.jetbrains.kotlin.fir.java.scopes import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.fir.FirSession -import org.jetbrains.kotlin.fir.declarations.FirCallableMemberDeclaration -import org.jetbrains.kotlin.fir.declarations.FirProperty -import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction -import org.jetbrains.kotlin.fir.declarations.modality +import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.java.JavaTypeParameterStack import org.jetbrains.kotlin.fir.java.enhancement.readOnlyToMutable import org.jetbrains.kotlin.fir.java.toConeKotlinTypeProbablyFlexible import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor +import org.jetbrains.kotlin.fir.resolve.substitution.substitutorByMap import org.jetbrains.kotlin.fir.scopes.impl.FirAbstractOverrideChecker import org.jetbrains.kotlin.fir.typeContext import org.jetbrains.kotlin.fir.types.* @@ -54,13 +52,29 @@ class JavaOverrideChecker internal constructor( substitutor ) + override fun buildTypeParametersSubstitutorIfCompatible( + overrideCandidate: FirCallableMemberDeclaration<*>, + baseDeclaration: FirCallableMemberDeclaration<*> + ): ConeSubstitutor? { + + if (overrideCandidate.typeParameters.isEmpty() && baseDeclaration.typeParameters.isEmpty()) return ConeSubstitutor.Empty + + val typeParametersErasure = + (overrideCandidate.typeParameters + baseDeclaration.typeParameters).associate { + val symbol = it.symbol + val firstBound = symbol.fir.bounds.first() // Note that in Java type parameter typed arguments always erased to first bound + symbol to firstBound.toConeKotlinTypeProbablyFlexible(session, javaTypeParameterStack) + } + return substitutorByMap(typeParametersErasure) + } + override fun isOverriddenFunction(overrideCandidate: FirSimpleFunction, baseDeclaration: FirSimpleFunction): Boolean { // NB: overrideCandidate is from Java and has no receiver val receiverTypeRef = baseDeclaration.receiverTypeRef val baseParameterTypes = listOfNotNull(receiverTypeRef) + baseDeclaration.valueParameters.map { it.returnTypeRef } if (overrideCandidate.valueParameters.size != baseParameterTypes.size) return false - val substitutor = getSubstitutorIfTypeParametersAreCompatible(overrideCandidate, baseDeclaration) ?: return false + val substitutor = buildTypeParametersSubstitutorIfCompatible(overrideCandidate, baseDeclaration) ?: return false return overrideCandidate.valueParameters.zip(baseParameterTypes).all { (paramFromJava, baseType) -> isEqualTypes(paramFromJava.returnTypeRef, baseType, substitutor) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/scopes/impl/FirAbstractOverrideChecker.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/scopes/impl/FirAbstractOverrideChecker.kt index 191ff2d3eed..80c5c333fda 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/scopes/impl/FirAbstractOverrideChecker.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/scopes/impl/FirAbstractOverrideChecker.kt @@ -16,28 +16,10 @@ abstract class FirAbstractOverrideChecker : FirOverrideChecker { protected abstract fun isEqualTypes(candidateTypeRef: FirTypeRef, baseTypeRef: FirTypeRef, substitutor: ConeSubstitutor): Boolean - private fun isCompatibleTypeParameters( - overrideCandidate: FirTypeParameterRef, - baseDeclaration: FirTypeParameterRef, - substitutor: ConeSubstitutor - ): Boolean { - if (overrideCandidate.symbol == baseDeclaration.symbol) return true - if (overrideCandidate !is FirTypeParameter || baseDeclaration !is FirTypeParameter) return false - return overrideCandidate.bounds.zip(baseDeclaration.bounds).all { (aBound, bBound) -> isEqualTypes(aBound, bBound, substitutor) } - } - - protected fun getSubstitutorIfTypeParametersAreCompatible( + protected abstract fun buildTypeParametersSubstitutorIfCompatible( overrideCandidate: FirCallableMemberDeclaration<*>, baseDeclaration: FirCallableMemberDeclaration<*> - ): ConeSubstitutor? { - val substitutor = buildSubstitutorForOverridesCheck(overrideCandidate, baseDeclaration) ?: return null - if ( - overrideCandidate.typeParameters.zip(baseDeclaration.typeParameters).any { (override, base) -> - !isCompatibleTypeParameters(override, base, substitutor) - } - ) return null - return substitutor - } + ): ConeSubstitutor? } fun buildSubstitutorForOverridesCheck( diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/scopes/impl/FirStandardOverrideChecker.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/scopes/impl/FirStandardOverrideChecker.kt index 9dcc8157602..944b29fd479 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/scopes/impl/FirStandardOverrideChecker.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/scopes/impl/FirStandardOverrideChecker.kt @@ -6,9 +6,7 @@ package org.jetbrains.kotlin.fir.scopes.impl import org.jetbrains.kotlin.fir.FirSession -import org.jetbrains.kotlin.fir.declarations.FirCallableMemberDeclaration -import org.jetbrains.kotlin.fir.declarations.FirProperty -import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction +import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor import org.jetbrains.kotlin.fir.typeContext import org.jetbrains.kotlin.fir.types.* @@ -20,9 +18,7 @@ class FirStandardOverrideChecker(session: FirSession) : FirAbstractOverrideCheck private val context: ConeTypeContext = session.typeContext - private fun isEqualTypes(candidateType: ConeKotlinType, baseType: ConeKotlinType, substitutor: ConeSubstitutor): Boolean { - val substitutedCandidateType = substitutor.substituteOrSelf(candidateType) - val substitutedBaseType = substitutor.substituteOrSelf(baseType) + private fun isEqualTypes(substitutedCandidateType: ConeKotlinType, substitutedBaseType: ConeKotlinType): Boolean { return with(context) { val baseIsFlexible = substitutedBaseType.isFlexible() val candidateIsFlexible = substitutedCandidateType.isFlexible() @@ -45,9 +41,62 @@ class FirStandardOverrideChecker(session: FirSession) : FirAbstractOverrideCheck } } + private fun isEqualTypes(candidateType: ConeKotlinType, baseType: ConeKotlinType, substitutor: ConeSubstitutor): Boolean { + val substitutedCandidateType = substitutor.substituteOrSelf(candidateType) + val substitutedBaseType = substitutor.substituteOrSelf(baseType) + return isEqualTypes(substitutedCandidateType, substitutedBaseType) + } + override fun isEqualTypes(candidateTypeRef: FirTypeRef, baseTypeRef: FirTypeRef, substitutor: ConeSubstitutor) = isEqualTypes((candidateTypeRef as FirResolvedTypeRef).type, (baseTypeRef as FirResolvedTypeRef).type, substitutor) + + /** + * Good case complexity is O(1) + * Worst case complexity is O(N), where N is number of type-parameter bound's + */ + private fun isEqualBound( + overrideBound: FirTypeRef, + baseBound: FirTypeRef, + overrideTypeParameter: FirTypeParameter, + baseTypeParameter: FirTypeParameter, + substitutor: ConeSubstitutor + ): Boolean { + val substitutedOverrideType = substitutor.substituteOrSelf(overrideBound.coneTypeUnsafe()) + val substitutedBaseType = substitutor.substituteOrSelf(baseBound.coneTypeUnsafe()) + + if (isEqualTypes(substitutedOverrideType, substitutedBaseType)) return true + + return overrideTypeParameter.bounds.any { bound -> isEqualTypes(bound.coneTypeUnsafe(), substitutedBaseType, substitutor) } && + baseTypeParameter.bounds.any { bound -> isEqualTypes(bound.coneTypeUnsafe(), substitutedOverrideType, substitutor) } + } + + private fun isCompatibleTypeParameters( + overrideCandidate: FirTypeParameterRef, + baseDeclaration: FirTypeParameterRef, + substitutor: ConeSubstitutor + ): Boolean { + if (overrideCandidate.symbol == baseDeclaration.symbol) return true + if (overrideCandidate !is FirTypeParameter || baseDeclaration !is FirTypeParameter) return false + if (overrideCandidate.bounds.size != baseDeclaration.bounds.size) return false + return overrideCandidate.bounds.zip(baseDeclaration.bounds) + .all { (aBound, bBound) -> isEqualBound(aBound, bBound, overrideCandidate, baseDeclaration, substitutor) } + } + + override fun buildTypeParametersSubstitutorIfCompatible( + overrideCandidate: FirCallableMemberDeclaration<*>, + baseDeclaration: FirCallableMemberDeclaration<*> + ): ConeSubstitutor? { + val substitutor = buildSubstitutorForOverridesCheck(overrideCandidate, baseDeclaration) ?: return null + if ( + overrideCandidate.typeParameters.isNotEmpty() && + overrideCandidate.typeParameters.zip(baseDeclaration.typeParameters).any { (override, base) -> + !isCompatibleTypeParameters(override, base, substitutor) + } + ) return null + return substitutor + } + private fun isEqualReceiverTypes(candidateTypeRef: FirTypeRef?, baseTypeRef: FirTypeRef?, substitutor: ConeSubstitutor): Boolean { return when { candidateTypeRef != null && baseTypeRef != null -> isEqualTypes(candidateTypeRef, baseTypeRef, substitutor) @@ -58,7 +107,7 @@ class FirStandardOverrideChecker(session: FirSession) : FirAbstractOverrideCheck override fun isOverriddenFunction(overrideCandidate: FirSimpleFunction, baseDeclaration: FirSimpleFunction): Boolean { if (overrideCandidate.valueParameters.size != baseDeclaration.valueParameters.size) return false - val substitutor = getSubstitutorIfTypeParametersAreCompatible(overrideCandidate, baseDeclaration) ?: return false + val substitutor = buildTypeParametersSubstitutorIfCompatible(overrideCandidate, baseDeclaration) ?: return false if (!isEqualReceiverTypes(overrideCandidate.receiverTypeRef, baseDeclaration.receiverTypeRef, substitutor)) return false @@ -73,7 +122,7 @@ class FirStandardOverrideChecker(session: FirSession) : FirAbstractOverrideCheck baseDeclaration: FirProperty ): Boolean { if (overrideCandidate !is FirProperty) return false - val substitutor = getSubstitutorIfTypeParametersAreCompatible(overrideCandidate, baseDeclaration) ?: return false + val substitutor = buildTypeParametersSubstitutorIfCompatible(overrideCandidate, baseDeclaration) ?: return false return isEqualReceiverTypes(overrideCandidate.receiverTypeRef, baseDeclaration.receiverTypeRef, substitutor) } } \ No newline at end of file