From 1018ff280e4868335bbf8705c657849c3aeb2f12 Mon Sep 17 00:00:00 2001 From: Kirill Rakhman Date: Tue, 2 Jan 2024 15:45:41 +0100 Subject: [PATCH] [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 --- .../fir/components/KtFirTypeInfoProvider.kt | 2 +- ...CompilerTestFE10TestdataTestGenerated.java | 6 +++++ ...sticCompilerFE10TestDataTestGenerated.java | 6 +++++ ...eeOldFrontendDiagnosticsTestGenerated.java | 6 +++++ ...siOldFrontendDiagnosticsTestGenerated.java | 6 +++++ .../analysis/cfa/FirReturnsImpliesAnalyzer.kt | 2 +- .../checkers/FirCastDiagnosticsHelpers.kt | 9 +++----- .../expression/FirNotNullAssertionChecker.kt | 2 +- .../expression/FirSpreadOfNullableChecker.kt | 2 +- .../FirUnnecessarySafeCallChecker.kt | 2 +- .../expression/FirUselessElvisChecker.kt | 2 +- .../type/FirDefinitelyNotNullableChecker.kt | 2 +- .../fir/backend/Fir2IrImplicitCastInserter.kt | 12 +++++----- .../generators/DelegatedMemberGenerator.kt | 2 +- .../jetbrains/kotlin/fir/types/TypeUtils.kt | 21 ++++++++++++++++++ .../fir/resolve/calls/ResolutionStages.kt | 2 +- .../fir/resolve/calls/tower/TowerLevels.kt | 2 +- .../fir/resolve/dfa/FirDataFlowAnalyzer.kt | 11 +++++----- .../kotlin/fir/resolve/dfa/LogicSystem.kt | 8 ++++--- .../kotlin/fir/resolve/dfa/contracts.kt | 3 ++- .../kotlin/fir/types/FirTypeUtils.kt | 22 ------------------- .../tests/smartCasts/castchecks/kt64645.kt | 18 +++++++++++++++ .../test/runners/DiagnosticTestGenerated.java | 6 +++++ 23 files changed, 100 insertions(+), 54 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/smartCasts/castchecks/kt64645.kt diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirTypeInfoProvider.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirTypeInfoProvider.kt index 49558f0d314..9c4134d1273 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirTypeInfoProvider.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirTypeInfoProvider.kt @@ -35,7 +35,7 @@ internal class KtFirTypeInfoProvider( 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 { val coneType = (type as KtFirType).coneType diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFE10TestdataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFE10TestdataTestGenerated.java index 6f9636c3267..2fe6afff1dd 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFE10TestdataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFE10TestdataTestGenerated.java @@ -35179,6 +35179,12 @@ public class DiagnosticCompilerTestFE10TestdataTestGenerated extends AbstractDia 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 @TestMetadata("smartCastOfNullableExpressionWithExpectedType.kt") public void testSmartCastOfNullableExpressionWithExpectedType() throws Exception { diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated.java index bf7384b7717..928ff841944 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated.java @@ -35179,6 +35179,12 @@ public class LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated 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 @TestMetadata("smartCastOfNullableExpressionWithExpectedType.kt") public void testSmartCastOfNullableExpressionWithExpectedType() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java index 7a2ef1e71e0..53c96a7f2d5 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java @@ -32905,6 +32905,12 @@ public class FirLightTreeOldFrontendDiagnosticsTestGenerated extends AbstractFir 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 @TestMetadata("smartCastOfNullableExpressionWithExpectedType.kt") public void testSmartCastOfNullableExpressionWithExpectedType() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java index 005db78df65..c64e7029fbb 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java @@ -33025,6 +33025,12 @@ public class FirPsiOldFrontendDiagnosticsTestGenerated extends AbstractFirPsiDia 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 @TestMetadata("smartCastOfNullableExpressionWithExpectedType.kt") public void testSmartCastOfNullableExpressionWithExpectedType() throws Exception { diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirReturnsImpliesAnalyzer.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirReturnsImpliesAnalyzer.kt index 0d56444667b..f0243447b49 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirReturnsImpliesAnalyzer.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirReturnsImpliesAnalyzer.kt @@ -151,7 +151,7 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() { private fun Operation.canBeTrueFor(session: FirSession, type: ConeKotlinType): Boolean = when (this) { Operation.EqTrue, Operation.EqFalse -> AbstractTypeChecker.isSubtypeOf(session.typeContext, session.builtinTypes.booleanType.type, type) - Operation.EqNull -> type.canBeNull + Operation.EqNull -> type.canBeNull(session) Operation.NotEqNull -> !type.isNullableNothing } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirCastDiagnosticsHelpers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirCastDiagnosticsHelpers.kt index c740e493b4f..9052891a079 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirCastDiagnosticsHelpers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirCastDiagnosticsHelpers.kt @@ -7,14 +7,11 @@ package org.jetbrains.kotlin.fir.analysis.checkers import org.jetbrains.kotlin.fir.FirSession 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.isLocal -import org.jetbrains.kotlin.fir.getContainingClassLookupTag import org.jetbrains.kotlin.fir.resolve.defaultType import org.jetbrains.kotlin.fir.resolve.getClassAndItsOuterClassesWhenLocal 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.symbols.impl.FirClassSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol @@ -58,8 +55,8 @@ fun checkCasting( return if (result) CastingType.Possible else CastingType.Impossible } - val lhsNullable = lhsLowerType.canBeNull - val rhsNullable = rhsLowerType.canBeNull + val lhsNullable = lhsLowerType.canBeNull(session) + val rhsNullable = rhsLowerType.canBeNull(session) if (lhsLowerType.isNothing) return CastingType.Possible if (lhsLowerType.isNullableNothing && !rhsNullable) { 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 if (isNonReifiedTypeParameter && !isUpcast) { // 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) { return true } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirNotNullAssertionChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirNotNullAssertionChecker.kt index 4d733120296..fcfd476b60d 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirNotNullAssertionChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirNotNullAssertionChecker.kt @@ -39,7 +39,7 @@ object FirNotNullAssertionChecker : FirCheckNotNullCallChecker() { 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) } } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirSpreadOfNullableChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirSpreadOfNullableChecker.kt index 7a25515d6f3..a3dfb070a77 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirSpreadOfNullableChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirSpreadOfNullableChecker.kt @@ -22,7 +22,7 @@ object FirSpreadOfNullableChecker : FirFunctionCallChecker() { override fun check(expression: FirFunctionCall, context: CheckerContext, reporter: DiagnosticReporter) { fun checkAndReport(argument: FirExpression, source: KtSourceElement?) { 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) } } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirUnnecessarySafeCallChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirUnnecessarySafeCallChecker.kt index b94bf7fa521..e1ef3791f91 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirUnnecessarySafeCallChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirUnnecessarySafeCallChecker.kt @@ -24,7 +24,7 @@ object FirUnnecessarySafeCallChecker : FirSafeCallExpressionChecker() { reporter.reportOn(expression.source, FirErrors.UNEXPECTED_SAFE_CALL, context) return } - if (!receiverType.canBeNull) { + if (!receiverType.canBeNull(context.session)) { if (context.languageVersionSettings.supportsFeature(LanguageFeature.EnableDfaWarningsInK2)) { reporter.reportOn(expression.source, FirErrors.UNNECESSARY_SAFE_CALL, receiverType, context) } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirUselessElvisChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirUselessElvisChecker.kt index 720391cb0bb..c64e3afb43c 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirUselessElvisChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirUselessElvisChecker.kt @@ -25,7 +25,7 @@ object FirUselessElvisChecker : FirElvisExpressionChecker() { val lhsType = expression.lhs.resolvedType if (lhsType is ConeErrorType) return - if (!lhsType.canBeNull) { + if (!lhsType.canBeNull(context.session)) { if (context.languageVersionSettings.supportsFeature(LanguageFeature.EnableDfaWarningsInK2)) { reporter.reportOn(expression.source, FirErrors.USELESS_ELVIS, lhsType, context) } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/type/FirDefinitelyNotNullableChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/type/FirDefinitelyNotNullableChecker.kt index c5f987227a7..90f8737ef7a 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/type/FirDefinitelyNotNullableChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/type/FirDefinitelyNotNullableChecker.kt @@ -19,7 +19,7 @@ object FirDefinitelyNotNullableChecker : FirTypeRefChecker() { 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) } diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrImplicitCastInserter.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrImplicitCastInserter.kt index 60b105990f8..a0f126a6636 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrImplicitCastInserter.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrImplicitCastInserter.kt @@ -6,15 +6,13 @@ package org.jetbrains.kotlin.fir.backend 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.expressions.* import org.jetbrains.kotlin.fir.expressions.impl.FirUnitExpression import org.jetbrains.kotlin.fir.references.toResolvedCallableSymbol import org.jetbrains.kotlin.fir.render 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.visitors.FirDefaultVisitor import org.jetbrains.kotlin.ir.IrBuiltIns @@ -235,7 +233,7 @@ class Fir2IrImplicitCastInserter( this } } - typeCanBeEnhancedOrFlexibleNullable(expandedValueType) && !expandedExpectedType.acceptsNullValues() -> { + typeCanBeEnhancedOrFlexibleNullable(expandedValueType, session) && !expandedExpectedType.acceptsNullValues() -> { insertImplicitNotNullCastIfNeeded(expression) } // TODO: coerceIntToAnotherIntegerType @@ -258,7 +256,7 @@ class Fir2IrImplicitCastInserter( } private fun ConeKotlinType.acceptsNullValues(): Boolean { - return canBeNull || hasEnhancedNullability + return canBeNull(session) || hasEnhancedNullability } 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 { type.hasEnhancedNullability -> true - type.hasFlexibleNullability && type.canBeNull -> true + type.hasFlexibleNullability && type.canBeNull(session) -> true else -> false } } diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/DelegatedMemberGenerator.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/DelegatedMemberGenerator.kt index 6752717e948..4f1add4aa4e 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/DelegatedMemberGenerator.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/DelegatedMemberGenerator.kt @@ -303,7 +303,7 @@ class DelegatedMemberGenerator(private val components: Fir2IrComponents) : Fir2I val substitutor = substitutorByMap(substitution, session) val substitutedType = substitutor.substituteOrSelf(originalFirDeclaration.returnTypeRef.coneType) - callTypeCanBeNullable = Fir2IrImplicitCastInserter.typeCanBeEnhancedOrFlexibleNullable(substitutedType) + callTypeCanBeNullable = Fir2IrImplicitCastInserter.typeCanBeEnhancedOrFlexibleNullable(substitutedType, session) substitutedType.toIrType(typeOrigin) } true -> { diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/TypeUtils.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/TypeUtils.kt index 453f354ed1f..979aabbb57b 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/TypeUtils.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/TypeUtils.kt @@ -780,3 +780,24 @@ fun ConeKotlinType.convertToNonRawVersion(): ConeKotlinType { 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 \ No newline at end of file diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ResolutionStages.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ResolutionStages.kt index fcb98768f67..28746f0abf7 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ResolutionStages.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ResolutionStages.kt @@ -197,7 +197,7 @@ object CheckDispatchReceiver : ResolutionStage() { if (smartcastedReceiver != null && !smartcastedReceiver.isStable && - (isCandidateFromUnstableSmartcast || (isReceiverNullable && !smartcastedReceiver.smartcastType.canBeNull)) + (isCandidateFromUnstableSmartcast || (isReceiverNullable && !smartcastedReceiver.smartcastType.coneType.canBeNull(callInfo.session))) ) { val dispatchReceiverType = (candidate.symbol as? FirCallableSymbol<*>)?.dispatchReceiverType?.let { context.session.typeApproximator.approximateToSuperType( diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/TowerLevels.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/TowerLevels.kt index 4aa4528963a..7b36f5c9fa5 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/TowerLevels.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/TowerLevels.kt @@ -118,7 +118,7 @@ class MemberScopeTowerLevel( // with two exceptions: // - 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. - if (existing == null || dispatchReceiverValue.type.isNullableNothing || receiverTypeWithoutSmartCast.canBeNull) { + if (existing == null || dispatchReceiverValue.type.isNullableNothing || receiverTypeWithoutSmartCast.canBeNull(session)) { map[candidateFromSmartCast.member] = MemberFromSmartcastScope(candidateFromSmartCast, DispatchReceiverToUse.SmartcastWithoutUnwrapping) } else { existing.dispatchReceiverToUse = DispatchReceiverToUse.SmartcastIfUnwrappedInvisible diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt index 53b3fb50230..afd22feaebc 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt @@ -368,7 +368,7 @@ abstract class FirDataFlowAnalyzer( if (operandVariable.isReal()) { flow.addImplication((expressionVariable eq isType) implies (operandVariable typeEq type)) } - if (!type.canBeNull) { + if (!type.canBeNull(components.session)) { // x is (T & Any) => x != null flow.addImplication((expressionVariable eq isType) implies (operandVariable notEq null)) } else if (type.isMarkedNullable) { @@ -383,7 +383,7 @@ abstract class FirDataFlowAnalyzer( if (operandVariable.isReal()) { flow.addTypeStatement(operandVariable typeEq type) } - if (!type.canBeNull) { + if (!type.canBeNull(components.session)) { flow.commitOperationStatement(operandVariable notEq null) } else { val expressionVariable = variableStorage.createSynthetic(typeOperatorCall) @@ -998,9 +998,10 @@ abstract class FirDataFlowAnalyzer( for (conditionalEffect in conditionalEffects) { val effect = conditionalEffect.effect as? ConeReturnsEffectDeclaration ?: continue val operation = effect.value.toOperation() - val statements = logicSystem.approveContractStatement(conditionalEffect.condition, argumentVariables, substitutor) { - logicSystem.approveOperationStatement(flow, it, removeApprovedOrImpossible = operation == null) - } ?: continue // TODO: do what if the result is known to be false? + val statements = + logicSystem.approveContractStatement(conditionalEffect.condition, argumentVariables, substitutor) { + logicSystem.approveOperationStatement(flow, it, removeApprovedOrImpossible = operation == null) + } ?: continue // TODO: do what if the result is known to be false? if (operation == null) { flow.addAllStatements(statements) } else { diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt index c0ecd0363c2..7de0fa57e29 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt @@ -6,14 +6,16 @@ package org.jetbrains.kotlin.fir.resolve.dfa import kotlinx.collections.immutable.* +import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.types.AbstractTypeChecker import java.util.* import kotlin.math.max abstract class LogicSystem(private val context: ConeInferenceContext) { - private val nullableNothingType = context.session.builtinTypes.nullableNothingType.type - private val anyType = context.session.builtinTypes.anyType.type + val session: FirSession get() = context.session + private val nullableNothingType = session.builtinTypes.nullableNothingType.type + private val anyType = session.builtinTypes.anyType.type abstract val variableStorage: VariableStorageImpl @@ -340,7 +342,7 @@ abstract class LogicSystem(private val context: ConeInferenceContext) { val result = when { unified.isNullableAny -> return null unified.isAcceptableForSmartcast() -> unified - unified.canBeNull -> return null + unified.canBeNull(context.session) -> return null else -> context.anyType() } return PersistentTypeStatement(variable, persistentSetOf(result)) diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/contracts.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/contracts.kt index 670c8f047cf..182a427c901 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/contracts.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/contracts.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.fir.resolve.dfa 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.resolve.substitution.ConeSubstitutor import org.jetbrains.kotlin.fir.types.canBeNull @@ -45,7 +46,7 @@ fun LogicSystem.approveContractStatement( substitutedType.isNullableNothing -> it.processEqNull(isType) else -> { // 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) else mapOf() diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/types/FirTypeUtils.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/types/FirTypeUtils.kt index b8579930369..cf80bee3590 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/types/FirTypeUtils.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/types/FirTypeUtils.kt @@ -155,25 +155,3 @@ fun FirTypeProjection.toConeTypeProjection(): ConeTypeProjection = when (this) { } 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 diff --git a/compiler/testData/diagnostics/tests/smartCasts/castchecks/kt64645.kt b/compiler/testData/diagnostics/tests/smartCasts/castchecks/kt64645.kt new file mode 100644 index 00000000000..9370fd4d87f --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/castchecks/kt64645.kt @@ -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 foo(x: T) { + if (x != null) { + x.value + } +} + +fun foo2(x: NullableBox) { + if (x != null) { + x.value + } +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java index 0d559b40533..feda07cc742 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java @@ -35179,6 +35179,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { 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 @TestMetadata("smartCastOfNullableExpressionWithExpectedType.kt") public void testSmartCastOfNullableExpressionWithExpectedType() throws Exception {