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