FIR: Weaken some UPPER_BOUND_VIOLATED restrictions

See test data at starProjectionInsteadOutCaptured.kt

^KT-49412 Fixed
^KT-50230 Relates
^KT-48044 Fixed
This commit is contained in:
Denis.Zharkov
2021-12-28 18:05:24 +03:00
committed by teamcity
parent 814f4803b7
commit 9be4f818f4
15 changed files with 290 additions and 34 deletions
@@ -13881,6 +13881,18 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag
runTest("compiler/testData/diagnostics/tests/inference/underscoredTypeInForbiddenPositions.kt");
}
@Test
@TestMetadata("unsoundness1.kt")
public void testUnsoundness1() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/unsoundness1.kt");
}
@Test
@TestMetadata("unsoundness2.kt")
public void testUnsoundness2() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/unsoundness2.kt");
}
@Test
@TestMetadata("useFunctionLiteralsToInferType.kt")
public void testUseFunctionLiteralsToInferType() throws Exception {
@@ -31083,6 +31095,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag
runTest("compiler/testData/diagnostics/tests/typeParameters/repeatedBound.kt");
}
@Test
@TestMetadata("starProjectionInsteadOutCaptured.kt")
public void testStarProjectionInsteadOutCaptured() throws Exception {
runTest("compiler/testData/diagnostics/tests/typeParameters/starProjectionInsteadOutCaptured.kt");
}
@Test
@TestMetadata("upperBoundCannotBeArray.kt")
public void testUpperBoundCannotBeArray() throws Exception {
@@ -140,3 +140,21 @@ FILE: upperBoundViolated.kt
}
public final typealias Alias<V1> = R|(Class<V1>) -> kotlin/Boolean|
public abstract class Base<T : R|Base<T>|> : R|kotlin/Any| {
public constructor<T : R|Base<T>|>(): R|Base<T>| {
super<R|kotlin/Any|>()
}
}
public final class DerivedOut<out O : R|Base<out O>|> : R|kotlin/Any| {
public constructor<out O : R|Base<out O>|>(): R|DerivedOut<O>| {
super<R|kotlin/Any|>()
}
}
public final class DerivedIn<in I : R|Base<in I>|> : R|kotlin/Any| {
public constructor<in I : R|Base<in I>|>(): R|DerivedIn<I>| {
super<R|kotlin/Any|>()
}
}
@@ -55,7 +55,7 @@ class Test1<S1 : Test1<S1, K>, K : Any>
class Test2<S2 : Test1<S2, *>>
class Test3<S3 : Test3<S3, in K>, K : Any>
class Test4<S4 : Test3<<!UPPER_BOUND_VIOLATED!>S4<!>, out Any>>
class Test4<S4 : Test3<S4, out Any>>
class Test5<S5 : Test5<S5, in K>, K : Any>
class Test6<S6 : Test5<S6, in Any>>
@@ -66,9 +66,8 @@ class Test8<S8 : Test7<S8, <!UPPER_BOUND_VIOLATED!>in Any<!>>>
class Class<V : Any>
typealias Alias <V1> = (Class<V1>) -> Boolean
/* TODO: Should not be errors. Uncomment after fixing of https://youtrack.jetbrains.com/issue/KT-48044
abstract class Base<T : Base<T>> {}
class DerivedOut<out O : Base<out O>> {}
class DerivedIn<in I : Base<in I>> {}*/
class DerivedIn<in I : Base<in I>> {}
@@ -13881,6 +13881,18 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
runTest("compiler/testData/diagnostics/tests/inference/underscoredTypeInForbiddenPositions.kt");
}
@Test
@TestMetadata("unsoundness1.kt")
public void testUnsoundness1() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/unsoundness1.kt");
}
@Test
@TestMetadata("unsoundness2.kt")
public void testUnsoundness2() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/unsoundness2.kt");
}
@Test
@TestMetadata("useFunctionLiteralsToInferType.kt")
public void testUseFunctionLiteralsToInferType() throws Exception {
@@ -31083,6 +31095,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
runTest("compiler/testData/diagnostics/tests/typeParameters/repeatedBound.kt");
}
@Test
@TestMetadata("starProjectionInsteadOutCaptured.kt")
public void testStarProjectionInsteadOutCaptured() throws Exception {
runTest("compiler/testData/diagnostics/tests/typeParameters/starProjectionInsteadOutCaptured.kt");
}
@Test
@TestMetadata("upperBoundCannotBeArray.kt")
public void testUpperBoundCannotBeArray() throws Exception {
@@ -13881,6 +13881,18 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac
runTest("compiler/testData/diagnostics/tests/inference/underscoredTypeInForbiddenPositions.kt");
}
@Test
@TestMetadata("unsoundness1.kt")
public void testUnsoundness1() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/unsoundness1.kt");
}
@Test
@TestMetadata("unsoundness2.kt")
public void testUnsoundness2() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/unsoundness2.kt");
}
@Test
@TestMetadata("useFunctionLiteralsToInferType.kt")
public void testUseFunctionLiteralsToInferType() throws Exception {
@@ -31083,6 +31095,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac
runTest("compiler/testData/diagnostics/tests/typeParameters/repeatedBound.kt");
}
@Test
@TestMetadata("starProjectionInsteadOutCaptured.kt")
public void testStarProjectionInsteadOutCaptured() throws Exception {
runTest("compiler/testData/diagnostics/tests/typeParameters/starProjectionInsteadOutCaptured.kt");
}
@Test
@TestMetadata("upperBoundCannotBeArray.kt")
public void testUpperBoundCannotBeArray() throws Exception {
@@ -6,10 +6,10 @@
package org.jetbrains.kotlin.fir.analysis.checkers
import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.withSuppressedDiagnostics
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutorByMap
@@ -70,7 +70,11 @@ private fun buildDeepSubstitutionMultimap(
for (index in 0 until count) {
val typeArgument = typeArguments[index]
val substitutedArgument = ConeSubstitutorByMap(substitution, session).substituteArgument(typeArgument) ?: typeArgument
val substitutedArgument = ConeSubstitutorByMap(substitution, session).substituteArgument(
typeArgument,
classSymbol.toLookupTag(),
index
) ?: typeArgument
val substitutedType = substitutedArgument.type ?: continue
val typeParameterSymbol = typeParameterSymbols[index]
@@ -8,16 +8,23 @@ package org.jetbrains.kotlin.fir.analysis.checkers
import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.substitution.AbstractConeSubstitutor
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.resolve.substitution.substitutorByMap
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.resolve.withCombinedAttributesFrom
import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.TypeApproximatorConfiguration
import org.jetbrains.kotlin.types.Variance
import kotlin.reflect.KClass
/**
* Recursively analyzes type parameters and reports the diagnostic on the given source calculated using typeRef
@@ -44,23 +51,9 @@ fun checkUpperBoundViolated(
return
}
val count = minOf(typeParameterSymbols.size, type.typeArguments.size)
val substitution = mutableMapOf<FirTypeParameterSymbol, ConeKotlinType>()
val substitution = typeParameterSymbols.zip(type.typeArguments).toMap()
val substitutor = FE10LikeConeSubstitutor(substitution, context.session)
for (index in 0 until count) {
val typeArgument = type.typeArguments[index]
val typeParameterSymbol = typeParameterSymbols[index]
val typeArgumentType = typeArgument.type
if (typeArgumentType != null) {
substitution[typeParameterSymbol] = typeArgumentType
} else {
substitution[typeParameterSymbol] =
ConeStubTypeForTypeVariableInSubtyping(ConeTypeVariable("", typeParameterSymbol.toLookupTag()), ConeNullability.NOT_NULL)
}
}
val substitutor = substitutorByMap(substitution, context.session)
val typeRefAndSourcesForArguments = extractArgumentsTypeRefAndSource(typeRef) ?: return
val typeArgumentsWithSourceInfo = type.typeArguments.withIndex().map { (index, projection) ->
val (argTypeRef, source) =
@@ -80,6 +73,87 @@ fun checkUpperBoundViolated(
)
}
private class FE10LikeConeSubstitutor(
private val substitution: Map<FirTypeParameterSymbol, ConeTypeProjection>,
private val useSiteSession: FirSession
) : AbstractConeSubstitutor(useSiteSession.typeContext) {
override fun substituteType(type: ConeKotlinType): ConeKotlinType? {
if (type !is ConeTypeParameterType) return null
val projection = substitution[type.lookupTag.symbol] ?: return null
if (projection.isStarProjection) {
return StandardClassIds.Any.constructClassLikeType(emptyArray(), isNullable = true).withProjection(projection)
}
val result =
projection.type!!.updateNullabilityIfNeeded(type)
?.withCombinedAttributesFrom(type, useSiteSession.typeContext)
?: return null
if (type.isUnsafeVarianceType(useSiteSession)) {
useSiteSession.typeApproximator.approximateToSuperType(
result, TypeApproximatorConfiguration.FinalApproximationAfterResolutionAndInference
)?.let {
return it.withProjection(projection)
}
}
return result.withProjection(projection)
}
private fun ConeKotlinType.withProjection(projection: ConeTypeProjection): ConeKotlinType {
if (projection.kind == ProjectionKind.INVARIANT) return this
return withAttributes(ConeAttributes.create(listOf(OriginalProjectionTypeAttribute(projection))), useSiteSession.typeContext)
}
override fun substituteArgument(projection: ConeTypeProjection, lookupTag: ConeClassLikeLookupTag, index: Int): ConeTypeProjection? {
val substitutedProjection = super.substituteArgument(projection, lookupTag, index) ?: return null
if (substitutedProjection.isStarProjection) return null
val type = substitutedProjection.type!!
val projectionFromType = type.attributes.originalProjection?.data ?: type
val projectionKindFromType = projectionFromType.kind
if (projectionKindFromType == ProjectionKind.STAR) return ConeStarProjection
if (projectionKindFromType == ProjectionKind.INVARIANT || projectionKindFromType == projection.kind) {
return substitutedProjection
}
if (projection.kind == ProjectionKind.INVARIANT) {
return wrapProjection(projectionFromType, type)
}
return ConeStarProjection
}
}
private class OriginalProjectionTypeAttribute(val data: ConeTypeProjection) : ConeAttribute<OriginalProjectionTypeAttribute>() {
override fun union(other: OriginalProjectionTypeAttribute?): OriginalProjectionTypeAttribute? {
return other
}
override fun intersect(other: OriginalProjectionTypeAttribute?): OriginalProjectionTypeAttribute? {
return other
}
override fun add(other: OriginalProjectionTypeAttribute?): OriginalProjectionTypeAttribute? {
return other
}
override fun isSubtypeOf(other: OriginalProjectionTypeAttribute?): Boolean {
return true
}
override fun toString() = "OriginalProjectionTypeAttribute: $data"
override val key: KClass<out OriginalProjectionTypeAttribute>
get() = OriginalProjectionTypeAttribute::class
}
private val ConeAttributes.originalProjection: OriginalProjectionTypeAttribute? by ConeAttributes.attributeAccessor<OriginalProjectionTypeAttribute>()
class TypeArgumentWithSourceInfo(
val coneTypeProjection: ConeTypeProjection,
val typeRef: FirTypeRef?,
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.declarations.FirTypeAlias
import org.jetbrains.kotlin.fir.declarations.utils.expandedConeType
import org.jetbrains.kotlin.fir.resolve.substitution.AbstractConeSubstitutor
import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag
import org.jetbrains.kotlin.fir.symbols.ensureResolved
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeAliasSymbol
import org.jetbrains.kotlin.fir.types.*
@@ -115,10 +116,18 @@ private fun mapTypeAliasArguments(
return null
}
override fun substituteArgument(projection: ConeTypeProjection): ConeTypeProjection? {
override fun substituteArgument(
projection: ConeTypeProjection,
lookupTag: ConeClassLikeLookupTag,
index: Int
): ConeTypeProjection? {
val type = (projection as? ConeKotlinTypeProjection)?.type ?: return null
val symbol = (type as? ConeTypeParameterType)?.lookupTag?.symbol ?: return super.substituteArgument(projection)
val mappedProjection = typeAliasMap[symbol] ?: return super.substituteArgument(projection)
val symbol = (type as? ConeTypeParameterType)?.lookupTag?.symbol ?: return super.substituteArgument(
projection,
lookupTag,
index
)
val mappedProjection = typeAliasMap[symbol] ?: return super.substituteArgument(projection, lookupTag, index)
var mappedType = (mappedProjection as? ConeKotlinTypeProjection)?.type.updateNullabilityIfNeeded(type)
mappedType = when (mappedType) {
is ConeClassErrorType,
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.resolve.substitution
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.resolve.withCombinedAttributesFrom
import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
@@ -17,7 +18,7 @@ import org.jetbrains.kotlin.types.model.TypeVariableMarker
import org.jetbrains.kotlin.types.model.typeConstructor
abstract class AbstractConeSubstitutor(protected val typeContext: ConeTypeContext) : ConeSubstitutor() {
private fun wrapProjection(old: ConeTypeProjection, newType: ConeKotlinType): ConeTypeProjection {
protected fun wrapProjection(old: ConeTypeProjection, newType: ConeKotlinType): ConeTypeProjection {
return when (old) {
is ConeStarProjection -> old
is ConeKotlinTypeProjectionIn -> ConeKotlinTypeProjectionIn(newType)
@@ -29,7 +30,7 @@ abstract class AbstractConeSubstitutor(protected val typeContext: ConeTypeContex
}
abstract fun substituteType(type: ConeKotlinType): ConeKotlinType?
open fun substituteArgument(projection: ConeTypeProjection): ConeTypeProjection? {
open fun substituteArgument(projection: ConeTypeProjection, lookupTag: ConeClassLikeLookupTag, index: Int): ConeTypeProjection? {
val type = (projection as? ConeKotlinTypeProjection)?.type ?: return null
val newType = substituteOrNull(type) ?: return null
return wrapProjection(projection, newType)
@@ -124,8 +125,11 @@ abstract class AbstractConeSubstitutor(protected val typeContext: ConeTypeContex
private fun ConeKotlinType.substituteArguments(): ConeKotlinType? {
val newArguments by lazy { arrayOfNulls<ConeTypeProjection>(typeArguments.size) }
var initialized = false
require(this is ConeClassLikeType) { "Unknown type to substitute: $this, ${this::class}" }
for ((index, typeArgument) in this.typeArguments.withIndex()) {
newArguments[index] = substituteArgument(typeArgument)?.also {
newArguments[index] = substituteArgument(typeArgument, lookupTag, index)?.also {
initialized = true
}
}
@@ -144,8 +148,7 @@ abstract class AbstractConeSubstitutor(protected val typeContext: ConeTypeContex
nullability.isNullable,
attributes
)
is ConeClassLikeType -> error("Unknown class-like type to substitute: $this, ${this::class}")
else -> error("Unknown type to substitute: $this, ${this::class}")
else -> error("Unknown class-like type to substitute: $this, ${this::class}")
}
}
return null
+1 -3
View File
@@ -1,6 +1,4 @@
// IGNORE_BACKEND: JVM
// IGNORE_BACKEND_FIR: JVM_IR
// FIR status: UPPER_BOUND_VIOLATED at Service<out T>
inline fun <
reified TService : Service<TService, TEvent>,
@@ -25,4 +23,4 @@ class SomeService : Service<SomeService, SomeService.SomeEvent> {
fun box(): String {
event { someEvent: SomeService.SomeEvent -> } // REIFIED_TYPE_FORBIDDEN_SUBSTITUTION
return "OK"
}
}
@@ -0,0 +1,23 @@
// SKIP_TXT
// !LANGUAGE: +ProperTypeInferenceConstraintsProcessing
class A<T, F : T>
fun foo(a: A<*, in CharSequence>) {}
fun <T, U> coerce(t: T): U {
val constrain: Constrain<U, *, in T>? = null
val bind = Bind(constrain)
return bind.upcast(t)
}
class Constrain<A, B : A, C : B>
class Bind<A, B : A, C : B>(val constrain: Constrain<A, B, C>?) {
fun upcast(c: C): A = c
}
fun <T, U> coerce2(t: T): U {
// We might report an error on unsound type reference Constrain<U, *, T>?, too
val constrain: Constrain<U, *, T>? = null
val bind = Bind(<!ARGUMENT_TYPE_MISMATCH!>constrain<!>) // WARNING: Type mismatch: inferred type is T but U was expected
return bind.upcast(t)
}
@@ -0,0 +1,23 @@
// SKIP_TXT
// !LANGUAGE: +ProperTypeInferenceConstraintsProcessing
class A<T, F : T>
fun foo(a: A<*, in CharSequence>) {}
fun <T, U> coerce(t: T): U {
val constrain: Constrain<U, *, in T>? = null
val bind = Bind(constrain)
return bind.upcast(t)
}
class Constrain<A, B : A, C : B>
class Bind<A, B : A, C : B>(val constrain: Constrain<A, B, C>?) {
fun upcast(c: C): A = c
}
fun <T, U> coerce2(t: T): U {
// We might report an error on unsound type reference Constrain<U, *, T>?, too
val constrain: Constrain<U, *, T>? = null
val bind = Bind(<!TYPE_MISMATCH, TYPE_MISMATCH!>constrain<!>) // WARNING: Type mismatch: inferred type is T but U was expected
return bind.upcast(t)
}
@@ -0,0 +1,22 @@
// FIR_IDENTICAL
// SKIP_TXT
// !LANGUAGE: +ProperTypeInferenceConstraintsProcessing
fun main(args: Array<String>) {
val zero = coerce<Int, String>(0)
}
fun <T, U> coerce(t: T): U {
// Should be an error somewhere because this code leads to unsoundness
// We may report that `Constrain<U, *, in T>?` type definition is unsound or the call `Bind(constrain)`
// See KT-50230
val constrain: Constrain<U, *, in T>? = null
val bind = Bind(constrain)
return bind.upcast(t)
}
class Constrain<A, B : A, C : B>
class Bind<A, B : A, C : B>(val constrain: Constrain<A, B, C>?) {
fun upcast(c: C): A = c
}
@@ -0,0 +1,11 @@
// FIR_IDENTICAL
// SKIP_TXT
interface A
interface B<X : A>
interface C<E : A, F : B<E>>
fun foo1(c: C<out A, out B<*>>) {}
fun foo2(c: C<*, B<*>>) {}
fun <T : B<*>> foo3(c: C<*, T>) {}
fun <T : B<*>> foo4(c: C<out A, T>) {}
@@ -13887,6 +13887,18 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
runTest("compiler/testData/diagnostics/tests/inference/underscoredTypeInForbiddenPositions.kt");
}
@Test
@TestMetadata("unsoundness1.kt")
public void testUnsoundness1() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/unsoundness1.kt");
}
@Test
@TestMetadata("unsoundness2.kt")
public void testUnsoundness2() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/unsoundness2.kt");
}
@Test
@TestMetadata("useFunctionLiteralsToInferType.kt")
public void testUseFunctionLiteralsToInferType() throws Exception {
@@ -31173,6 +31185,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
runTest("compiler/testData/diagnostics/tests/typeParameters/repeatedBound.kt");
}
@Test
@TestMetadata("starProjectionInsteadOutCaptured.kt")
public void testStarProjectionInsteadOutCaptured() throws Exception {
runTest("compiler/testData/diagnostics/tests/typeParameters/starProjectionInsteadOutCaptured.kt");
}
@Test
@TestMetadata("upperBoundCannotBeArray.kt")
public void testUpperBoundCannotBeArray() throws Exception {