[FIR] Fix capturing of flexible types during resolution
Previously, because we didn't handle flexible types properly in prepareCapturedType, projections inside flexible types would only be captured during subtyping with captureStatus=FOR_SUBTYPING which would lead to the constraint type being wrongly approximated (see ConstraintInjector.TypeCheckerStateForConstraintInjector .addNewIncorporatedConstraint). Fixing the capturing produced two kinds of false positive diagnostics: 1. In ConstraintInjector.TypeCheckerStateForConstraintInjector .addNewIncorporatedConstraint we would get two instances of cone types that are structurally equal and containing the same captured type. However, because we only skipped subtyping if the types were referentially equal, we would get a contradiction here. The fix was to use structural equality instead, which should be okay as the captured type instances are the same. 2. Reified type variables were inferred to captured types because flexible arrays with captured upper bounds (Array<Foo>..Array<Captured(out Foo)>?) were not properly approximated. #KT-62609 Fixed
This commit is contained in:
committed by
Space Team
parent
ed4941d9f9
commit
560c1cacf3
+6
@@ -16424,6 +16424,12 @@ public class DiagnosticCompilerTestFE10TestdataTestGenerated extends AbstractDia
|
||||
runTest("compiler/testData/diagnostics/tests/inference/kt619.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("kt62609.kt")
|
||||
public void testKt62609() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/inference/kt62609.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("lambdaArgumentWithLabel.kt")
|
||||
public void testLambdaArgumentWithLabel() throws Exception {
|
||||
|
||||
+6
@@ -16424,6 +16424,12 @@ public class LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated
|
||||
runTest("compiler/testData/diagnostics/tests/inference/kt619.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("kt62609.kt")
|
||||
public void testKt62609() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/inference/kt62609.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("lambdaArgumentWithLabel.kt")
|
||||
public void testLambdaArgumentWithLabel() throws Exception {
|
||||
|
||||
+6
@@ -16418,6 +16418,12 @@ public class FirLightTreeOldFrontendDiagnosticsTestGenerated extends AbstractFir
|
||||
runTest("compiler/testData/diagnostics/tests/inference/kt619.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("kt62609.kt")
|
||||
public void testKt62609() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/inference/kt62609.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("lambdaArgumentWithLabel.kt")
|
||||
public void testLambdaArgumentWithLabel() throws Exception {
|
||||
|
||||
+6
@@ -16424,6 +16424,12 @@ public class FirPsiOldFrontendDiagnosticsTestGenerated extends AbstractFirPsiDia
|
||||
runTest("compiler/testData/diagnostics/tests/inference/kt619.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("kt62609.kt")
|
||||
public void testKt62609() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/inference/kt62609.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("lambdaArgumentWithLabel.kt")
|
||||
public void testLambdaArgumentWithLabel() throws Exception {
|
||||
|
||||
@@ -412,6 +412,10 @@ interface ConeTypeContext : TypeSystemContext, TypeSystemOptimizationContext, Ty
|
||||
return this is ConeClassLikeLookupTag && classId == StandardClassIds.Nothing
|
||||
}
|
||||
|
||||
override fun TypeConstructorMarker.isArrayConstructor(): Boolean {
|
||||
return this is ConeClassLikeLookupTag && classId == StandardClassIds.Array
|
||||
}
|
||||
|
||||
override fun SimpleTypeMarker.isSingleClassifierType(): Boolean {
|
||||
if (isError()) return false
|
||||
if (this is ConeCapturedType) return true
|
||||
|
||||
@@ -33,7 +33,6 @@ import org.jetbrains.kotlin.resolve.calls.inference.addSubtypeConstraintIfCompat
|
||||
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintPosition
|
||||
import org.jetbrains.kotlin.resolve.calls.inference.model.SimpleConstraintSystemConstraintPosition
|
||||
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
||||
import org.jetbrains.kotlin.types.model.CaptureStatus
|
||||
import org.jetbrains.kotlin.types.model.TypeSystemCommonSuperTypesContext
|
||||
import org.jetbrains.kotlin.types.model.typeConstructor
|
||||
|
||||
@@ -303,28 +302,9 @@ private fun argumentTypeWithCustomConversion(
|
||||
}
|
||||
}
|
||||
|
||||
fun Candidate.prepareCapturedType(argumentType: ConeKotlinType, context: ResolutionContext): ConeKotlinType {
|
||||
return captureTypeFromExpressionOrNull(argumentType, context) ?: argumentType
|
||||
}
|
||||
|
||||
private fun Candidate.captureTypeFromExpressionOrNull(argumentType: ConeKotlinType, context: ResolutionContext): ConeKotlinType? {
|
||||
val type = argumentType.fullyExpandedType(context.session)
|
||||
if (type is ConeIntersectionType) {
|
||||
val intersectedTypes = type.intersectedTypes.map { captureTypeFromExpressionOrNull(it, context) ?: it }
|
||||
if (intersectedTypes == type.intersectedTypes) return null
|
||||
return ConeIntersectionType(
|
||||
intersectedTypes,
|
||||
type.alternativeType?.let { captureTypeFromExpressionOrNull(it, context) ?: it }
|
||||
)
|
||||
}
|
||||
|
||||
if (type !is ConeClassLikeType && type !is ConeFlexibleType) return null
|
||||
|
||||
if (type.typeArguments.isEmpty()) return null
|
||||
|
||||
return context.session.typeContext.captureFromArgumentsInternal(
|
||||
type, CaptureStatus.FROM_EXPRESSION
|
||||
)
|
||||
internal fun prepareCapturedType(argumentType: ConeKotlinType, context: ResolutionContext): ConeKotlinType {
|
||||
if (argumentType.isRaw()) return argumentType
|
||||
return context.typeContext.captureFromExpression(argumentType.fullyExpandedType(context.session)) ?: argumentType
|
||||
}
|
||||
|
||||
private fun checkApplicabilityForArgumentType(
|
||||
|
||||
@@ -303,6 +303,9 @@ interface IrTypeSystemContext : TypeSystemContext, TypeSystemCommonSuperTypesCon
|
||||
override fun TypeConstructorMarker.isNothingConstructor(): Boolean =
|
||||
this is IrClassSymbol && isClassWithFqName(StandardNames.FqNames.nothing)
|
||||
|
||||
override fun TypeConstructorMarker.isArrayConstructor(): Boolean =
|
||||
this is IrClassSymbol && isClassWithFqName(StandardNames.FqNames.array)
|
||||
|
||||
override fun SimpleTypeMarker.isSingleClassifierType() = true
|
||||
|
||||
override fun SimpleTypeMarker.possibleIntegerTypes() = irBuiltIns.run {
|
||||
|
||||
+6
-1
@@ -466,7 +466,12 @@ class ConstraintInjector(
|
||||
isFromNullabilityConstraint: Boolean,
|
||||
isFromDeclaredUpperBound: Boolean
|
||||
) {
|
||||
if (lowerType === upperType) return
|
||||
// Avoid checking trivial incorporated constraints
|
||||
if (isK2) {
|
||||
if (lowerType == upperType) return
|
||||
} else {
|
||||
if (lowerType === upperType) return
|
||||
}
|
||||
if (c.isAllowedType(lowerType) && c.isAllowedType(upperType)) {
|
||||
fun runIsSubtypeOf() =
|
||||
runIsSubtypeOf(lowerType, upperType, shouldTryUseDifferentFlexibilityForUpperType, isFromNullabilityConstraint)
|
||||
|
||||
+24
-3
@@ -143,10 +143,12 @@ abstract class AbstractTypeApproximator(
|
||||
|
||||
val lowerResult = approximateTo(lowerBound, conf, depth)
|
||||
|
||||
val upperResult = if (!type.isRawType() && lowerBound.typeConstructor() == upperBound.typeConstructor())
|
||||
val upperResult = if (!type.isRawType() && !shouldApproximateUpperBoundSeparately(lowerBound, upperBound, conf)) {
|
||||
// We skip approximating the upper bound if the type constructors match as an optimization.
|
||||
lowerResult?.withNullability(upperBound.isMarkedNullable())
|
||||
else
|
||||
} else {
|
||||
approximateTo(upperBound, conf, depth)
|
||||
}
|
||||
if (lowerResult == null && upperResult == null) return null
|
||||
|
||||
/**
|
||||
@@ -168,11 +170,30 @@ abstract class AbstractTypeApproximator(
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldApproximateUpperBoundSeparately(
|
||||
lowerBound: SimpleTypeMarker,
|
||||
upperBound: SimpleTypeMarker,
|
||||
conf: TypeApproximatorConfiguration,
|
||||
): Boolean {
|
||||
val upperBoundConstructor = upperBound.typeConstructor()
|
||||
if (lowerBound.typeConstructor() != upperBoundConstructor) return true
|
||||
|
||||
// Flexible arrays have the shape `Array<X>..Array<out X>?`.
|
||||
// When such a type is captured, it results in `Array<X>..Array<Captured(out X)>?`, therefore it's necessary to approximate the
|
||||
// upper bound separately.
|
||||
// As an important performance optimization, we explicitly check if the type in question is an array with a captured type argument
|
||||
// that needs to be approximated.
|
||||
// This saves us from doing twice the work unnecessarily in many cases.
|
||||
return isK2 &&
|
||||
upperBoundConstructor.isArrayConstructor() &&
|
||||
upperBound.getArgumentOrNull(0).let { it is CapturedTypeMarker && conf.capturedType(ctx, it) }
|
||||
}
|
||||
|
||||
private fun approximateLocalTypes(
|
||||
type: SimpleTypeMarker,
|
||||
conf: TypeApproximatorConfiguration,
|
||||
toSuper: Boolean,
|
||||
depth: Int
|
||||
depth: Int,
|
||||
): SimpleTypeMarker? {
|
||||
if (!toSuper) return null
|
||||
if (!conf.localTypes && !conf.anonymous) return null
|
||||
|
||||
Vendored
+10
-2
@@ -12,8 +12,16 @@ fun <K> select(x: K, y: K): K = x
|
||||
|
||||
fun <R> foo(f: () -> R): R = f()
|
||||
|
||||
interface Inv<T> {
|
||||
fun createArray(): Array<T>
|
||||
}
|
||||
|
||||
fun test(n: Number) {
|
||||
val a = select(foo { JavaTest.createNumberArray() }, emptyArray())
|
||||
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Array<kotlin.Number..kotlin.Number?!>..kotlin.Array<out kotlin.Number..kotlin.Number?!>?!")!>a<!>
|
||||
}
|
||||
|
||||
a
|
||||
}
|
||||
fun test2(inv: Inv<out CharSequence>) {
|
||||
val a = select(foo { inv.createArray() }, emptyArray())
|
||||
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Array<out kotlin.CharSequence>")!>a<!>
|
||||
}
|
||||
|
||||
Vendored
+10
-2
@@ -12,8 +12,16 @@ fun <K> select(x: K, y: K): K = x
|
||||
|
||||
fun <R> foo(f: () -> R): R = f()
|
||||
|
||||
interface Inv<T> {
|
||||
fun createArray(): Array<T>
|
||||
}
|
||||
|
||||
fun test(n: Number) {
|
||||
val a = select(foo { JavaTest.createNumberArray() }, emptyArray())
|
||||
|
||||
<!DEBUG_INFO_EXPRESSION_TYPE("(kotlin.Array<(kotlin.Number..kotlin.Number?)>..kotlin.Array<out (kotlin.Number..kotlin.Number?)>?)")!>a<!>
|
||||
}
|
||||
}
|
||||
|
||||
fun test2(inv: Inv<out CharSequence>) {
|
||||
val a = select(foo { inv.createArray() }, emptyArray())
|
||||
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Array<out kotlin.CharSequence>")!>a<!>
|
||||
}
|
||||
|
||||
Vendored
-15
@@ -1,15 +0,0 @@
|
||||
package
|
||||
|
||||
public fun </*0*/ R> foo(/*0*/ f: () -> R): R
|
||||
public fun </*0*/ K> select(/*0*/ x: K, /*1*/ y: K): K
|
||||
public fun test(/*0*/ n: kotlin.Number): kotlin.Unit
|
||||
|
||||
public open class JavaTest {
|
||||
public constructor JavaTest()
|
||||
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 createNumberArray(): kotlin.Array<(out) kotlin.Number!>!
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// FIR_IDENTICAL
|
||||
// ISSUE: KT-62609
|
||||
// FILE: I.java
|
||||
public interface I<T> {}
|
||||
|
||||
// FILE: X.java
|
||||
public abstract class X<P> {}
|
||||
|
||||
// FILE: A.java
|
||||
public final class A extends X<String> implements I<String> {
|
||||
public static final A INSTANCE = new A();
|
||||
}
|
||||
|
||||
// FILE: B.java
|
||||
public final class B extends X<Integer> implements I<Integer> {
|
||||
public static final B INSTANCE = new B();
|
||||
}
|
||||
|
||||
// FILE: test.kt
|
||||
fun test1() {
|
||||
controlFun(A.INSTANCE)
|
||||
controlFun(B.INSTANCE)
|
||||
controlFun2(A.INSTANCE)
|
||||
controlFun2(B.INSTANCE)
|
||||
|
||||
val a = when {
|
||||
true -> A.INSTANCE
|
||||
else -> B.INSTANCE
|
||||
}
|
||||
|
||||
controlFun(a)
|
||||
controlFun2(a)
|
||||
}
|
||||
|
||||
fun test2() {
|
||||
controlFun(A())
|
||||
controlFun(B())
|
||||
controlFun2(A())
|
||||
controlFun2(B())
|
||||
|
||||
val a = when {
|
||||
true -> A()
|
||||
else -> B()
|
||||
}
|
||||
|
||||
controlFun(a)
|
||||
controlFun2(a)
|
||||
}
|
||||
|
||||
|
||||
fun <T> controlFun(c: I<T>) {}
|
||||
fun <T> controlFun2(c: X<T>) {}
|
||||
Generated
+6
@@ -16424,6 +16424,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
|
||||
runTest("compiler/testData/diagnostics/tests/inference/kt619.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("kt62609.kt")
|
||||
public void testKt62609() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/inference/kt62609.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("lambdaArgumentWithLabel.kt")
|
||||
public void testLambdaArgumentWithLabel() throws Exception {
|
||||
|
||||
@@ -518,6 +518,7 @@ interface TypeSystemContext : TypeSystemOptimizationContext {
|
||||
|
||||
fun TypeConstructorMarker.isAnyConstructor(): Boolean
|
||||
fun TypeConstructorMarker.isNothingConstructor(): Boolean
|
||||
fun TypeConstructorMarker.isArrayConstructor(): Boolean
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@@ -350,6 +350,11 @@ interface ClassicTypeSystemContext : TypeSystemInferenceExtensionContext, TypeSy
|
||||
return KotlinBuiltIns.isTypeConstructorForGivenClass(this, FqNames.nothing)
|
||||
}
|
||||
|
||||
override fun TypeConstructorMarker.isArrayConstructor(): Boolean {
|
||||
require(this is TypeConstructor, this::errorMessage)
|
||||
return KotlinBuiltIns.isTypeConstructorForGivenClass(this, FqNames.array)
|
||||
}
|
||||
|
||||
override fun KotlinTypeMarker.asTypeArgument(): TypeArgumentMarker {
|
||||
require(this is KotlinType, this::errorMessage)
|
||||
return this.asTypeProjection()
|
||||
|
||||
Reference in New Issue
Block a user