From 73dec25eb10307a48d67fa7422100aff207d7893 Mon Sep 17 00:00:00 2001 From: Victor Petukhov Date: Thu, 14 May 2020 13:43:30 +0300 Subject: [PATCH] NI: intersect DFI types before capturing ^KT-37887 Fixed --- ...endDiagnosticsTestWithStdlibGenerated.java | 5 +++ .../resolve/calls/tower/NewCallArguments.kt | 2 +- .../resolve/calls/tower/PSICallResolver.kt | 2 +- .../calls/components/ArgumentsUtils.kt | 31 ++++++++++++++++--- .../kotlin/resolve/calls/tower/TowerLevels.kt | 16 ++++------ .../resolve/calls/tower/TowerResolver.kt | 4 +-- .../scopes/receivers/QualifierReceiver.kt | 27 +++++++++------- .../bareTypesWithStarProjections.fir.kt | 2 +- .../generics/bareTypesWithStarProjections.kt | 2 +- .../intersectDfiTypesBeforeCapturing.fir.kt | 28 +++++++++++++++++ .../intersectDfiTypesBeforeCapturing.kt | 28 +++++++++++++++++ .../intersectDfiTypesBeforeCapturing.txt | 15 +++++++++ .../DiagnosticsTestWithStdLibGenerated.java | 5 +++ ...ticsTestWithStdLibUsingJavacGenerated.java | 5 +++ .../kotlin/types/AbstractTypeChecker.kt | 1 + 15 files changed, 141 insertions(+), 32 deletions(-) create mode 100644 compiler/testData/diagnostics/testsWithStdLib/inference/intersectDfiTypesBeforeCapturing.fir.kt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/inference/intersectDfiTypesBeforeCapturing.kt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/inference/intersectDfiTypesBeforeCapturing.txt diff --git a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirOldFrontendDiagnosticsTestWithStdlibGenerated.java b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirOldFrontendDiagnosticsTestWithStdlibGenerated.java index c82cec7acb3..397165f44fa 100644 --- a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirOldFrontendDiagnosticsTestWithStdlibGenerated.java +++ b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirOldFrontendDiagnosticsTestWithStdlibGenerated.java @@ -1866,6 +1866,11 @@ public class FirOldFrontendDiagnosticsTestWithStdlibGenerated extends AbstractFi runTest("compiler/testData/diagnostics/testsWithStdLib/inference/integerLiterals.kt"); } + @TestMetadata("intersectDfiTypesBeforeCapturing.kt") + public void testIntersectDfiTypesBeforeCapturing() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/inference/intersectDfiTypesBeforeCapturing.kt"); + } + @TestMetadata("intersectionInputType.kt") public void testIntersectionInputType() throws Exception { runTest("compiler/testData/diagnostics/testsWithStdLib/inference/intersectionInputType.kt"); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/tower/NewCallArguments.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/tower/NewCallArguments.kt index 6a294effd75..d3800768ac1 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/tower/NewCallArguments.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/tower/NewCallArguments.kt @@ -72,7 +72,7 @@ class ParseErrorKotlinCallArgument( ) : ExpressionKotlinCallArgument, SimplePSIKotlinCallArgument() { override val receiver = ReceiverValueWithSmartCastInfo( TransientReceiver(ErrorUtils.createErrorType("Error type for ParseError-argument $valueArgument")), - possibleTypes = emptySet(), + typesFromSmartCasts = emptySet(), isStable = true ) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/tower/PSICallResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/tower/PSICallResolver.kt index 8325913c810..c4037443260 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/tower/PSICallResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/tower/PSICallResolver.kt @@ -471,7 +471,7 @@ class PSICallResolver( private fun createReceiverCallArgument(variable: KotlinResolutionCandidate): SimpleKotlinCallArgument { variable.forceResolution() val variableReceiver = createReceiverValueWithSmartCastInfo(variable) - if (variableReceiver.possibleTypes.isNotEmpty()) { + if (variableReceiver.hasTypesFromSmartCasts()) { return ReceiverExpressionKotlinCallArgument( createReceiverValueWithSmartCastInfo(variable), isForImplicitInvoke = true diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/components/ArgumentsUtils.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/components/ArgumentsUtils.kt index 74b2f569e74..66f9dd43567 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/components/ArgumentsUtils.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/components/ArgumentsUtils.kt @@ -31,6 +31,7 @@ import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValueWithSmartCastInfo import org.jetbrains.kotlin.types.UnwrappedType import org.jetbrains.kotlin.types.checker.intersectWrappedTypes +import org.jetbrains.kotlin.types.checker.prepareArgumentTypeRegardingCaptureTypes import org.jetbrains.kotlin.utils.DFS import org.jetbrains.kotlin.utils.addToStdlib.safeAs @@ -40,15 +41,37 @@ internal fun unexpectedArgument(argument: KotlinCallArgument): Nothing = // if expression is not stable and has smart casts, then we create this type internal val ReceiverValueWithSmartCastInfo.unstableType: UnwrappedType? get() { - if (isStable || possibleTypes.isEmpty()) return null - return intersectWrappedTypes(possibleTypes + receiverValue.type) + if (isStable || !hasTypesFromSmartCasts()) + return if (isStable) null else receiverValue.type.unwrap() + + val intersectionType = intersectWrappedTypes(allOriginalTypes) + + return prepareArgumentTypeRegardingCaptureTypes(intersectionType) ?: intersectionType } // with all smart casts if stable val ReceiverValueWithSmartCastInfo.stableType: UnwrappedType get() { - if (!isStable || possibleTypes.isEmpty()) return receiverValue.type.unwrap() - return intersectWrappedTypes(possibleTypes + receiverValue.type) + if (!isStable || !hasTypesFromSmartCasts()) + return receiverValue.type.unwrap() + + /* + * We have to intersect types first as after capturing, subtyping relation may change and some type won't be excluded from intersection type. + * + * Example: + * allOriginalTypes = [Inv, Inv] + * intersect(Inv, Inv) = Inv + * capture(Inv) = Inv + * But with capturing first: + * capture(Inv) = Inv + * capture(Inv) = Inv + * intersect(Inv, Inv) = Inv & Inv + * + * Such redundant type with captured argument may further lead to contradiction in constraint system or less exact solution. + */ + val intersectionType = intersectWrappedTypes(allOriginalTypes) + + return prepareArgumentTypeRegardingCaptureTypes(intersectionType) ?: intersectionType } internal fun KotlinCallArgument.getExpectedType(parameter: ParameterDescriptor, languageVersionSettings: LanguageVersionSettings) = diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/TowerLevels.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/TowerLevels.kt index e764e8f949d..4031c6fd2e2 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/TowerLevels.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/TowerLevels.kt @@ -97,7 +97,7 @@ internal class MemberScopeTowerLevel( val unstableError = if (dispatchReceiver.isStable) null else UnstableSmartCastDiagnostic val unstableCandidates = if (unstableError != null) ArrayList(0) else null - for (possibleType in dispatchReceiver.possibleTypes) { + for (possibleType in dispatchReceiver.typesFromSmartCasts) { possibleType.memberScope.getMembers(possibleType).mapTo(unstableCandidates ?: result) { createCandidateDescriptor( it, @@ -107,7 +107,7 @@ internal class MemberScopeTowerLevel( } } - if (dispatchReceiver.possibleTypes.isNotEmpty()) { + if (dispatchReceiver.hasTypesFromSmartCasts()) { if (unstableCandidates == null) { result.retainAll(result.selectMostSpecificInEachOverridableGroup { descriptor.approximateCapturedTypes(typeApproximator) }) } else { @@ -156,7 +156,7 @@ internal class MemberScopeTowerLevel( if (receiverValue !is ImplicitClassReceiver) return this val newReceiverValue = CastImplicitClassReceiver(receiverValue.classDescriptor, targetType) - return ReceiverValueWithSmartCastInfo(newReceiverValue, possibleTypes, isStable) + return ReceiverValueWithSmartCastInfo(newReceiverValue, typesFromSmartCasts, isStable) } override fun getVariables( @@ -184,9 +184,8 @@ internal class MemberScopeTowerLevel( } override fun recordLookup(name: Name) { - dispatchReceiver.receiverValue.type.memberScope.recordLookup(name, location) - dispatchReceiver.possibleTypes.forEach { - it.memberScope.recordLookup(name, location) + for (type in dispatchReceiver.allOriginalTypes) { + type.memberScope.recordLookup(name, location) } } } @@ -292,16 +291,13 @@ internal class SyntheticScopeBasedTowerLevel( scopeTower: ImplicitScopeTower, private val syntheticScopes: SyntheticScopes ) : AbstractScopeTowerLevel(scopeTower) { - private val ReceiverValueWithSmartCastInfo.allTypes: Set - get() = possibleTypes + receiverValue.type - override fun getVariables( name: Name, extensionReceiver: ReceiverValueWithSmartCastInfo? ): Collection { if (extensionReceiver == null) return emptyList() - return syntheticScopes.collectSyntheticExtensionProperties(extensionReceiver.allTypes, name, location).map { + return syntheticScopes.collectSyntheticExtensionProperties(extensionReceiver.allOriginalTypes, name, location).map { createCandidateDescriptor(it, dispatchReceiver = null) } } diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/TowerResolver.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/TowerResolver.kt index 45ba0c0d19d..de983b8a772 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/TowerResolver.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/TowerResolver.kt @@ -240,8 +240,8 @@ class TowerResolver { private fun ReceiverValueWithSmartCastInfo.mayFitForName(name: Name): Boolean { if (receiverValue.type.mayFitForName(name)) return true - if (possibleTypes.isEmpty()) return false - return possibleTypes.any { it.mayFitForName(name) } + if (!hasTypesFromSmartCasts()) return false + return typesFromSmartCasts.any { it.mayFitForName(name) } } private fun KotlinType.mayFitForName(name: Name) = diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/scopes/receivers/QualifierReceiver.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/scopes/receivers/QualifierReceiver.kt index 5e236630aec..b5f7bff0d85 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/scopes/receivers/QualifierReceiver.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/scopes/receivers/QualifierReceiver.kt @@ -19,17 +19,26 @@ package org.jetbrains.kotlin.resolve.scopes.receivers import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.types.KotlinType -import org.jetbrains.kotlin.types.checker.prepareArgumentTypeRegardingCaptureTypes -import org.jetbrains.kotlin.utils.newLinkedHashSetWithExpectedSize +import org.jetbrains.kotlin.types.checker.* // this receiver used only for resolution. see subtypes interface DetailedReceiver class ReceiverValueWithSmartCastInfo( val receiverValue: ReceiverValue, - val possibleTypes: Set, // doesn't include receiver.type - val isStable: Boolean + /* + * It doesn't include receiver.type and is used only to special marking such types (e.g. for IDE green highlighting) + * but not to construct the resulting type + */ + val typesFromSmartCasts: Set, + val isStable: Boolean, + originalBaseType: KotlinType = receiverValue.type ) : DetailedReceiver { + // It's used to construct the resulting type + val allOriginalTypes = typesFromSmartCasts + originalBaseType + + fun hasTypesFromSmartCasts() = typesFromSmartCasts.isNotEmpty() + override fun toString() = receiverValue.toString() } @@ -46,13 +55,7 @@ interface QualifierReceiver : Receiver, DetailedReceiver { } fun ReceiverValueWithSmartCastInfo.prepareReceiverRegardingCaptureTypes(): ReceiverValueWithSmartCastInfo { - val preparedBaseType = prepareArgumentTypeRegardingCaptureTypes(receiverValue.type.unwrap()) - if (preparedBaseType == null && possibleTypes.isEmpty()) return this + val preparedBaseType = prepareArgumentTypeRegardingCaptureTypes(receiverValue.type.unwrap()) ?: return this - val newPossibleTypes = possibleTypes.mapTo(newLinkedHashSetWithExpectedSize(possibleTypes.size)) { - prepareArgumentTypeRegardingCaptureTypes(it.unwrap()) ?: it - } - val newReceiver = if (preparedBaseType != null) receiverValue.replaceType(preparedBaseType) else receiverValue - - return ReceiverValueWithSmartCastInfo(newReceiver, newPossibleTypes, isStable) + return ReceiverValueWithSmartCastInfo(receiverValue.replaceType(preparedBaseType), typesFromSmartCasts, isStable, receiverValue.type) } diff --git a/compiler/testData/diagnostics/tests/generics/bareTypesWithStarProjections.fir.kt b/compiler/testData/diagnostics/tests/generics/bareTypesWithStarProjections.fir.kt index 4653efc9429..6c8fcf68cb7 100644 --- a/compiler/testData/diagnostics/tests/generics/bareTypesWithStarProjections.fir.kt +++ b/compiler/testData/diagnostics/tests/generics/bareTypesWithStarProjections.fir.kt @@ -36,5 +36,5 @@ fun foo(coneSymbol: AbstractFirBasedSymbol<*>) { coneSymbol.phasedFir() checkType { _>() } if (coneSymbol !is FirPropertySymbol) return - coneSymbol.phasedFir() checkType { _>() } + coneSymbol.phasedFir() checkType { _() } } diff --git a/compiler/testData/diagnostics/tests/generics/bareTypesWithStarProjections.kt b/compiler/testData/diagnostics/tests/generics/bareTypesWithStarProjections.kt index 4653efc9429..6c8fcf68cb7 100644 --- a/compiler/testData/diagnostics/tests/generics/bareTypesWithStarProjections.kt +++ b/compiler/testData/diagnostics/tests/generics/bareTypesWithStarProjections.kt @@ -36,5 +36,5 @@ fun foo(coneSymbol: AbstractFirBasedSymbol<*>) { coneSymbol.phasedFir() checkType { _>() } if (coneSymbol !is FirPropertySymbol) return - coneSymbol.phasedFir() checkType { _>() } + coneSymbol.phasedFir() checkType { _() } } diff --git a/compiler/testData/diagnostics/testsWithStdLib/inference/intersectDfiTypesBeforeCapturing.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/inference/intersectDfiTypesBeforeCapturing.fir.kt new file mode 100644 index 00000000000..b2f05188b74 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/inference/intersectDfiTypesBeforeCapturing.fir.kt @@ -0,0 +1,28 @@ +// !DIAGNOSTICS: -UNCHECKED_CAST -UNUSED_DESTRUCTURED_PARAMETER_ENTRY -USELESS_CAST -UNUSED_PARAMETER -UNUSED_EXPRESSION + +class Inv(val y: T) + +fun takeTwoInv(x: Inv, y: Inv) = x.y + +fun takeTwoInvOut(x: Inv, y: Inv) : K = x.y + +fun test1(y: Any) { + y as Map + y as Map<*, *> + y.forEach { (k: String, u: Any?) -> } + & kotlin.Any")!>y +} + +fun test2(x: Any, y: Inv) { + x as Inv + x as Inv + val z = takeTwoInv(x, y) + z +} + +fun test3(x: Any, y: Inv) { + x as Inv + x as Inv + val z = takeTwoInvOut(x, y) + z +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/inference/intersectDfiTypesBeforeCapturing.kt b/compiler/testData/diagnostics/testsWithStdLib/inference/intersectDfiTypesBeforeCapturing.kt new file mode 100644 index 00000000000..2ea3d95eae0 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/inference/intersectDfiTypesBeforeCapturing.kt @@ -0,0 +1,28 @@ +// !DIAGNOSTICS: -UNCHECKED_CAST -UNUSED_DESTRUCTURED_PARAMETER_ENTRY -USELESS_CAST -UNUSED_PARAMETER -UNUSED_EXPRESSION + +class Inv(val y: T) + +fun takeTwoInv(x: Inv, y: Inv) = x.y + +fun takeTwoInvOut(x: Inv, y: Inv) : K = x.y + +fun test1(y: Any) { + y as Map + y as Map<*, *> + y.forEach { (k: String, u: Any?) -> } + & kotlin.collections.Map")!>y +} + +fun test2(x: Any, y: Inv) { + x as Inv + x as Inv + val z = takeTwoInv(x, y) + z +} + +fun test3(x: Any, y: Inv) { + x as Inv + x as Inv + val z = takeTwoInvOut(x, y) + z +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/inference/intersectDfiTypesBeforeCapturing.txt b/compiler/testData/diagnostics/testsWithStdLib/inference/intersectDfiTypesBeforeCapturing.txt new file mode 100644 index 00000000000..84bea7cdf01 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/inference/intersectDfiTypesBeforeCapturing.txt @@ -0,0 +1,15 @@ +package + +public fun takeTwoInv(/*0*/ x: Inv, /*1*/ y: Inv): K +public fun takeTwoInvOut(/*0*/ x: Inv, /*1*/ y: Inv): K +public fun test1(/*0*/ y: kotlin.Any): kotlin.Unit +public fun test2(/*0*/ x: kotlin.Any, /*1*/ y: Inv): kotlin.Unit +public fun test3(/*0*/ x: kotlin.Any, /*1*/ y: Inv): kotlin.Unit + +public final class Inv { + public constructor Inv(/*0*/ y: T) + public final val y: T + 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 +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java index f33bcd93ea4..a8028f641d2 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java @@ -2881,6 +2881,11 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW runTest("compiler/testData/diagnostics/testsWithStdLib/inference/integerLiterals.kt"); } + @TestMetadata("intersectDfiTypesBeforeCapturing.kt") + public void testIntersectDfiTypesBeforeCapturing() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/inference/intersectDfiTypesBeforeCapturing.kt"); + } + @TestMetadata("intersectionInputType.kt") public void testIntersectionInputType() throws Exception { runTest("compiler/testData/diagnostics/testsWithStdLib/inference/intersectionInputType.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java index 5c992197264..f776fd5c186 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java @@ -2881,6 +2881,11 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno runTest("compiler/testData/diagnostics/testsWithStdLib/inference/integerLiterals.kt"); } + @TestMetadata("intersectDfiTypesBeforeCapturing.kt") + public void testIntersectDfiTypesBeforeCapturing() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/inference/intersectDfiTypesBeforeCapturing.kt"); + } + @TestMetadata("intersectionInputType.kt") public void testIntersectionInputType() throws Exception { runTest("compiler/testData/diagnostics/testsWithStdLib/inference/intersectionInputType.kt"); diff --git a/core/type-system/src/org/jetbrains/kotlin/types/AbstractTypeChecker.kt b/core/type-system/src/org/jetbrains/kotlin/types/AbstractTypeChecker.kt index 69c6fa66297..ade2be673ef 100644 --- a/core/type-system/src/org/jetbrains/kotlin/types/AbstractTypeChecker.kt +++ b/core/type-system/src/org/jetbrains/kotlin/types/AbstractTypeChecker.kt @@ -303,6 +303,7 @@ object AbstractTypeChecker { if (!anyNonOutParameter && isSubtypeForSameConstructor(newArguments, superType)) return true + // TODO: rethink this; now components order in intersection type affects semantic due to run subtyping (which can add constraints) only until the first successful candidate return supertypesWithSameConstructor.any { isSubtypeForSameConstructor(it.asArgumentList(), superType) } } }