diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java index 5949ca3821c..3def3ef1e90 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java @@ -29621,6 +29621,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/smartCasts/intersectionScope"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true); } + @Test + @TestMetadata("capturedSpecificity.kt") + public void testCapturedSpecificity() throws Exception { + runTest("compiler/testData/diagnostics/tests/smartCasts/intersectionScope/capturedSpecificity.kt"); + } + @Test @TestMetadata("conflictTypeParameters.kt") public void testConflictTypeParameters() throws Exception { @@ -29639,6 +29645,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag runTest("compiler/testData/diagnostics/tests/smartCasts/intersectionScope/flexibleTypes.kt"); } + @Test + @TestMetadata("intersectReturnType.kt") + public void testIntersectReturnType() throws Exception { + runTest("compiler/testData/diagnostics/tests/smartCasts/intersectionScope/intersectReturnType.kt"); + } + @Test @TestMetadata("moreSpecificSetter.kt") public void testMoreSpecificSetter() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java index 87b2ff34b00..42c5ec4b013 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java @@ -29621,6 +29621,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/smartCasts/intersectionScope"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true); } + @Test + @TestMetadata("capturedSpecificity.kt") + public void testCapturedSpecificity() throws Exception { + runTest("compiler/testData/diagnostics/tests/smartCasts/intersectionScope/capturedSpecificity.kt"); + } + @Test @TestMetadata("conflictTypeParameters.kt") public void testConflictTypeParameters() throws Exception { @@ -29639,6 +29645,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti runTest("compiler/testData/diagnostics/tests/smartCasts/intersectionScope/flexibleTypes.kt"); } + @Test + @TestMetadata("intersectReturnType.kt") + public void testIntersectReturnType() throws Exception { + runTest("compiler/testData/diagnostics/tests/smartCasts/intersectionScope/intersectReturnType.kt"); + } + @Test @TestMetadata("moreSpecificSetter.kt") public void testMoreSpecificSetter() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java index db07df84360..0697929632d 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java @@ -29621,6 +29621,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/smartCasts/intersectionScope"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true); } + @Test + @TestMetadata("capturedSpecificity.kt") + public void testCapturedSpecificity() throws Exception { + runTest("compiler/testData/diagnostics/tests/smartCasts/intersectionScope/capturedSpecificity.kt"); + } + @Test @TestMetadata("conflictTypeParameters.kt") public void testConflictTypeParameters() throws Exception { @@ -29639,6 +29645,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac runTest("compiler/testData/diagnostics/tests/smartCasts/intersectionScope/flexibleTypes.kt"); } + @Test + @TestMetadata("intersectReturnType.kt") + public void testIntersectReturnType() throws Exception { + runTest("compiler/testData/diagnostics/tests/smartCasts/intersectionScope/intersectReturnType.kt"); + } + @Test @TestMetadata("moreSpecificSetter.kt") public void testMoreSpecificSetter() throws Exception { diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/AbstractFirUseSiteMemberScope.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/AbstractFirUseSiteMemberScope.kt index 90b91646bd1..2b1129317d1 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/AbstractFirUseSiteMemberScope.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/AbstractFirUseSiteMemberScope.kt @@ -22,7 +22,8 @@ abstract class AbstractFirUseSiteMemberScope( dispatchReceiverType: ConeSimpleKotlinType, protected val declaredMemberScope: FirContainingNamesAwareScope ) : AbstractFirOverrideScope(session, overrideChecker) { - protected val supertypeScopeContext = FirTypeIntersectionScopeContext(session, overrideChecker, superTypeScopes, dispatchReceiverType) + protected val supertypeScopeContext = + FirTypeIntersectionScopeContext(session, overrideChecker, superTypeScopes, dispatchReceiverType, forSubtyping = true) private val functions: MutableMap> = hashMapOf() diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirTypeIntersectionScope.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirTypeIntersectionScope.kt index 1baaafa6e0c..e2e9b2c217b 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirTypeIntersectionScope.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirTypeIntersectionScope.kt @@ -18,7 +18,8 @@ class FirTypeIntersectionScope private constructor( private val scopes: List, dispatchReceiverType: ConeSimpleKotlinType, ) : AbstractFirOverrideScope(session, overrideChecker) { - private val intersectionContext = FirTypeIntersectionScopeContext(session, overrideChecker, scopes, dispatchReceiverType) + private val intersectionContext = + FirTypeIntersectionScopeContext(session, overrideChecker, scopes, dispatchReceiverType, forSubtyping = false) private val absentFunctions: MutableSet = mutableSetOf() private val absentProperties: MutableSet = mutableSetOf() diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirTypeIntersectionScopeContext.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirTypeIntersectionScopeContext.kt index fec9bebb98e..fb14bc86dba 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirTypeIntersectionScopeContext.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirTypeIntersectionScopeContext.kt @@ -21,9 +21,7 @@ import org.jetbrains.kotlin.fir.resolve.transformers.ReturnTypeCalculatorForFull import org.jetbrains.kotlin.fir.scopes.* import org.jetbrains.kotlin.fir.scopes.impl.FirTypeIntersectionScopeContext.ResultOfIntersection import org.jetbrains.kotlin.fir.symbols.impl.* -import org.jetbrains.kotlin.fir.types.ConeKotlinType -import org.jetbrains.kotlin.fir.types.ConeSimpleKotlinType -import org.jetbrains.kotlin.fir.types.classId +import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.name.CallableId import org.jetbrains.kotlin.name.Name import kotlin.contracts.ExperimentalContracts @@ -36,6 +34,7 @@ class FirTypeIntersectionScopeContext( private val overrideChecker: FirOverrideChecker, val scopes: List, private val dispatchReceiverType: ConeSimpleKotlinType, + private val forSubtyping: Boolean, ) { private val overrideService = session.overrideService @@ -164,15 +163,12 @@ class FirTypeIntersectionScopeContext( }.takeIf { it.isNotEmpty() } ?: extractBothWaysWithPrivate val baseMembersForIntersection = extractedOverrides.calcBaseMembersForIntersectionOverride() if (baseMembersForIntersection.size > 1) { - result += ResultOfIntersection.NonTrivial( - this, - overrideService.selectMostSpecificMembers(baseMembersForIntersection, ReturnTypeCalculatorForFullBodyResolve), - extractedOverrides, - containingScope = null - ) + val mostSpecific = + overrideService.selectMostSpecificMembers(baseMembersForIntersection, ReturnTypeCalculatorForFullBodyResolve) + result += ResultOfIntersection.NonTrivial(this, mostSpecific, extractedOverrides, containingScope = null) } else { - val (mostSpecific, containingScope) = baseMembersForIntersection.single() - result += ResultOfIntersection.SingleMember(mostSpecific, extractedOverrides, containingScope) + val (member, containingScope) = baseMembersForIntersection.single() + result += ResultOfIntersection.SingleMember(member, extractedOverrides, containingScope) } } @@ -215,7 +211,10 @@ class FirTypeIntersectionScopeContext( // we should just take most specific member without creating intersection // A typical sample here is inheritance of the same class in different places of hierarchy if (unwrappedMemberSet.size == 1) { - return listOf(overrideService.selectMostSpecificMember(this, ReturnTypeCalculatorForFullBodyResolve)) + return when { + forSubtyping -> listOf(overrideService.selectMostSpecificMember(this, ReturnTypeCalculatorForFullBodyResolve)) + else -> this + } } val baseMembers = mutableSetOf() @@ -239,10 +238,7 @@ class FirTypeIntersectionScopeContext( } } } - - val result = this.toMutableList() - result.removeIf { (member, _) -> member.fir.unwrapSubstitutionOverrides().symbol in baseMembers } - return result + return filter { it.member.fir.unwrapSubstitutionOverrides().symbol !in baseMembers } } private fun > findMemberWithMaxVisibility(members: Collection>): MemberWithBaseScope { @@ -381,6 +377,7 @@ class FirTypeIntersectionScopeContext( newModality = newModality, newVisibility = newVisibility, newDispatchReceiverType = dispatchReceiverType, + newReturnType = if (!forSubtyping) intersectReturnTypes(mostSpecific) else null, ).apply { originalForIntersectionOverrideAttr = keyFir } @@ -405,11 +402,30 @@ class FirTypeIntersectionScopeContext( newModality = newModality, newVisibility = newVisibility, newDispatchReceiverType = dispatchReceiverType, + // If any of the properties are vars and the types are not equal, these declarations are conflicting + // anyway and their uses should result in an overload resolution error. + newReturnType = if (!forSubtyping && !mostSpecific.any { (it as FirPropertySymbol).fir.isVar }) + intersectReturnTypes(mostSpecific) + else + null, ).apply { originalForIntersectionOverrideAttr = keyFir } return newSymbol } + + private fun intersectReturnTypes(overrides: Collection>): ConeKotlinType? { + val key = overrides.first() + // Remap type parameters to the first declaration's: + // (fun foo(): B) & (fun foo(): D?) -> (fun foo(): B & B?) + val substituted = overrides.mapNotNull { + val returnType = it.fir.returnTypeRef.coneTypeSafe() + if (it == key) return@mapNotNull returnType + val substitutor = buildSubstitutorForOverridesCheck(it.fir, key.fir, session) ?: return@mapNotNull null + returnType?.let(substitutor::substituteOrSelf) + } + return if (substituted.isNotEmpty()) session.typeContext.intersectTypes(substituted) else null + } } private fun > D.withScope(baseScope: FirTypeScope) = MemberWithBaseScope(this, baseScope) diff --git a/compiler/testData/diagnostics/tests/smartCasts/intersectionScope/capturedSpecificity.kt b/compiler/testData/diagnostics/tests/smartCasts/intersectionScope/capturedSpecificity.kt new file mode 100644 index 00000000000..19b9e712a0a --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/intersectionScope/capturedSpecificity.kt @@ -0,0 +1,14 @@ +// FIR_IDENTICAL +// SKIP_TXT +class C(val value: T?) + +fun assignable(x: () -> T) {} + +fun foo(t: C, v: C) { + assignable { v.value } + if (t == v) { + // `value: CapturedType(out V)?` <: `value: CapturedType(out Any)?` - same instantiation + assignable { v.value } + assignable { t.value } + } +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/intersectionScope/intersectReturnType.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/intersectionScope/intersectReturnType.fir.kt new file mode 100644 index 00000000000..90131c8c6d0 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/intersectionScope/intersectReturnType.fir.kt @@ -0,0 +1,17 @@ +// SKIP_TXT +class C(val value: T) + +fun assignable(x: () -> T) {} + +fun foo(t: C, v: C) { + assignable { t.value } // sure + assignable { v.value } // obviously + if (t == v) { + // => {t,v} is C & C + // => {t,v}.value is T & V + assignable { t.value } + assignable { v.value } + assignable { v.value } + assignable { t.value } + } +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/intersectionScope/intersectReturnType.kt b/compiler/testData/diagnostics/tests/smartCasts/intersectionScope/intersectReturnType.kt new file mode 100644 index 00000000000..f552c168ed9 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/intersectionScope/intersectReturnType.kt @@ -0,0 +1,17 @@ +// SKIP_TXT +class C(val value: T) + +fun assignable(x: () -> T) {} + +fun foo(t: C, v: C) { + assignable { t.value } // sure + assignable { v.value } // obviously + if (t == v) { + // => {t,v} is C & C + // => {t,v}.value is T & V + assignable { t.value } + assignable { v.value } + assignable { v.value } + assignable { t.value } + } +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java index ee8b2ffe3b7..5ba4e58774b 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java @@ -29711,6 +29711,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/smartCasts/intersectionScope"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true); } + @Test + @TestMetadata("capturedSpecificity.kt") + public void testCapturedSpecificity() throws Exception { + runTest("compiler/testData/diagnostics/tests/smartCasts/intersectionScope/capturedSpecificity.kt"); + } + @Test @TestMetadata("conflictTypeParameters.kt") public void testConflictTypeParameters() throws Exception { @@ -29729,6 +29735,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/smartCasts/intersectionScope/flexibleTypes.kt"); } + @Test + @TestMetadata("intersectReturnType.kt") + public void testIntersectReturnType() throws Exception { + runTest("compiler/testData/diagnostics/tests/smartCasts/intersectionScope/intersectReturnType.kt"); + } + @Test @TestMetadata("moreSpecificSetter.kt") public void testMoreSpecificSetter() throws Exception { diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/72.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/72.fir.kt index 2c88fd9873a..1141fb35b16 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/72.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/72.fir.kt @@ -12,5 +12,5 @@ fun case_1() { val strs = list as MutableList strs.add("two") & kotlin.collections.MutableList & kotlin.collections.MutableList")!>list - val s: String = & kotlin.collections.MutableList & kotlin.collections.MutableList")!>list[0] + val s: String = & kotlin.collections.MutableList & kotlin.collections.MutableList")!>list[0] }