FIR: Avoid potentially dangerous checks at makesSenseToBeDefinitelyNotNull

Sometimes, it might be called before type parameter bounds are initialized
or even before the symbols are bound to FIR
In such cases, we just assume it makes sense to create DNN there
This commit is contained in:
Denis.Zharkov
2022-05-05 16:05:08 +03:00
committed by teamcity
parent c766f20554
commit 9f7876efb6
6 changed files with 34 additions and 22 deletions
@@ -7,15 +7,12 @@ package org.jetbrains.kotlin.fir.java
import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap
import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirTypeParameter
import org.jetbrains.kotlin.fir.diagnostics.ConeIntermediateDiagnostic
import org.jetbrains.kotlin.fir.diagnostics.ConeSimpleDiagnostic import org.jetbrains.kotlin.fir.diagnostics.ConeSimpleDiagnostic
import org.jetbrains.kotlin.fir.diagnostics.DiagnosticKind import org.jetbrains.kotlin.fir.diagnostics.DiagnosticKind
import org.jetbrains.kotlin.fir.java.enhancement.readOnlyToMutable import org.jetbrains.kotlin.fir.java.enhancement.readOnlyToMutable
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag
import org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl import org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef
import org.jetbrains.kotlin.fir.types.impl.ConeTypeParameterTypeImpl import org.jetbrains.kotlin.fir.types.impl.ConeTypeParameterTypeImpl
@@ -109,7 +106,7 @@ private fun JavaType?.toConeTypeProjection(
lowerBound, session.typeContext, lowerBound, session.typeContext,
// Upper bounds might be not initialized properly yet, so we force creating DefinitelyNotNullType // Upper bounds might be not initialized properly yet, so we force creating DefinitelyNotNullType
// It should not affect semantics, since it would be still a valid type anyway // It should not affect semantics, since it would be still a valid type anyway
forceWithoutCheck = true, avoidComprehensiveCheck = true,
) ?: lowerBound ) ?: lowerBound
else -> lowerBound else -> lowerBound
} }
@@ -47,7 +47,7 @@ abstract class AbstractConeSubstitutor(protected val typeContext: ConeTypeContex
override fun substituteOrNull(type: ConeKotlinType): ConeKotlinType? { override fun substituteOrNull(type: ConeKotlinType): ConeKotlinType? {
val newType = substituteType(type) val newType = substituteType(type)
if (newType != null && type is ConeDefinitelyNotNullType) { if (newType != null && type is ConeDefinitelyNotNullType) {
return newType.makeConeTypeDefinitelyNotNullOrNotNull(typeContext) return newType.makeConeTypeDefinitelyNotNullOrNotNull(typeContext, avoidComprehensiveCheck = false)
} }
return (newType ?: type.substituteRecursive()) return (newType ?: type.substituteRecursive())
} }
@@ -109,7 +109,9 @@ abstract class AbstractConeSubstitutor(protected val typeContext: ConeTypeContex
typeContext, typeContext,
substitutedOriginal.attributes.add(original.attributes) substitutedOriginal.attributes.add(original.attributes)
) )
return ConeDefinitelyNotNullType.create(substituted, typeContext) ?: substituted return ConeDefinitelyNotNullType.create(
substituted, typeContext, avoidComprehensiveCheck = true,
) ?: substituted
} }
private fun ConeFlexibleType.substituteBounds(): ConeFlexibleType? { private fun ConeFlexibleType.substituteBounds(): ConeFlexibleType? {
@@ -58,13 +58,16 @@ fun ConeInferenceContext.intersectTypesOrNull(types: List<ConeKotlinType>): Cone
fun TypeCheckerProviderContext.equalTypes(a: ConeKotlinType, b: ConeKotlinType): Boolean = fun TypeCheckerProviderContext.equalTypes(a: ConeKotlinType, b: ConeKotlinType): Boolean =
AbstractTypeChecker.equalTypes(this, a, b) AbstractTypeChecker.equalTypes(this, a, b)
private fun ConeTypeContext.makesSenseToBeDefinitelyNotNull(originalType: ConeKotlinType): Boolean { private fun ConeTypeContext.makesSenseToBeDefinitelyNotNull(
return when (val type = originalType.lowerBoundIfFlexible()) { type: ConeSimpleKotlinType,
is ConeTypeParameterType -> !type.lookupTag.symbol.isInitializedFir || type.isNullableType() avoidComprehensiveCheck: Boolean,
): Boolean {
return when (type) {
is ConeTypeParameterType -> avoidComprehensiveCheck || type.isNullableType()
// Actually, this branch should work for type parameters as well, but it breaks some cases. See KT-40114. // Actually, this branch should work for type parameters as well, but it breaks some cases. See KT-40114.
// Basically, if we have `T : X..X?`, then `T <: Any` but we still have `T` != `T & Any`. // Basically, if we have `T : X..X?`, then `T <: Any` but we still have `T` != `T & Any`.
is ConeTypeVariableType, is ConeCapturedType -> { is ConeTypeVariableType, is ConeCapturedType -> {
!AbstractNullabilityChecker.isSubtypeOfAny( avoidComprehensiveCheck || !AbstractNullabilityChecker.isSubtypeOfAny(
newTypeCheckerState(errorTypesEqualToAnything = false, stubTypesEqualToAnything = false), type newTypeCheckerState(errorTypesEqualToAnything = false, stubTypesEqualToAnything = false), type
) )
} }
@@ -76,22 +79,32 @@ private fun ConeTypeContext.makesSenseToBeDefinitelyNotNull(originalType: ConeKo
fun ConeDefinitelyNotNullType.Companion.create( fun ConeDefinitelyNotNullType.Companion.create(
original: ConeKotlinType, original: ConeKotlinType,
typeContext: ConeTypeContext, typeContext: ConeTypeContext,
forceWithoutCheck: Boolean = false, // Sometimes, it might be called before type parameter bounds are initialized
// or even before the symbols are bound to FIR
// In such cases, we just assume it makes sense to create DNN there
// NB: `makesSenseToBeDefinitelyNotNull` is mostly an optimization, it should not affect semantics
avoidComprehensiveCheck: Boolean = false,
): ConeDefinitelyNotNullType? { ): ConeDefinitelyNotNullType? {
return when (original) { return when (original) {
is ConeDefinitelyNotNullType -> original is ConeDefinitelyNotNullType -> original
is ConeFlexibleType -> create(original.lowerBound, typeContext) is ConeFlexibleType -> create(original.lowerBound, typeContext, avoidComprehensiveCheck)
is ConeSimpleKotlinType -> runIf(forceWithoutCheck || typeContext.makesSenseToBeDefinitelyNotNull(original)) { is ConeSimpleKotlinType -> runIf(typeContext.makesSenseToBeDefinitelyNotNull(original, avoidComprehensiveCheck)) {
ConeDefinitelyNotNullType(original.coneLowerBoundIfFlexible()) ConeDefinitelyNotNullType(original.coneLowerBoundIfFlexible())
} }
} }
} }
fun ConeKotlinType.makeConeTypeDefinitelyNotNullOrNotNull(typeContext: ConeTypeContext): ConeKotlinType { fun ConeKotlinType.makeConeTypeDefinitelyNotNullOrNotNull(
typeContext: ConeTypeContext,
avoidComprehensiveCheck: Boolean = false,
): ConeKotlinType {
if (this is ConeIntersectionType) { if (this is ConeIntersectionType) {
return ConeIntersectionType(intersectedTypes.map { it.makeConeTypeDefinitelyNotNullOrNotNull(typeContext) }) return ConeIntersectionType(intersectedTypes.map {
it.makeConeTypeDefinitelyNotNullOrNotNull(typeContext, avoidComprehensiveCheck)
})
} }
return ConeDefinitelyNotNullType.create(this, typeContext) ?: this.withNullability(ConeNullability.NOT_NULL, typeContext) return ConeDefinitelyNotNullType.create(this, typeContext, avoidComprehensiveCheck)
?: this.withNullability(ConeNullability.NOT_NULL, typeContext)
} }
fun <T : ConeKotlinType> T.withArguments(arguments: Array<out ConeTypeProjection>): T { fun <T : ConeKotlinType> T.withArguments(arguments: Array<out ConeTypeProjection>): T {
@@ -89,7 +89,7 @@ fun main4() {
fun takeStarFoo3(x: Foo3<*>) { fun takeStarFoo3(x: Foo3<*>) {
x.value = <!ASSIGNMENT_TYPE_MISMATCH!>"test"<!> x.value = <!ASSIGNMENT_TYPE_MISMATCH!>"test"<!>
x.value <!NONE_APPLICABLE!>+=<!> "test" x.value <!UNRESOLVED_REFERENCE!>+=<!> "test"
} }
fun main5() { fun main5() {
@@ -89,7 +89,7 @@ fun main4() {
fun takeStarFoo3(x: Foo3<*>) { fun takeStarFoo3(x: Foo3<*>) {
x.value = <!ASSIGNMENT_TYPE_MISMATCH!>"test"<!> x.value = <!ASSIGNMENT_TYPE_MISMATCH!>"test"<!>
x.value <!NONE_APPLICABLE!>+=<!> "test" x.value <!UNRESOLVED_REFERENCE!>+=<!> "test"
} }
fun main5() { fun main5() {
@@ -26,10 +26,10 @@ fun test() {
} }
fun testJava(appender: JavaWriterAppender) { fun testJava(appender: JavaWriterAppender) {
<!DEBUG_INFO_EXPRESSION_TYPE("JavaWriterAppender.Builder1<*>..JavaWriterAppender.Builder1<*>?!")!>appender.newBuilder()<!> <!DEBUG_INFO_EXPRESSION_TYPE("JavaWriterAppender.Builder1<*>..JavaWriterAppender.Builder1<*>?!"), NEW_INFERENCE_ERROR!>appender.newBuilder()<!>
<!DEBUG_INFO_EXPRESSION_TYPE("JavaWriterAppender.Builder1<out JavaWriterAppender.Builder1<*>..JavaWriterAppender.Builder1<*>?!>")!>appender.Builder1()<!> <!DEBUG_INFO_EXPRESSION_TYPE("JavaWriterAppender.Builder1<out JavaWriterAppender.Builder1<*>..JavaWriterAppender.Builder1<*>?!>"), NEW_INFERENCE_ERROR!>appender.Builder1()<!>
<!DEBUG_INFO_EXPRESSION_TYPE("JavaWriterAppender.Builder1<*>..JavaWriterAppender.Builder1<*>?! & JavaWriterAppender.Builder2<*>..JavaWriterAppender.Builder2<*>?!..JavaWriterAppender.Builder1<*>? & JavaWriterAppender.Builder2<*>?")!>appender.intersectTwoSelfTypes()<!> <!DEBUG_INFO_EXPRESSION_TYPE("JavaWriterAppender.Builder1<*> & JavaWriterAppender.Builder2<*>..JavaWriterAppender.Builder1<*>? & JavaWriterAppender.Builder2<*>?"), NEW_INFERENCE_ERROR!>appender.intersectTwoSelfTypes()<!>
} }
object WriterAppender { object WriterAppender {