NI: intersect DFI types before capturing
^KT-37887 Fixed
This commit is contained in:
+5
@@ -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
|
||||
|
||||
+27
-4
@@ -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) =
|
||||
|
||||
+15
-12
@@ -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)
|
||||
}
|
||||
|
||||
+1
-1
@@ -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>() }
|
||||
}
|
||||
|
||||
+1
-1
@@ -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>() }
|
||||
}
|
||||
|
||||
Vendored
+28
@@ -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<!>
|
||||
}
|
||||
Vendored
+28
@@ -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<!>
|
||||
}
|
||||
Vendored
+15
@@ -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
|
||||
}
|
||||
+5
@@ -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");
|
||||
|
||||
compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java
Generated
+5
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user