NI: intersect DFI types before capturing

^KT-37887 Fixed
This commit is contained in:
Victor Petukhov
2020-05-14 13:43:30 +03:00
parent 5a7ceec985
commit 73dec25eb1
15 changed files with 141 additions and 32 deletions
@@ -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");
@@ -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
)
@@ -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
@@ -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<out CharSequence>, Inv<String>]
* intersect(Inv<out CharSequence>, Inv<String>) = Inv<String>
* capture(Inv<String>) = Inv<String>
* But with capturing first:
* capture(Inv<out CharSequence>) = Inv<CapturedType(out CharSequence)>
* capture(Inv<String>) = Inv<String>
* intersect(Inv<CapturedType(out CharSequence)>, Inv<String>) = Inv<CapturedType(out CharSequence)> & Inv<String>
*
* 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) =
@@ -97,7 +97,7 @@ internal class MemberScopeTowerLevel(
val unstableError = if (dispatchReceiver.isStable) null else UnstableSmartCastDiagnostic
val unstableCandidates = if (unstableError != null) ArrayList<CandidateWithBoundDispatchReceiver>(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<KotlinType>
get() = possibleTypes + receiverValue.type
override fun getVariables(
name: Name,
extensionReceiver: ReceiverValueWithSmartCastInfo?
): Collection<CandidateWithBoundDispatchReceiver> {
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)
}
}
@@ -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) =
@@ -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<KotlinType>, // 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<KotlinType>,
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)
}
@@ -36,5 +36,5 @@ fun foo(coneSymbol: AbstractFirBasedSymbol<*>) {
coneSymbol.phasedFir() checkType { _<FirVariable<*>>() }
if (coneSymbol !is FirPropertySymbol) return
coneSymbol.phasedFir() checkType { _<FirVariable<*>>() }
coneSymbol.phasedFir() checkType { _<FirProperty>() }
}
@@ -36,5 +36,5 @@ fun foo(coneSymbol: AbstractFirBasedSymbol<*>) {
coneSymbol.phasedFir() checkType { _<FirVariable<*>>() }
if (coneSymbol !is FirPropertySymbol) return
coneSymbol.phasedFir() checkType { _<FirVariable<*>>() }
coneSymbol.phasedFir() checkType { _<FirProperty>() }
}
@@ -0,0 +1,28 @@
// !DIAGNOSTICS: -UNCHECKED_CAST -UNUSED_DESTRUCTURED_PARAMETER_ENTRY -USELESS_CAST -UNUSED_PARAMETER -UNUSED_EXPRESSION
class Inv<T>(val y: T)
fun <K> takeTwoInv(x: Inv<K>, y: Inv<K>) = x.y
fun <K> takeTwoInvOut(x: Inv<out K>, y: Inv<out K>) : K = x.y
fun test1(y: Any) {
y as Map<String, Any?>
y as Map<*, *>
y.forEach { (k: String, u: Any?) -> }
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.collections.Map<kotlin.String, kotlin.Any?> & kotlin.Any")!>y<!>
}
fun test2(x: Any, y: Inv<String>) {
x as Inv<String>
x as Inv<out CharSequence>
val z = takeTwoInv(x, y)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String")!>z<!>
}
fun test3(x: Any, y: Inv<String>) {
x as Inv<out CharSequence>
x as Inv<String>
val z = takeTwoInvOut(x, y)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String")!>z<!>
}
@@ -0,0 +1,28 @@
// !DIAGNOSTICS: -UNCHECKED_CAST -UNUSED_DESTRUCTURED_PARAMETER_ENTRY -USELESS_CAST -UNUSED_PARAMETER -UNUSED_EXPRESSION
class Inv<T>(val y: T)
fun <K> takeTwoInv(x: Inv<K>, y: Inv<K>) = x.y
fun <K> takeTwoInvOut(x: Inv<out K>, y: Inv<out K>) : K = x.y
fun test1(y: Any) {
y as Map<String, Any?>
y as Map<*, *>
<!DEBUG_INFO_SMARTCAST!>y<!>.forEach { (k: String, u: Any?) -> }
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Any & kotlin.collections.Map<*, *> & kotlin.collections.Map<kotlin.String, kotlin.Any?>")!>y<!>
}
fun test2(x: Any, y: Inv<String>) {
x as Inv<String>
x as Inv<out CharSequence>
val z = takeTwoInv(<!DEBUG_INFO_SMARTCAST!>x<!>, y)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String")!>z<!>
}
fun test3(x: Any, y: Inv<String>) {
x as Inv<out CharSequence>
x as Inv<String>
val z = takeTwoInvOut(<!DEBUG_INFO_SMARTCAST!>x<!>, y)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String")!>z<!>
}
@@ -0,0 +1,15 @@
package
public fun </*0*/ K> takeTwoInv(/*0*/ x: Inv<K>, /*1*/ y: Inv<K>): K
public fun </*0*/ K> takeTwoInvOut(/*0*/ x: Inv<out K>, /*1*/ y: Inv<out K>): K
public fun test1(/*0*/ y: kotlin.Any): kotlin.Unit
public fun test2(/*0*/ x: kotlin.Any, /*1*/ y: Inv<kotlin.String>): kotlin.Unit
public fun test3(/*0*/ x: kotlin.Any, /*1*/ y: Inv<kotlin.String>): kotlin.Unit
public final class Inv</*0*/ T> {
public constructor Inv</*0*/ T>(/*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
}
@@ -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");
@@ -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");
@@ -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) }
}
}