diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFE10TestdataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFE10TestdataTestGenerated.java index ecd27b140a6..27b1e7f2eee 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFE10TestdataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFE10TestdataTestGenerated.java @@ -15417,6 +15417,12 @@ public class DiagnosticCompilerTestFE10TestdataTestGenerated extends AbstractDia runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/approximateContravariantCapturedTypes.kt"); } + @Test + @TestMetadata("approximationLeavesNonTrivialLowerBound.kt") + public void testApproximationLeavesNonTrivialLowerBound() throws Exception { + runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/approximationLeavesNonTrivialLowerBound.kt"); + } + @Test @TestMetadata("avoidCreatingUselessCapturedTypes.kt") public void testAvoidCreatingUselessCapturedTypes() throws Exception { @@ -15441,6 +15447,18 @@ public class DiagnosticCompilerTestFE10TestdataTestGenerated extends AbstractDia runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/captureForPlatformTypes.kt"); } + @Test + @TestMetadata("captureFromNullableTypeInScope.kt") + public void testCaptureFromNullableTypeInScope() throws Exception { + runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/captureFromNullableTypeInScope.kt"); + } + + @Test + @TestMetadata("captureFromNullableTypeInScopeAny.kt") + public void testCaptureFromNullableTypeInScopeAny() throws Exception { + runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/captureFromNullableTypeInScopeAny.kt"); + } + @Test @TestMetadata("captureFromNullableTypeVariable.kt") public void testCaptureFromNullableTypeVariable() throws Exception { diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated.java index 0fa796327aa..985fc6ea386 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated.java @@ -15417,6 +15417,12 @@ public class LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/approximateContravariantCapturedTypes.kt"); } + @Test + @TestMetadata("approximationLeavesNonTrivialLowerBound.kt") + public void testApproximationLeavesNonTrivialLowerBound() throws Exception { + runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/approximationLeavesNonTrivialLowerBound.kt"); + } + @Test @TestMetadata("avoidCreatingUselessCapturedTypes.kt") public void testAvoidCreatingUselessCapturedTypes() throws Exception { @@ -15441,6 +15447,18 @@ public class LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/captureForPlatformTypes.kt"); } + @Test + @TestMetadata("captureFromNullableTypeInScope.kt") + public void testCaptureFromNullableTypeInScope() throws Exception { + runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/captureFromNullableTypeInScope.kt"); + } + + @Test + @TestMetadata("captureFromNullableTypeInScopeAny.kt") + public void testCaptureFromNullableTypeInScopeAny() throws Exception { + runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/captureFromNullableTypeInScopeAny.kt"); + } + @Test @TestMetadata("captureFromNullableTypeVariable.kt") public void testCaptureFromNullableTypeVariable() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java index e7ddde47253..21e5028dbc3 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java @@ -15417,6 +15417,12 @@ public class FirLightTreeOldFrontendDiagnosticsTestGenerated extends AbstractFir runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/approximateContravariantCapturedTypes.kt"); } + @Test + @TestMetadata("approximationLeavesNonTrivialLowerBound.kt") + public void testApproximationLeavesNonTrivialLowerBound() throws Exception { + runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/approximationLeavesNonTrivialLowerBound.kt"); + } + @Test @TestMetadata("avoidCreatingUselessCapturedTypes.kt") public void testAvoidCreatingUselessCapturedTypes() throws Exception { @@ -15441,6 +15447,18 @@ public class FirLightTreeOldFrontendDiagnosticsTestGenerated extends AbstractFir runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/captureForPlatformTypes.kt"); } + @Test + @TestMetadata("captureFromNullableTypeInScope.kt") + public void testCaptureFromNullableTypeInScope() throws Exception { + runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/captureFromNullableTypeInScope.kt"); + } + + @Test + @TestMetadata("captureFromNullableTypeInScopeAny.kt") + public void testCaptureFromNullableTypeInScopeAny() throws Exception { + runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/captureFromNullableTypeInScopeAny.kt"); + } + @Test @TestMetadata("captureFromNullableTypeVariable.kt") public void testCaptureFromNullableTypeVariable() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java index 99a4b957dea..45435fe951e 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java @@ -15423,6 +15423,12 @@ public class FirPsiOldFrontendDiagnosticsTestGenerated extends AbstractFirPsiDia runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/approximateContravariantCapturedTypes.kt"); } + @Test + @TestMetadata("approximationLeavesNonTrivialLowerBound.kt") + public void testApproximationLeavesNonTrivialLowerBound() throws Exception { + runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/approximationLeavesNonTrivialLowerBound.kt"); + } + @Test @TestMetadata("avoidCreatingUselessCapturedTypes.kt") public void testAvoidCreatingUselessCapturedTypes() throws Exception { @@ -15447,6 +15453,18 @@ public class FirPsiOldFrontendDiagnosticsTestGenerated extends AbstractFirPsiDia runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/captureForPlatformTypes.kt"); } + @Test + @TestMetadata("captureFromNullableTypeInScope.kt") + public void testCaptureFromNullableTypeInScope() throws Exception { + runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/captureFromNullableTypeInScope.kt"); + } + + @Test + @TestMetadata("captureFromNullableTypeInScopeAny.kt") + public void testCaptureFromNullableTypeInScopeAny() throws Exception { + runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/captureFromNullableTypeInScopeAny.kt"); + } + @Test @TestMetadata("captureFromNullableTypeVariable.kt") public void testCaptureFromNullableTypeVariable() throws Exception { diff --git a/compiler/resolution.common/src/org/jetbrains/kotlin/types/AbstractTypeApproximator.kt b/compiler/resolution.common/src/org/jetbrains/kotlin/types/AbstractTypeApproximator.kt index 89d10150e4a..a64ffcb50a6 100644 --- a/compiler/resolution.common/src/org/jetbrains/kotlin/types/AbstractTypeApproximator.kt +++ b/compiler/resolution.common/src/org/jetbrains/kotlin/types/AbstractTypeApproximator.kt @@ -531,25 +531,27 @@ abstract class AbstractTypeApproximator( continue@loop } - /** - * Example with non-trivial both type approximations: - * Inv> where C = in Int - * Inv> <: Inv> - * Inv> <: Inv> - * - * So such case is rare and we will chose Inv> for now. - * - * Note that for case Inv we will chose Inv, because it is more informative then Inv. - * May be we should do the same for deeper types, but not now. - */ + // In case of Inv and C = Captured(in Int), we choose Inv as resulting approximation + // NB: Inv <: Inv because Int <: C (as Int is a lower bound of the C captured type) + // + // That behavior of choosing non-trivial lower bound is crucial when there's also non-trivial upper bound, + // like if Inv would be declared as `interface Inv` (see test approximationLeavesNonTrivialLowerBound.kt) + // + // In that case the next condition after that doesn't help because in case of both non-trivial bounds, it chooses the upper one if (argumentType.typeConstructor().isCapturedTypeConstructor()) { val subType = approximateToSubType(argumentType, conf, depth) ?: continue@loop - if (!subType.isTrivialSub()) { + if (shouldUseSubTypeForCapturedArgument(subType, argumentType, conf, depth)) { newArguments[index] = createTypeArgument(subType, TypeVariance.IN) continue@loop } } + // Example with non-trivial both type approximations: + // Inv> where C = Captured(in Int) + // Inv> <: Inv> + // Inv> <: Inv> + // + // So, both of the options are possible, but since such case is rare we will chose Inv> for now val approximatedSuperType = approximateToSuperType(argumentType, conf, depth) ?: continue@loop // null means that this type we can leave as is if (approximatedSuperType.isTrivialSuper()) { @@ -577,6 +579,36 @@ abstract class AbstractTypeApproximator( return approximateLocalTypes(approximatedType, conf, toSuper) ?: approximatedType } + private fun shouldUseSubTypeForCapturedArgument( + subType: KotlinTypeMarker, + capturedArgumentType: KotlinTypeMarker, + conf: TypeApproximatorConfiguration, + depth: Int, + ): Boolean { + if (subType.isTrivialSub()) return false + // For K1, the result is always `!subType.isTrivialSub()` (leaving the old behavior) + if (!isK2) return true + + // Basically, what's written further might be simplified like + // return !approximateToSubType(capturedArgumentType.withNullability(false), conf, depth)!!.isTrivialSub() + // But it seems that now it looks a bit more clear and probably performant, thus first two if's are basically fast paths + + // If it's not `Nothing?`, then the lower bound is indeed non-trivial + if (!subType.lowerBoundIfFlexible().isNullableNothing()) return true + + // Here the subType is `Nothing?`, and it might be trivial only in cause the nullability is caused by nullability of captured type itself + + // If captured type is not marked as nullable, then nullability of subType came from the lower bound of the captured type. + // Thus, the lower bound is non-trivial for sure + if (!capturedArgumentType.isMarkedNullable()) return true + + val notMarkedNullableSubType = + approximateToSubType(capturedArgumentType.withNullability(false), conf, depth) + ?: error("Not-marked-nullable version of captured type approximation should also return not-null") + + return !notMarkedNullableSubType.isTrivialSub() + } + private fun KotlinTypeMarker.defaultResult(toSuper: Boolean) = if (toSuper) nullableAnyType() else { if (this is SimpleTypeMarker && isMarkedNullable()) nullableNothingType() else nothingType() } diff --git a/compiler/testData/diagnostics/tests/inference/capturedTypes/approximationLeavesNonTrivialLowerBound.kt b/compiler/testData/diagnostics/tests/inference/capturedTypes/approximationLeavesNonTrivialLowerBound.kt new file mode 100644 index 00000000000..0c053b04de9 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inference/capturedTypes/approximationLeavesNonTrivialLowerBound.kt @@ -0,0 +1,19 @@ +// FIR_IDENTICAL +interface Inv + +fun id(i: Inv): Inv = i + +fun foo1(x: Inv) { + val y = ")!>id(x) // Should return Inv + bar1(y) +} + +fun bar1(i: Inv) {} + +fun foo2(x: Inv) { + // Inv is OK here, because it's still a subtype of Inv + val y = ")!>id(x) + bar2(y) +} + +fun bar2(i: Inv) {} diff --git a/compiler/testData/diagnostics/tests/inference/capturedTypes/captureForNullableTypes.fir.kt b/compiler/testData/diagnostics/tests/inference/capturedTypes/captureForNullableTypes.fir.kt index a1d38994dd9..0c2184f6c8f 100644 --- a/compiler/testData/diagnostics/tests/inference/capturedTypes/captureForNullableTypes.fir.kt +++ b/compiler/testData/diagnostics/tests/inference/capturedTypes/captureForNullableTypes.fir.kt @@ -4,15 +4,15 @@ fun bar(a: Array): Array = null!! fun test1(a: Array) { - val r: Array = bar(a) + val r: Array = bar(a) val t = bar(a) - t checkType { _>() } + t checkType { _>() } } fun foo(l: Array): Array> = null!! fun test2(a: Array) { - val r: Array> = foo(a) + val r: Array> = foo(a) val t = foo(a) - t checkType { _>>() } + t checkType { _>>() } } diff --git a/compiler/testData/diagnostics/tests/inference/capturedTypes/captureFromNullableTypeInScope.kt b/compiler/testData/diagnostics/tests/inference/capturedTypes/captureFromNullableTypeInScope.kt new file mode 100644 index 00000000000..532bee2f210 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inference/capturedTypes/captureFromNullableTypeInScope.kt @@ -0,0 +1,18 @@ +// FIR_IDENTICAL +// ISSUE: KT-57958 + +fun ListVM<*>.foo() { + val currentItem1: MutableProperty?> = currentItem +} + +interface MutableProperty { + var value: T +} + +interface ListItemVM { + val value: TItem +} + +interface ListVM> { + val currentItem: MutableProperty +} diff --git a/compiler/testData/diagnostics/tests/inference/capturedTypes/captureFromNullableTypeInScopeAny.kt b/compiler/testData/diagnostics/tests/inference/capturedTypes/captureFromNullableTypeInScopeAny.kt new file mode 100644 index 00000000000..753166b9168 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inference/capturedTypes/captureFromNullableTypeInScopeAny.kt @@ -0,0 +1,14 @@ +// FIR_IDENTICAL +// ISSUE: KT-57958 + +fun ListVM<*>.foo() { + val currentItem1: MutableProperty = ")!>currentItem +} + +interface MutableProperty { + var value: T +} + +interface ListVM { + val currentItem: MutableProperty +} 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 511a845cc63..73fc97c33a4 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 @@ -15423,6 +15423,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/approximateContravariantCapturedTypes.kt"); } + @Test + @TestMetadata("approximationLeavesNonTrivialLowerBound.kt") + public void testApproximationLeavesNonTrivialLowerBound() throws Exception { + runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/approximationLeavesNonTrivialLowerBound.kt"); + } + @Test @TestMetadata("avoidCreatingUselessCapturedTypes.kt") public void testAvoidCreatingUselessCapturedTypes() throws Exception { @@ -15447,6 +15453,18 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/captureForPlatformTypes.kt"); } + @Test + @TestMetadata("captureFromNullableTypeInScope.kt") + public void testCaptureFromNullableTypeInScope() throws Exception { + runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/captureFromNullableTypeInScope.kt"); + } + + @Test + @TestMetadata("captureFromNullableTypeInScopeAny.kt") + public void testCaptureFromNullableTypeInScopeAny() throws Exception { + runTest("compiler/testData/diagnostics/tests/inference/capturedTypes/captureFromNullableTypeInScopeAny.kt"); + } + @Test @TestMetadata("captureFromNullableTypeVariable.kt") public void testCaptureFromNullableTypeVariable() throws Exception {