[FIR] Fully expand the bounds of type parameters for canBeNull check
This is required because a not-null bound `Foo` can resolve to a `typealias Foo = Bar?` in which case we must return true. #KT-64645 Fixed
This commit is contained in:
committed by
Space Team
parent
438b2dd164
commit
1018ff280e
+1
-1
@@ -35,7 +35,7 @@ internal class KtFirTypeInfoProvider(
|
|||||||
return (type as KtFirType).coneType.functionTypeKind(analysisSession.useSiteSession)
|
return (type as KtFirType).coneType.functionTypeKind(analysisSession.useSiteSession)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun canBeNull(type: KtType): Boolean = (type as KtFirType).coneType.canBeNull
|
override fun canBeNull(type: KtType): Boolean = (type as KtFirType).coneType.canBeNull(analysisSession.useSiteSession)
|
||||||
|
|
||||||
override fun isDenotable(type: KtType): Boolean {
|
override fun isDenotable(type: KtType): Boolean {
|
||||||
val coneType = (type as KtFirType).coneType
|
val coneType = (type as KtFirType).coneType
|
||||||
|
|||||||
+6
@@ -35179,6 +35179,12 @@ public class DiagnosticCompilerTestFE10TestdataTestGenerated extends AbstractDia
|
|||||||
runTest("compiler/testData/diagnostics/tests/smartCasts/castchecks/insideCall.kt");
|
runTest("compiler/testData/diagnostics/tests/smartCasts/castchecks/insideCall.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("kt64645.kt")
|
||||||
|
public void testKt64645() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/smartCasts/castchecks/kt64645.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("smartCastOfNullableExpressionWithExpectedType.kt")
|
@TestMetadata("smartCastOfNullableExpressionWithExpectedType.kt")
|
||||||
public void testSmartCastOfNullableExpressionWithExpectedType() throws Exception {
|
public void testSmartCastOfNullableExpressionWithExpectedType() throws Exception {
|
||||||
|
|||||||
+6
@@ -35179,6 +35179,12 @@ public class LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated
|
|||||||
runTest("compiler/testData/diagnostics/tests/smartCasts/castchecks/insideCall.kt");
|
runTest("compiler/testData/diagnostics/tests/smartCasts/castchecks/insideCall.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("kt64645.kt")
|
||||||
|
public void testKt64645() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/smartCasts/castchecks/kt64645.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("smartCastOfNullableExpressionWithExpectedType.kt")
|
@TestMetadata("smartCastOfNullableExpressionWithExpectedType.kt")
|
||||||
public void testSmartCastOfNullableExpressionWithExpectedType() throws Exception {
|
public void testSmartCastOfNullableExpressionWithExpectedType() throws Exception {
|
||||||
|
|||||||
+6
@@ -32905,6 +32905,12 @@ public class FirLightTreeOldFrontendDiagnosticsTestGenerated extends AbstractFir
|
|||||||
runTest("compiler/testData/diagnostics/tests/smartCasts/castchecks/insideCall.kt");
|
runTest("compiler/testData/diagnostics/tests/smartCasts/castchecks/insideCall.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("kt64645.kt")
|
||||||
|
public void testKt64645() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/smartCasts/castchecks/kt64645.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("smartCastOfNullableExpressionWithExpectedType.kt")
|
@TestMetadata("smartCastOfNullableExpressionWithExpectedType.kt")
|
||||||
public void testSmartCastOfNullableExpressionWithExpectedType() throws Exception {
|
public void testSmartCastOfNullableExpressionWithExpectedType() throws Exception {
|
||||||
|
|||||||
+6
@@ -33025,6 +33025,12 @@ public class FirPsiOldFrontendDiagnosticsTestGenerated extends AbstractFirPsiDia
|
|||||||
runTest("compiler/testData/diagnostics/tests/smartCasts/castchecks/insideCall.kt");
|
runTest("compiler/testData/diagnostics/tests/smartCasts/castchecks/insideCall.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("kt64645.kt")
|
||||||
|
public void testKt64645() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/smartCasts/castchecks/kt64645.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("smartCastOfNullableExpressionWithExpectedType.kt")
|
@TestMetadata("smartCastOfNullableExpressionWithExpectedType.kt")
|
||||||
public void testSmartCastOfNullableExpressionWithExpectedType() throws Exception {
|
public void testSmartCastOfNullableExpressionWithExpectedType() throws Exception {
|
||||||
|
|||||||
+1
-1
@@ -151,7 +151,7 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
|
|||||||
private fun Operation.canBeTrueFor(session: FirSession, type: ConeKotlinType): Boolean = when (this) {
|
private fun Operation.canBeTrueFor(session: FirSession, type: ConeKotlinType): Boolean = when (this) {
|
||||||
Operation.EqTrue, Operation.EqFalse ->
|
Operation.EqTrue, Operation.EqFalse ->
|
||||||
AbstractTypeChecker.isSubtypeOf(session.typeContext, session.builtinTypes.booleanType.type, type)
|
AbstractTypeChecker.isSubtypeOf(session.typeContext, session.builtinTypes.booleanType.type, type)
|
||||||
Operation.EqNull -> type.canBeNull
|
Operation.EqNull -> type.canBeNull(session)
|
||||||
Operation.NotEqNull -> !type.isNullableNothing
|
Operation.NotEqNull -> !type.isNullableNothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-6
@@ -7,14 +7,11 @@ package org.jetbrains.kotlin.fir.analysis.checkers
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.fir.FirSession
|
import org.jetbrains.kotlin.fir.FirSession
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||||
import org.jetbrains.kotlin.fir.declarations.utils.isInner
|
|
||||||
import org.jetbrains.kotlin.fir.declarations.utils.isInterface
|
import org.jetbrains.kotlin.fir.declarations.utils.isInterface
|
||||||
import org.jetbrains.kotlin.fir.declarations.utils.isLocal
|
import org.jetbrains.kotlin.fir.declarations.utils.isLocal
|
||||||
import org.jetbrains.kotlin.fir.getContainingClassLookupTag
|
|
||||||
import org.jetbrains.kotlin.fir.resolve.defaultType
|
import org.jetbrains.kotlin.fir.resolve.defaultType
|
||||||
import org.jetbrains.kotlin.fir.resolve.getClassAndItsOuterClassesWhenLocal
|
import org.jetbrains.kotlin.fir.resolve.getClassAndItsOuterClassesWhenLocal
|
||||||
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutorByMap
|
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutorByMap
|
||||||
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
|
|
||||||
import org.jetbrains.kotlin.fir.scopes.platformClassMapper
|
import org.jetbrains.kotlin.fir.scopes.platformClassMapper
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
|
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
||||||
@@ -58,8 +55,8 @@ fun checkCasting(
|
|||||||
return if (result) CastingType.Possible else CastingType.Impossible
|
return if (result) CastingType.Possible else CastingType.Impossible
|
||||||
}
|
}
|
||||||
|
|
||||||
val lhsNullable = lhsLowerType.canBeNull
|
val lhsNullable = lhsLowerType.canBeNull(session)
|
||||||
val rhsNullable = rhsLowerType.canBeNull
|
val rhsNullable = rhsLowerType.canBeNull(session)
|
||||||
if (lhsLowerType.isNothing) return CastingType.Possible
|
if (lhsLowerType.isNothing) return CastingType.Possible
|
||||||
if (lhsLowerType.isNullableNothing && !rhsNullable) {
|
if (lhsLowerType.isNullableNothing && !rhsNullable) {
|
||||||
return if (isSafeCase) CastingType.Always else CastingType.Impossible
|
return if (isSafeCase) CastingType.Always else CastingType.Impossible
|
||||||
@@ -150,7 +147,7 @@ fun isCastErased(supertype: ConeKotlinType, subtype: ConeKotlinType, context: Ch
|
|||||||
// here we want to restrict cases such as `x is T` for x = T?, when T might have nullable upper bound
|
// here we want to restrict cases such as `x is T` for x = T?, when T might have nullable upper bound
|
||||||
if (isNonReifiedTypeParameter && !isUpcast) {
|
if (isNonReifiedTypeParameter && !isUpcast) {
|
||||||
// hack to save previous behavior in case when `x is T`, where T is not nullable, see IsErasedNullableTasT.kt
|
// hack to save previous behavior in case when `x is T`, where T is not nullable, see IsErasedNullableTasT.kt
|
||||||
val nullableToDefinitelyNotNull = !subtype.canBeNull && supertype.withNullability(ConeNullability.NOT_NULL, typeContext) == subtype
|
val nullableToDefinitelyNotNull = !subtype.canBeNull(context.session) && supertype.withNullability(ConeNullability.NOT_NULL, typeContext) == subtype
|
||||||
if (!nullableToDefinitelyNotNull) {
|
if (!nullableToDefinitelyNotNull) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ object FirNotNullAssertionChecker : FirCheckNotNullCallChecker() {
|
|||||||
|
|
||||||
val type = argument.resolvedType.fullyExpandedType(context.session)
|
val type = argument.resolvedType.fullyExpandedType(context.session)
|
||||||
|
|
||||||
if (!type.canBeNull && context.languageVersionSettings.supportsFeature(LanguageFeature.EnableDfaWarningsInK2)) {
|
if (!type.canBeNull(context.session) && context.languageVersionSettings.supportsFeature(LanguageFeature.EnableDfaWarningsInK2)) {
|
||||||
reporter.reportOn(expression.source, FirErrors.UNNECESSARY_NOT_NULL_ASSERTION, type, context)
|
reporter.reportOn(expression.source, FirErrors.UNNECESSARY_NOT_NULL_ASSERTION, type, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ object FirSpreadOfNullableChecker : FirFunctionCallChecker() {
|
|||||||
override fun check(expression: FirFunctionCall, context: CheckerContext, reporter: DiagnosticReporter) {
|
override fun check(expression: FirFunctionCall, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||||
fun checkAndReport(argument: FirExpression, source: KtSourceElement?) {
|
fun checkAndReport(argument: FirExpression, source: KtSourceElement?) {
|
||||||
val coneType = argument.resolvedType
|
val coneType = argument.resolvedType
|
||||||
if (argument is FirSpreadArgumentExpression && coneType !is ConeFlexibleType && coneType.canBeNull) {
|
if (argument is FirSpreadArgumentExpression && coneType !is ConeFlexibleType && coneType.canBeNull(context.session)) {
|
||||||
reporter.reportOn(source, FirErrors.SPREAD_OF_NULLABLE, context)
|
reporter.reportOn(source, FirErrors.SPREAD_OF_NULLABLE, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ object FirUnnecessarySafeCallChecker : FirSafeCallExpressionChecker() {
|
|||||||
reporter.reportOn(expression.source, FirErrors.UNEXPECTED_SAFE_CALL, context)
|
reporter.reportOn(expression.source, FirErrors.UNEXPECTED_SAFE_CALL, context)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!receiverType.canBeNull) {
|
if (!receiverType.canBeNull(context.session)) {
|
||||||
if (context.languageVersionSettings.supportsFeature(LanguageFeature.EnableDfaWarningsInK2)) {
|
if (context.languageVersionSettings.supportsFeature(LanguageFeature.EnableDfaWarningsInK2)) {
|
||||||
reporter.reportOn(expression.source, FirErrors.UNNECESSARY_SAFE_CALL, receiverType, context)
|
reporter.reportOn(expression.source, FirErrors.UNNECESSARY_SAFE_CALL, receiverType, context)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ object FirUselessElvisChecker : FirElvisExpressionChecker() {
|
|||||||
|
|
||||||
val lhsType = expression.lhs.resolvedType
|
val lhsType = expression.lhs.resolvedType
|
||||||
if (lhsType is ConeErrorType) return
|
if (lhsType is ConeErrorType) return
|
||||||
if (!lhsType.canBeNull) {
|
if (!lhsType.canBeNull(context.session)) {
|
||||||
if (context.languageVersionSettings.supportsFeature(LanguageFeature.EnableDfaWarningsInK2)) {
|
if (context.languageVersionSettings.supportsFeature(LanguageFeature.EnableDfaWarningsInK2)) {
|
||||||
reporter.reportOn(expression.source, FirErrors.USELESS_ELVIS, lhsType, context)
|
reporter.reportOn(expression.source, FirErrors.USELESS_ELVIS, lhsType, context)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ object FirDefinitelyNotNullableChecker : FirTypeRefChecker() {
|
|||||||
reporter.reportOn(intersection.source, FirErrors.NULLABLE_ON_DEFINITELY_NOT_NULLABLE, context)
|
reporter.reportOn(intersection.source, FirErrors.NULLABLE_ON_DEFINITELY_NOT_NULLABLE, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!intersection.isLeftValidForDefinitelyNotNullable) {
|
if (!intersection.isLeftValidForDefinitelyNotNullable(context.session)) {
|
||||||
reporter.reportOn(intersection.leftType.source, FirErrors.INCORRECT_LEFT_COMPONENT_OF_INTERSECTION, context)
|
reporter.reportOn(intersection.leftType.source, FirErrors.INCORRECT_LEFT_COMPONENT_OF_INTERSECTION, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-7
@@ -6,15 +6,13 @@
|
|||||||
package org.jetbrains.kotlin.fir.backend
|
package org.jetbrains.kotlin.fir.backend
|
||||||
|
|
||||||
import org.jetbrains.kotlin.fir.FirElement
|
import org.jetbrains.kotlin.fir.FirElement
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration
|
import org.jetbrains.kotlin.fir.FirSession
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirTypeAlias
|
import org.jetbrains.kotlin.fir.declarations.FirTypeAlias
|
||||||
import org.jetbrains.kotlin.fir.expressions.*
|
import org.jetbrains.kotlin.fir.expressions.*
|
||||||
import org.jetbrains.kotlin.fir.expressions.impl.FirUnitExpression
|
import org.jetbrains.kotlin.fir.expressions.impl.FirUnitExpression
|
||||||
import org.jetbrains.kotlin.fir.references.toResolvedCallableSymbol
|
import org.jetbrains.kotlin.fir.references.toResolvedCallableSymbol
|
||||||
import org.jetbrains.kotlin.fir.render
|
import org.jetbrains.kotlin.fir.render
|
||||||
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
|
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
|
||||||
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
|
|
||||||
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutorByMap
|
|
||||||
import org.jetbrains.kotlin.fir.types.*
|
import org.jetbrains.kotlin.fir.types.*
|
||||||
import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor
|
import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor
|
||||||
import org.jetbrains.kotlin.ir.IrBuiltIns
|
import org.jetbrains.kotlin.ir.IrBuiltIns
|
||||||
@@ -235,7 +233,7 @@ class Fir2IrImplicitCastInserter(
|
|||||||
this
|
this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
typeCanBeEnhancedOrFlexibleNullable(expandedValueType) && !expandedExpectedType.acceptsNullValues() -> {
|
typeCanBeEnhancedOrFlexibleNullable(expandedValueType, session) && !expandedExpectedType.acceptsNullValues() -> {
|
||||||
insertImplicitNotNullCastIfNeeded(expression)
|
insertImplicitNotNullCastIfNeeded(expression)
|
||||||
}
|
}
|
||||||
// TODO: coerceIntToAnotherIntegerType
|
// TODO: coerceIntToAnotherIntegerType
|
||||||
@@ -258,7 +256,7 @@ class Fir2IrImplicitCastInserter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun ConeKotlinType.acceptsNullValues(): Boolean {
|
private fun ConeKotlinType.acceptsNullValues(): Boolean {
|
||||||
return canBeNull || hasEnhancedNullability
|
return canBeNull(session) || hasEnhancedNullability
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun IrExpression.insertImplicitNotNullCastIfNeeded(expression: FirExpression): IrExpression {
|
private fun IrExpression.insertImplicitNotNullCastIfNeeded(expression: FirExpression): IrExpression {
|
||||||
@@ -432,10 +430,10 @@ class Fir2IrImplicitCastInserter(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun typeCanBeEnhancedOrFlexibleNullable(type: ConeKotlinType): Boolean {
|
internal fun typeCanBeEnhancedOrFlexibleNullable(type: ConeKotlinType, session: FirSession): Boolean {
|
||||||
return when {
|
return when {
|
||||||
type.hasEnhancedNullability -> true
|
type.hasEnhancedNullability -> true
|
||||||
type.hasFlexibleNullability && type.canBeNull -> true
|
type.hasFlexibleNullability && type.canBeNull(session) -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -303,7 +303,7 @@ class DelegatedMemberGenerator(private val components: Fir2IrComponents) : Fir2I
|
|||||||
|
|
||||||
val substitutor = substitutorByMap(substitution, session)
|
val substitutor = substitutorByMap(substitution, session)
|
||||||
val substitutedType = substitutor.substituteOrSelf(originalFirDeclaration.returnTypeRef.coneType)
|
val substitutedType = substitutor.substituteOrSelf(originalFirDeclaration.returnTypeRef.coneType)
|
||||||
callTypeCanBeNullable = Fir2IrImplicitCastInserter.typeCanBeEnhancedOrFlexibleNullable(substitutedType)
|
callTypeCanBeNullable = Fir2IrImplicitCastInserter.typeCanBeEnhancedOrFlexibleNullable(substitutedType, session)
|
||||||
substitutedType.toIrType(typeOrigin)
|
substitutedType.toIrType(typeOrigin)
|
||||||
}
|
}
|
||||||
true -> {
|
true -> {
|
||||||
|
|||||||
@@ -780,3 +780,24 @@ fun ConeKotlinType.convertToNonRawVersion(): ConeKotlinType {
|
|||||||
|
|
||||||
return withAttributes(attributes.remove(CompilerConeAttributes.RawType))
|
return withAttributes(attributes.remove(CompilerConeAttributes.RawType))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ConeKotlinType.canBeNull(session: FirSession): Boolean {
|
||||||
|
if (isMarkedNullable) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return when (this) {
|
||||||
|
is ConeFlexibleType -> upperBound.canBeNull(session)
|
||||||
|
is ConeDefinitelyNotNullType -> false
|
||||||
|
is ConeTypeParameterType -> this.lookupTag.typeParameterSymbol.resolvedBounds.all {
|
||||||
|
// Upper bounds can resolve to typealiases that can expand to nullable types.
|
||||||
|
it.coneType.fullyExpandedType(session).canBeNull(session)
|
||||||
|
}
|
||||||
|
is ConeIntersectionType -> intersectedTypes.all { it.canBeNull(session) }
|
||||||
|
else -> fullyExpandedType(session).isNullable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun FirIntersectionTypeRef.isLeftValidForDefinitelyNotNullable(session: FirSession) =
|
||||||
|
leftType.coneType.let { it is ConeTypeParameterType && it.canBeNull(session) && !it.isMarkedNullable }
|
||||||
|
|
||||||
|
val FirIntersectionTypeRef.isRightValidForDefinitelyNotNullable get() = rightType.coneType.isAny
|
||||||
@@ -197,7 +197,7 @@ object CheckDispatchReceiver : ResolutionStage() {
|
|||||||
|
|
||||||
if (smartcastedReceiver != null &&
|
if (smartcastedReceiver != null &&
|
||||||
!smartcastedReceiver.isStable &&
|
!smartcastedReceiver.isStable &&
|
||||||
(isCandidateFromUnstableSmartcast || (isReceiverNullable && !smartcastedReceiver.smartcastType.canBeNull))
|
(isCandidateFromUnstableSmartcast || (isReceiverNullable && !smartcastedReceiver.smartcastType.coneType.canBeNull(callInfo.session)))
|
||||||
) {
|
) {
|
||||||
val dispatchReceiverType = (candidate.symbol as? FirCallableSymbol<*>)?.dispatchReceiverType?.let {
|
val dispatchReceiverType = (candidate.symbol as? FirCallableSymbol<*>)?.dispatchReceiverType?.let {
|
||||||
context.session.typeApproximator.approximateToSuperType(
|
context.session.typeApproximator.approximateToSuperType(
|
||||||
|
|||||||
+1
-1
@@ -118,7 +118,7 @@ class MemberScopeTowerLevel(
|
|||||||
// with two exceptions:
|
// with two exceptions:
|
||||||
// - When the smart-casted type is always null, we want to return it and report UNSAFE_CALL.
|
// - When the smart-casted type is always null, we want to return it and report UNSAFE_CALL.
|
||||||
// - When the original type can be null, in this case the smart-case either makes it not-null or the call is red anyway.
|
// - When the original type can be null, in this case the smart-case either makes it not-null or the call is red anyway.
|
||||||
if (existing == null || dispatchReceiverValue.type.isNullableNothing || receiverTypeWithoutSmartCast.canBeNull) {
|
if (existing == null || dispatchReceiverValue.type.isNullableNothing || receiverTypeWithoutSmartCast.canBeNull(session)) {
|
||||||
map[candidateFromSmartCast.member] = MemberFromSmartcastScope(candidateFromSmartCast, DispatchReceiverToUse.SmartcastWithoutUnwrapping)
|
map[candidateFromSmartCast.member] = MemberFromSmartcastScope(candidateFromSmartCast, DispatchReceiverToUse.SmartcastWithoutUnwrapping)
|
||||||
} else {
|
} else {
|
||||||
existing.dispatchReceiverToUse = DispatchReceiverToUse.SmartcastIfUnwrappedInvisible
|
existing.dispatchReceiverToUse = DispatchReceiverToUse.SmartcastIfUnwrappedInvisible
|
||||||
|
|||||||
+6
-5
@@ -368,7 +368,7 @@ abstract class FirDataFlowAnalyzer(
|
|||||||
if (operandVariable.isReal()) {
|
if (operandVariable.isReal()) {
|
||||||
flow.addImplication((expressionVariable eq isType) implies (operandVariable typeEq type))
|
flow.addImplication((expressionVariable eq isType) implies (operandVariable typeEq type))
|
||||||
}
|
}
|
||||||
if (!type.canBeNull) {
|
if (!type.canBeNull(components.session)) {
|
||||||
// x is (T & Any) => x != null
|
// x is (T & Any) => x != null
|
||||||
flow.addImplication((expressionVariable eq isType) implies (operandVariable notEq null))
|
flow.addImplication((expressionVariable eq isType) implies (operandVariable notEq null))
|
||||||
} else if (type.isMarkedNullable) {
|
} else if (type.isMarkedNullable) {
|
||||||
@@ -383,7 +383,7 @@ abstract class FirDataFlowAnalyzer(
|
|||||||
if (operandVariable.isReal()) {
|
if (operandVariable.isReal()) {
|
||||||
flow.addTypeStatement(operandVariable typeEq type)
|
flow.addTypeStatement(operandVariable typeEq type)
|
||||||
}
|
}
|
||||||
if (!type.canBeNull) {
|
if (!type.canBeNull(components.session)) {
|
||||||
flow.commitOperationStatement(operandVariable notEq null)
|
flow.commitOperationStatement(operandVariable notEq null)
|
||||||
} else {
|
} else {
|
||||||
val expressionVariable = variableStorage.createSynthetic(typeOperatorCall)
|
val expressionVariable = variableStorage.createSynthetic(typeOperatorCall)
|
||||||
@@ -998,9 +998,10 @@ abstract class FirDataFlowAnalyzer(
|
|||||||
for (conditionalEffect in conditionalEffects) {
|
for (conditionalEffect in conditionalEffects) {
|
||||||
val effect = conditionalEffect.effect as? ConeReturnsEffectDeclaration ?: continue
|
val effect = conditionalEffect.effect as? ConeReturnsEffectDeclaration ?: continue
|
||||||
val operation = effect.value.toOperation()
|
val operation = effect.value.toOperation()
|
||||||
val statements = logicSystem.approveContractStatement(conditionalEffect.condition, argumentVariables, substitutor) {
|
val statements =
|
||||||
logicSystem.approveOperationStatement(flow, it, removeApprovedOrImpossible = operation == null)
|
logicSystem.approveContractStatement(conditionalEffect.condition, argumentVariables, substitutor) {
|
||||||
} ?: continue // TODO: do what if the result is known to be false?
|
logicSystem.approveOperationStatement(flow, it, removeApprovedOrImpossible = operation == null)
|
||||||
|
} ?: continue // TODO: do what if the result is known to be false?
|
||||||
if (operation == null) {
|
if (operation == null) {
|
||||||
flow.addAllStatements(statements)
|
flow.addAllStatements(statements)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -6,14 +6,16 @@
|
|||||||
package org.jetbrains.kotlin.fir.resolve.dfa
|
package org.jetbrains.kotlin.fir.resolve.dfa
|
||||||
|
|
||||||
import kotlinx.collections.immutable.*
|
import kotlinx.collections.immutable.*
|
||||||
|
import org.jetbrains.kotlin.fir.FirSession
|
||||||
import org.jetbrains.kotlin.fir.types.*
|
import org.jetbrains.kotlin.fir.types.*
|
||||||
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
abstract class LogicSystem(private val context: ConeInferenceContext) {
|
abstract class LogicSystem(private val context: ConeInferenceContext) {
|
||||||
private val nullableNothingType = context.session.builtinTypes.nullableNothingType.type
|
val session: FirSession get() = context.session
|
||||||
private val anyType = context.session.builtinTypes.anyType.type
|
private val nullableNothingType = session.builtinTypes.nullableNothingType.type
|
||||||
|
private val anyType = session.builtinTypes.anyType.type
|
||||||
|
|
||||||
abstract val variableStorage: VariableStorageImpl
|
abstract val variableStorage: VariableStorageImpl
|
||||||
|
|
||||||
@@ -340,7 +342,7 @@ abstract class LogicSystem(private val context: ConeInferenceContext) {
|
|||||||
val result = when {
|
val result = when {
|
||||||
unified.isNullableAny -> return null
|
unified.isNullableAny -> return null
|
||||||
unified.isAcceptableForSmartcast() -> unified
|
unified.isAcceptableForSmartcast() -> unified
|
||||||
unified.canBeNull -> return null
|
unified.canBeNull(context.session) -> return null
|
||||||
else -> context.anyType()
|
else -> context.anyType()
|
||||||
}
|
}
|
||||||
return PersistentTypeStatement(variable, persistentSetOf(result))
|
return PersistentTypeStatement(variable, persistentSetOf(result))
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
package org.jetbrains.kotlin.fir.resolve.dfa
|
package org.jetbrains.kotlin.fir.resolve.dfa
|
||||||
|
|
||||||
import org.jetbrains.kotlin.contracts.description.LogicOperationKind
|
import org.jetbrains.kotlin.contracts.description.LogicOperationKind
|
||||||
|
import org.jetbrains.kotlin.fir.FirSession
|
||||||
import org.jetbrains.kotlin.fir.contracts.description.*
|
import org.jetbrains.kotlin.fir.contracts.description.*
|
||||||
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
|
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
|
||||||
import org.jetbrains.kotlin.fir.types.canBeNull
|
import org.jetbrains.kotlin.fir.types.canBeNull
|
||||||
@@ -45,7 +46,7 @@ fun LogicSystem.approveContractStatement(
|
|||||||
substitutedType.isNullableNothing -> it.processEqNull(isType)
|
substitutedType.isNullableNothing -> it.processEqNull(isType)
|
||||||
else -> {
|
else -> {
|
||||||
// x is (T & Any) || x !is T? => x != null
|
// x is (T & Any) || x !is T? => x != null
|
||||||
val fromNullability = if ((isType && !type.canBeNull) || (!isType && type.isMarkedNullable))
|
val fromNullability = if ((isType && !type.canBeNull(session)) || (!isType && type.isMarkedNullable))
|
||||||
it.processEqNull(false)
|
it.processEqNull(false)
|
||||||
else
|
else
|
||||||
mapOf()
|
mapOf()
|
||||||
|
|||||||
@@ -155,25 +155,3 @@ fun FirTypeProjection.toConeTypeProjection(): ConeTypeProjection = when (this) {
|
|||||||
}
|
}
|
||||||
else -> errorWithAttachment("Unexpected ${this::class.simpleName}") { withFirEntry("projection", this@toConeTypeProjection) }
|
else -> errorWithAttachment("Unexpected ${this::class.simpleName}") { withFirEntry("projection", this@toConeTypeProjection) }
|
||||||
}
|
}
|
||||||
|
|
||||||
val FirTypeRef.canBeNull: Boolean
|
|
||||||
get() = coneType.canBeNull
|
|
||||||
|
|
||||||
val ConeKotlinType.canBeNull: Boolean
|
|
||||||
get() {
|
|
||||||
if (isMarkedNullable) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return when (this) {
|
|
||||||
is ConeFlexibleType -> upperBound.canBeNull
|
|
||||||
is ConeDefinitelyNotNullType -> false
|
|
||||||
is ConeTypeParameterType -> this.lookupTag.typeParameterSymbol.resolvedBounds.all { it.coneType.canBeNull }
|
|
||||||
is ConeIntersectionType -> intersectedTypes.all { it.canBeNull }
|
|
||||||
else -> isNullable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val FirIntersectionTypeRef.isLeftValidForDefinitelyNotNullable
|
|
||||||
get() = leftType.coneType.let { it is ConeTypeParameterType && it.canBeNull && !it.isMarkedNullable }
|
|
||||||
|
|
||||||
val FirIntersectionTypeRef.isRightValidForDefinitelyNotNullable get() = rightType.coneType.isAny
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
|
// DIAGNOSTICS: -DEBUG_INFO_SMARTCAST, -FINAL_UPPER_BOUND
|
||||||
|
// ISSUE: KT-64645
|
||||||
|
|
||||||
|
class Box(val value: String)
|
||||||
|
typealias NullableBox = Box?
|
||||||
|
|
||||||
|
fun <T : NullableBox> foo(x: T) {
|
||||||
|
if (x != null) {
|
||||||
|
x.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun foo2(x: NullableBox) {
|
||||||
|
if (x != null) {
|
||||||
|
x.value
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+6
@@ -35179,6 +35179,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
|
|||||||
runTest("compiler/testData/diagnostics/tests/smartCasts/castchecks/insideCall.kt");
|
runTest("compiler/testData/diagnostics/tests/smartCasts/castchecks/insideCall.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("kt64645.kt")
|
||||||
|
public void testKt64645() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/smartCasts/castchecks/kt64645.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("smartCastOfNullableExpressionWithExpectedType.kt")
|
@TestMetadata("smartCastOfNullableExpressionWithExpectedType.kt")
|
||||||
public void testSmartCastOfNullableExpressionWithExpectedType() throws Exception {
|
public void testSmartCastOfNullableExpressionWithExpectedType() throws Exception {
|
||||||
|
|||||||
Reference in New Issue
Block a user