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 5c0892c6305..2e1494c6d9a 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 @@ -39120,6 +39120,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag runTest("compiler/testData/diagnostics/testsWithStdLib/java/inheritedFunctionN.kt"); } + @Test + @TestMetadata("intersectFlexibleAndMutable.kt") + public void testIntersectFlexibleAndMutable() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/java/intersectFlexibleAndMutable.kt"); + } + @Test @TestMetadata("patternCompileCallableReference.kt") public void testPatternCompileCallableReference() 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 0349a34ecdc..37206cf733c 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 @@ -39216,6 +39216,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti runTest("compiler/testData/diagnostics/testsWithStdLib/java/inheritedFunctionN.kt"); } + @Test + @TestMetadata("intersectFlexibleAndMutable.kt") + public void testIntersectFlexibleAndMutable() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/java/intersectFlexibleAndMutable.kt"); + } + @Test @TestMetadata("patternCompileCallableReference.kt") public void testPatternCompileCallableReference() 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 4f1a0b03e16..cb779baa1d6 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 @@ -39120,6 +39120,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac runTest("compiler/testData/diagnostics/testsWithStdLib/java/inheritedFunctionN.kt"); } + @Test + @TestMetadata("intersectFlexibleAndMutable.kt") + public void testIntersectFlexibleAndMutable() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/java/intersectFlexibleAndMutable.kt"); + } + @Test @TestMetadata("patternCompileCallableReference.kt") public void testPatternCompileCallableReference() throws Exception { diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrTypeConverter.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrTypeConverter.kt index 73bfafed449..0895ed52983 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrTypeConverter.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrTypeConverter.kt @@ -177,8 +177,8 @@ class Fir2IrTypeConverter( is ConeFlexibleType -> with(session.typeContext) { if (upperBound is ConeClassLikeType) { val upper = upperBound as ConeClassLikeType - val lower = lowerBound as? ConeClassLikeType ?: error("Expecting class-like type, got $lowerBound") - val intermediate = if (lower.lookupTag == upper.lookupTag) { + val lower = lowerBound + val intermediate = if (lower is ConeClassLikeType && lower.lookupTag == upper.lookupTag) { lower.replaceArguments(upper.getArguments()) } else lower (intermediate.withNullability(upper.isNullable) as ConeKotlinType) diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/ConeTypeIntersector.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/ConeTypeIntersector.kt index fb7f533aac9..491a7c92726 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/ConeTypeIntersector.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/ConeTypeIntersector.kt @@ -27,6 +27,16 @@ object ConeTypeIntersector { } } + if (inputTypes.any { it is ConeFlexibleType }) { + // (A..B) & C = (A & C)..(B & C) + val lowerBound = intersectTypes(context, inputTypes.map { it.lowerBoundIfFlexible() }) + val upperBound = intersectTypes(context, inputTypes.map { it.upperBoundIfFlexible() }) + // Special case - if C is `Nothing?`, then the result is `Nothing!`; but if it is non-null, + // then this code is unreachable, so it's more useful to do resolution/diagnostics + // under the assumption that it is purely nullable. + return if (lowerBound.isNothing) upperBound else coneFlexibleOrSimpleType(context, lowerBound, upperBound) + } + /** * resultNullability. Value description: * ACCEPT_NULL means that all types marked nullable @@ -37,11 +47,11 @@ object ConeTypeIntersector { * UNKNOWN means, that we do not know, i.e. more precisely, all singleClassifier types marked nullable if any, * and other types is captured types or type parameters without not-null upper bound. Example: `String? & T` such types we should leave as is. */ - val isResultNullable = inputTypes.all { it.isNullable(context) } - val inputTypesWithNullability = inputTypes.mapTo(LinkedHashSet()) { - if (isResultNullable) it else it.makeConeTypeDefinitelyNotNullOrNotNull(context) + val isResultNotNullable = inputTypes.any { !it.isNullable(context) } + val inputTypesMadeNotNullIfNeeded = inputTypes.mapTo(LinkedHashSet()) { + if (isResultNotNullable) it.makeConeTypeDefinitelyNotNullOrNotNull(context) else it } - if (inputTypesWithNullability.size == 1) return inputTypesWithNullability.single() + if (inputTypesMadeNotNullIfNeeded.size == 1) return inputTypesMadeNotNullIfNeeded.single() /* * Here we drop types from intersection set for cases like that: @@ -54,25 +64,12 @@ object ConeTypeIntersector { * We want to drop A from that set, because it's useless for type checking. But in case if * A came from inference and B came from smartcast we want to save both types in intersection */ - val resultList = inputTypesWithNullability.toMutableList() + val resultList = inputTypesMadeNotNullIfNeeded.toMutableList() resultList.removeIfNonSingleErrorOrInRelation { candidate, other -> other.isStrictSubtypeOf(context, candidate) } assert(resultList.isNotEmpty()) { "no types left after removing strict supertypes: ${inputTypes.joinToString()}" } ConeIntegerLiteralIntersector.findCommonIntersectionType(resultList)?.let { return it } - /* - * For the case like it(ft(String..String?), String?), where ft(String..String?) == String?, we prefer to _keep_ flexible type. - * When a == b, the former, i.e., the one in the list will be filtered out, and the other one will remain. - * So, here, we sort the interim list such that flexible types appear later. - */ - resultList.sortWith { p0, p1 -> - when { - p0 is ConeFlexibleType && p1 is ConeFlexibleType -> 0 - p0 is ConeFlexibleType -> 1 - p1 is ConeFlexibleType -> -1 - else -> 0 - } - } resultList.removeIfNonSingleErrorOrInRelation { candidate, other -> AbstractTypeChecker.equalTypes(context, candidate, other) } assert(resultList.isNotEmpty()) { "no types left after removing equal types: ${inputTypes.joinToString()}" } return resultList.singleOrNull() ?: ConeIntersectionType(resultList) diff --git a/compiler/testData/diagnostics/tests/j+k/purelyImplementedSupertype.fir.kt b/compiler/testData/diagnostics/tests/j+k/purelyImplementedSupertype.fir.kt index 053114311ea..7e2d3cbd091 100644 --- a/compiler/testData/diagnostics/tests/j+k/purelyImplementedSupertype.fir.kt +++ b/compiler/testData/diagnostics/tests/j+k/purelyImplementedSupertype.fir.kt @@ -21,7 +21,7 @@ fun testWithUtil(map: ConcurrentHashMap): Int { if (string == null) { string = Util.getString() } - return string.length + return string.length } fun test(list: java.util.ArrayList) { diff --git a/compiler/testData/diagnostics/testsWithStdLib/java/intersectFlexibleAndMutable.kt b/compiler/testData/diagnostics/testsWithStdLib/java/intersectFlexibleAndMutable.kt new file mode 100644 index 00000000000..b4c4a02cce9 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/java/intersectFlexibleAndMutable.kt @@ -0,0 +1,14 @@ +// FIR_IDENTICAL +// FILE: Util.java +import java.util.List; + +public class Util { + public static List id(List x) { return x; } +} + +// FILE: main.kt +fun main() { + var list = mutableListOf(1) + list = Util.id(list) + list += 2 +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/java/intersectFlexibleAndMutable.txt b/compiler/testData/diagnostics/testsWithStdLib/java/intersectFlexibleAndMutable.txt new file mode 100644 index 00000000000..6070fb1e48d --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/java/intersectFlexibleAndMutable.txt @@ -0,0 +1,13 @@ +package + +public fun main(): kotlin.Unit + +public open class Util { + public constructor Util() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public open fun id(/*0*/ x: kotlin.collections.(Mutable)List!): kotlin.collections.(Mutable)List! +} 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 0dfe932a353..33b35ff9bbe 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 @@ -39216,6 +39216,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/testsWithStdLib/java/inheritedFunctionN.kt"); } + @Test + @TestMetadata("intersectFlexibleAndMutable.kt") + public void testIntersectFlexibleAndMutable() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/java/intersectFlexibleAndMutable.kt"); + } + @Test @TestMetadata("patternCompileCallableReference.kt") public void testPatternCompileCallableReference() throws Exception {