[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:
Kirill Rakhman
2023-11-21 16:51:50 +01:00
committed by Space Team
parent ed4941d9f9
commit 560c1cacf3
16 changed files with 148 additions and 46 deletions
@@ -16424,6 +16424,12 @@ public class DiagnosticCompilerTestFE10TestdataTestGenerated extends AbstractDia
runTest("compiler/testData/diagnostics/tests/inference/kt619.kt"); 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 @Test
@TestMetadata("lambdaArgumentWithLabel.kt") @TestMetadata("lambdaArgumentWithLabel.kt")
public void testLambdaArgumentWithLabel() throws Exception { public void testLambdaArgumentWithLabel() throws Exception {
@@ -16424,6 +16424,12 @@ public class LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated
runTest("compiler/testData/diagnostics/tests/inference/kt619.kt"); 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 @Test
@TestMetadata("lambdaArgumentWithLabel.kt") @TestMetadata("lambdaArgumentWithLabel.kt")
public void testLambdaArgumentWithLabel() throws Exception { public void testLambdaArgumentWithLabel() throws Exception {
@@ -16418,6 +16418,12 @@ public class FirLightTreeOldFrontendDiagnosticsTestGenerated extends AbstractFir
runTest("compiler/testData/diagnostics/tests/inference/kt619.kt"); 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 @Test
@TestMetadata("lambdaArgumentWithLabel.kt") @TestMetadata("lambdaArgumentWithLabel.kt")
public void testLambdaArgumentWithLabel() throws Exception { public void testLambdaArgumentWithLabel() throws Exception {
@@ -16424,6 +16424,12 @@ public class FirPsiOldFrontendDiagnosticsTestGenerated extends AbstractFirPsiDia
runTest("compiler/testData/diagnostics/tests/inference/kt619.kt"); 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 @Test
@TestMetadata("lambdaArgumentWithLabel.kt") @TestMetadata("lambdaArgumentWithLabel.kt")
public void testLambdaArgumentWithLabel() throws Exception { public void testLambdaArgumentWithLabel() throws Exception {
@@ -412,6 +412,10 @@ interface ConeTypeContext : TypeSystemContext, TypeSystemOptimizationContext, Ty
return this is ConeClassLikeLookupTag && classId == StandardClassIds.Nothing return this is ConeClassLikeLookupTag && classId == StandardClassIds.Nothing
} }
override fun TypeConstructorMarker.isArrayConstructor(): Boolean {
return this is ConeClassLikeLookupTag && classId == StandardClassIds.Array
}
override fun SimpleTypeMarker.isSingleClassifierType(): Boolean { override fun SimpleTypeMarker.isSingleClassifierType(): Boolean {
if (isError()) return false if (isError()) return false
if (this is ConeCapturedType) return true 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.ConstraintPosition
import org.jetbrains.kotlin.resolve.calls.inference.model.SimpleConstraintSystemConstraintPosition import org.jetbrains.kotlin.resolve.calls.inference.model.SimpleConstraintSystemConstraintPosition
import org.jetbrains.kotlin.types.AbstractTypeChecker 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.TypeSystemCommonSuperTypesContext
import org.jetbrains.kotlin.types.model.typeConstructor import org.jetbrains.kotlin.types.model.typeConstructor
@@ -303,28 +302,9 @@ private fun argumentTypeWithCustomConversion(
} }
} }
fun Candidate.prepareCapturedType(argumentType: ConeKotlinType, context: ResolutionContext): ConeKotlinType { internal fun prepareCapturedType(argumentType: ConeKotlinType, context: ResolutionContext): ConeKotlinType {
return captureTypeFromExpressionOrNull(argumentType, context) ?: argumentType if (argumentType.isRaw()) return argumentType
} return context.typeContext.captureFromExpression(argumentType.fullyExpandedType(context.session)) ?: 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
)
} }
private fun checkApplicabilityForArgumentType( private fun checkApplicabilityForArgumentType(
@@ -303,6 +303,9 @@ interface IrTypeSystemContext : TypeSystemContext, TypeSystemCommonSuperTypesCon
override fun TypeConstructorMarker.isNothingConstructor(): Boolean = override fun TypeConstructorMarker.isNothingConstructor(): Boolean =
this is IrClassSymbol && isClassWithFqName(StandardNames.FqNames.nothing) 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.isSingleClassifierType() = true
override fun SimpleTypeMarker.possibleIntegerTypes() = irBuiltIns.run { override fun SimpleTypeMarker.possibleIntegerTypes() = irBuiltIns.run {
@@ -466,7 +466,12 @@ class ConstraintInjector(
isFromNullabilityConstraint: Boolean, isFromNullabilityConstraint: Boolean,
isFromDeclaredUpperBound: 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)) { if (c.isAllowedType(lowerType) && c.isAllowedType(upperType)) {
fun runIsSubtypeOf() = fun runIsSubtypeOf() =
runIsSubtypeOf(lowerType, upperType, shouldTryUseDifferentFlexibilityForUpperType, isFromNullabilityConstraint) runIsSubtypeOf(lowerType, upperType, shouldTryUseDifferentFlexibilityForUpperType, isFromNullabilityConstraint)
@@ -143,10 +143,12 @@ abstract class AbstractTypeApproximator(
val lowerResult = approximateTo(lowerBound, conf, depth) 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()) lowerResult?.withNullability(upperBound.isMarkedNullable())
else } else {
approximateTo(upperBound, conf, depth) approximateTo(upperBound, conf, depth)
}
if (lowerResult == null && upperResult == null) return null 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( private fun approximateLocalTypes(
type: SimpleTypeMarker, type: SimpleTypeMarker,
conf: TypeApproximatorConfiguration, conf: TypeApproximatorConfiguration,
toSuper: Boolean, toSuper: Boolean,
depth: Int depth: Int,
): SimpleTypeMarker? { ): SimpleTypeMarker? {
if (!toSuper) return null if (!toSuper) return null
if (!conf.localTypes && !conf.anonymous) return null if (!conf.localTypes && !conf.anonymous) return null
@@ -12,8 +12,16 @@ fun <K> select(x: K, y: K): K = x
fun <R> foo(f: () -> R): R = f() fun <R> foo(f: () -> R): R = f()
interface Inv<T> {
fun createArray(): Array<T>
}
fun test(n: Number) { fun test(n: Number) {
val a = select(foo { JavaTest.createNumberArray() }, emptyArray()) 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<!>
}
@@ -12,8 +12,16 @@ fun <K> select(x: K, y: K): K = x
fun <R> foo(f: () -> R): R = f() fun <R> foo(f: () -> R): R = f()
interface Inv<T> {
fun createArray(): Array<T>
}
fun test(n: Number) { fun test(n: Number) {
val a = select(foo { JavaTest.createNumberArray() }, emptyArray()) 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<!> <!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<!>
}
@@ -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>) {}
@@ -16424,6 +16424,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
runTest("compiler/testData/diagnostics/tests/inference/kt619.kt"); 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 @Test
@TestMetadata("lambdaArgumentWithLabel.kt") @TestMetadata("lambdaArgumentWithLabel.kt")
public void testLambdaArgumentWithLabel() throws Exception { public void testLambdaArgumentWithLabel() throws Exception {
@@ -518,6 +518,7 @@ interface TypeSystemContext : TypeSystemOptimizationContext {
fun TypeConstructorMarker.isAnyConstructor(): Boolean fun TypeConstructorMarker.isAnyConstructor(): Boolean
fun TypeConstructorMarker.isNothingConstructor(): Boolean fun TypeConstructorMarker.isNothingConstructor(): Boolean
fun TypeConstructorMarker.isArrayConstructor(): Boolean
/** /**
* *
@@ -350,6 +350,11 @@ interface ClassicTypeSystemContext : TypeSystemInferenceExtensionContext, TypeSy
return KotlinBuiltIns.isTypeConstructorForGivenClass(this, FqNames.nothing) 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 { override fun KotlinTypeMarker.asTypeArgument(): TypeArgumentMarker {
require(this is KotlinType, this::errorMessage) require(this is KotlinType, this::errorMessage)
return this.asTypeProjection() return this.asTypeProjection()