From 84334b087c82513f9d14bb90deb38c5249dcc08f Mon Sep 17 00:00:00 2001 From: Tianyu Geng Date: Sat, 8 May 2021 20:51:02 -0700 Subject: [PATCH] FIR checkers: report SMARTCAST_IMPOSSIBLE --- .../resolve/smartcasts/nullability.dot | 24 ++-- .../resolve/smartcasts/nullability.fir.txt | 16 +-- .../resolve/smartcasts/nullability.kt | 16 +-- .../diagnostics/FirDiagnosticsList.kt | 6 + .../fir/analysis/diagnostics/FirErrors.kt | 1 + .../FirFunctionReturnTypeMismatchChecker.kt | 24 +++- .../diagnostics/FirDefaultErrorMessages.kt | 8 ++ .../coneDiagnosticToFirDiagnostic.kt | 29 ++++- .../fir/backend/Fir2IrImplicitCastInserter.kt | 1 + .../kotlin/fir/resolve/ScopeUtils.kt | 21 ++++ .../kotlin/fir/resolve/calls/Arguments.kt | 31 ++--- .../kotlin/fir/resolve/calls/FirReceivers.kt | 8 +- .../fir/resolve/calls/ResolutionDiagnostic.kt | 17 +-- .../fir/resolve/calls/ResolutionStages.kt | 15 +++ .../scopes/FirUnstableSmartcastTypeScope.kt | 106 ++++++++++++++++++ .../calls/tower/CandidateApplicability.kt | 2 +- .../calls/model/KotlinCallDiagnostics.kt | 4 +- .../resolve/calls/tower/ImplicitScopeTower.kt | 2 +- ...rtcastWhenOpenGetterWithOverloading.fir.kt | 15 --- ...eSmartcastWhenOpenGetterWithOverloading.kt | 1 + ...leSmartcastWithOverloadedExtensions.fir.kt | 14 --- ...stableSmartcastWithOverloadedExtensions.kt | 1 + .../tests/smartCasts/inference/kt39010.fir.kt | 11 -- .../tests/smartCasts/inference/kt39010.kt | 1 + .../smartCasts/inference/kt39010_2.fir.kt | 16 --- .../tests/smartCasts/inference/kt39010_2.kt | 1 + .../unstableSmartCast.fir.kt | 37 ------ .../intersectionScope/unstableSmartCast.kt | 1 + .../localDelegatedPropertyAfter.fir.kt | 4 +- .../localDelegatedPropertyBefore.fir.kt | 4 +- .../tests/smartCasts/openInSealed.fir.kt | 2 +- .../tests/smartCasts/propertyToNotNull.fir.kt | 2 +- .../smartCasts/publicVals/customGetter.fir.kt | 13 --- .../smartCasts/publicVals/customGetter.kt | 1 + .../tests/smartCasts/publicVals/open.fir.kt | 11 -- .../tests/smartCasts/publicVals/open.kt | 1 + .../smartCasts/publicVals/otherModule.fir.kt | 35 ------ .../smartCasts/publicVals/otherModule.kt | 1 + .../tests/smartCasts/publicVals/var.fir.kt | 15 --- .../tests/smartCasts/publicVals/var.kt | 1 + .../tests/smartCasts/unstableToStable.fir.kt | 18 --- .../tests/smartCasts/unstableToStable.kt | 1 + .../smartCasts/unstableToStableTypes.fir.kt | 22 ---- .../tests/smartCasts/unstableToStableTypes.kt | 1 + .../contracts/smartcasts/receiver.fir.kt | 2 +- .../fir/handlers/FirDiagnosticsHandler.kt | 4 +- .../diagnostics/notLinked/dfa/pos/53.fir.kt | 4 +- .../diagnostics/KtFirDataClassConverters.kt | 9 ++ .../api/fir/diagnostics/KtFirDiagnostics.kt | 7 ++ .../fir/diagnostics/KtFirDiagnosticsImpl.kt | 10 ++ idea/testData/checker/infos/SmartCasts.fir.kt | 8 +- 51 files changed, 308 insertions(+), 297 deletions(-) create mode 100644 compiler/fir/tree/src/org/jetbrains/kotlin/fir/scopes/FirUnstableSmartcastTypeScope.kt delete mode 100644 compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/unstableSmartcastWhenOpenGetterWithOverloading.fir.kt delete mode 100644 compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/unstableSmartcastWithOverloadedExtensions.fir.kt delete mode 100644 compiler/testData/diagnostics/tests/smartCasts/inference/kt39010.fir.kt delete mode 100644 compiler/testData/diagnostics/tests/smartCasts/inference/kt39010_2.fir.kt delete mode 100644 compiler/testData/diagnostics/tests/smartCasts/intersectionScope/unstableSmartCast.fir.kt delete mode 100644 compiler/testData/diagnostics/tests/smartCasts/publicVals/customGetter.fir.kt delete mode 100644 compiler/testData/diagnostics/tests/smartCasts/publicVals/open.fir.kt delete mode 100644 compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModule.fir.kt delete mode 100644 compiler/testData/diagnostics/tests/smartCasts/publicVals/var.fir.kt delete mode 100644 compiler/testData/diagnostics/tests/smartCasts/unstableToStable.fir.kt delete mode 100644 compiler/testData/diagnostics/tests/smartCasts/unstableToStableTypes.fir.kt diff --git a/compiler/fir/analysis-tests/testData/resolve/smartcasts/nullability.dot b/compiler/fir/analysis-tests/testData/resolve/smartcasts/nullability.dot index 6fbd681984e..9636ca4f760 100644 --- a/compiler/fir/analysis-tests/testData/resolve/smartcasts/nullability.dot +++ b/compiler/fir/analysis-tests/testData/resolve/smartcasts/nullability.dot @@ -492,11 +492,11 @@ digraph nullability_kt { 176 [label="Access variable R|/Q.data|"]; 177 [label="Access variable R|/q|"]; 178 [label="Access variable R|/Q.data|"]; - 179 [label="Access variable #"]; + 179 [label="Access variable #"]; 180 [label="Access variable R|/q|"]; 181 [label="Access variable R|/Q.data|"]; - 182 [label="Access variable #"]; - 183 [label="Function call: R|/q|.R|/Q.data|.#.R|kotlin/Int.inc|()"]; + 182 [label="Access variable #"]; + 183 [label="Function call: R|/q|.R|/Q.data|.#.R|kotlin/Int.inc|()"]; 184 [label="Exit block"]; } 185 [label="Exit when branch result"]; @@ -566,11 +566,11 @@ digraph nullability_kt { 208 [label="Access variable R|/Q.data|"]; 209 [label="Access variable R|/q|"]; 210 [label="Access variable R|/Q.data|"]; - 211 [label="Access variable #"]; + 211 [label="Access variable #"]; 212 [label="Access variable R|/q|"]; 213 [label="Access variable R|/Q.data|"]; - 214 [label="Access variable #"]; - 215 [label="Function call: R|/q|.R|/Q.data|.#.R|kotlin/Int.inc|()"]; + 214 [label="Access variable #"]; + 215 [label="Function call: R|/q|.R|/Q.data|.#.R|kotlin/Int.inc|()"]; 216 [label="Exit block"]; } 217 [label="Exit function test_6" style="filled" fillcolor=red]; @@ -1280,11 +1280,11 @@ digraph nullability_kt { 490 [label="Access variable R|/QImplWithCustomGetter.data|"]; 491 [label="Access variable R|/q|"]; 492 [label="Access variable R|/QImplWithCustomGetter.data|"]; - 493 [label="Access variable #"]; + 493 [label="Access variable #"]; 494 [label="Access variable R|/q|"]; 495 [label="Access variable R|/QImplWithCustomGetter.data|"]; - 496 [label="Access variable #"]; - 497 [label="Function call: R|/q|.R|/QImplWithCustomGetter.data|.#.R|kotlin/Int.inc|()"]; + 496 [label="Access variable #"]; + 497 [label="Function call: R|/q|.R|/QImplWithCustomGetter.data|.#.R|kotlin/Int.inc|()"]; 498 [label="Exit block"]; } 499 [label="Exit when branch result"]; @@ -1363,11 +1363,11 @@ digraph nullability_kt { 524 [label="Access variable R|/QImplMutable.data|"]; 525 [label="Access variable R|/q|"]; 526 [label="Access variable R|/QImplMutable.data|"]; - 527 [label="Access variable #"]; + 527 [label="Access variable #"]; 528 [label="Access variable R|/q|"]; 529 [label="Access variable R|/QImplMutable.data|"]; - 530 [label="Access variable #"]; - 531 [label="Function call: R|/q|.R|/QImplMutable.data|.#.R|kotlin/Int.inc|()"]; + 530 [label="Access variable #"]; + 531 [label="Function call: R|/q|.R|/QImplMutable.data|.#.R|kotlin/Int.inc|()"]; 532 [label="Exit block"]; } 533 [label="Exit when branch result"]; diff --git a/compiler/fir/analysis-tests/testData/resolve/smartcasts/nullability.fir.txt b/compiler/fir/analysis-tests/testData/resolve/smartcasts/nullability.fir.txt index cfb6f6d0941..c9de93e9311 100644 --- a/compiler/fir/analysis-tests/testData/resolve/smartcasts/nullability.fir.txt +++ b/compiler/fir/analysis-tests/testData/resolve/smartcasts/nullability.fir.txt @@ -102,8 +102,8 @@ FILE: nullability.kt when () { !=(R|/q|?.{ $subj$.R|/Q.data| }?.{ $subj$.R|/MyData.s| }?.{ $subj$.R|kotlin/Int.inc|() }, Null(null)) -> { R|/q|.R|/Q.data| - R|/q|.R|/Q.data|.# - R|/q|.R|/Q.data|.#.R|kotlin/Int.inc|() + R|/q|.R|/Q.data|.# + R|/q|.R|/Q.data|.#.R|kotlin/Int.inc|() } } @@ -111,8 +111,8 @@ FILE: nullability.kt public final fun test_6(q: R|Q?|): R|kotlin/Unit| { R|/q|?.{ $subj$.R|/Q.data| }?.{ $subj$.R|/MyData.s| }?.{ $subj$.R|kotlin/Int.inc|() } ?: ^test_6 Unit R|/q|.R|/Q.data| - R|/q|.R|/Q.data|.# - R|/q|.R|/Q.data|.#.R|kotlin/Int.inc|() + R|/q|.R|/Q.data|.# + R|/q|.R|/Q.data|.#.R|kotlin/Int.inc|() } public final fun test_7(q: R|Q?|): R|kotlin/Unit| { when () { @@ -216,8 +216,8 @@ FILE: nullability.kt when () { !=(R|/q|?.{ $subj$.R|/QImplWithCustomGetter.data| }?.{ $subj$.R|/MyData.s| }?.{ $subj$.R|kotlin/Int.inc|() }, Null(null)) -> { R|/q|.R|/QImplWithCustomGetter.data| - R|/q|.R|/QImplWithCustomGetter.data|.# - R|/q|.R|/QImplWithCustomGetter.data|.#.R|kotlin/Int.inc|() + R|/q|.R|/QImplWithCustomGetter.data|.# + R|/q|.R|/QImplWithCustomGetter.data|.#.R|kotlin/Int.inc|() } } @@ -226,8 +226,8 @@ FILE: nullability.kt when () { !=(R|/q|?.{ $subj$.R|/QImplMutable.data| }?.{ $subj$.R|/MyData.s| }?.{ $subj$.R|kotlin/Int.inc|() }, Null(null)) -> { R|/q|.R|/QImplMutable.data| - R|/q|.R|/QImplMutable.data|.# - R|/q|.R|/QImplMutable.data|.#.R|kotlin/Int.inc|() + R|/q|.R|/QImplMutable.data|.# + R|/q|.R|/QImplMutable.data|.#.R|kotlin/Int.inc|() } } diff --git a/compiler/fir/analysis-tests/testData/resolve/smartcasts/nullability.kt b/compiler/fir/analysis-tests/testData/resolve/smartcasts/nullability.kt index 3f5877d797c..7347a02d70f 100644 --- a/compiler/fir/analysis-tests/testData/resolve/smartcasts/nullability.kt +++ b/compiler/fir/analysis-tests/testData/resolve/smartcasts/nullability.kt @@ -67,8 +67,8 @@ fun test_5(q: Q?) { // `q.data` is a property that has an open getter, so we can NOT smartcast it to non-nullable MyData. if (q?.data?.s?.inc() != null) { q.data // good - q.data.s // should be bad - q.data.s.inc() // should be bad + q.data.s // should be bad + q.data.s.inc() // should be bad } } @@ -76,8 +76,8 @@ fun test_6(q: Q?) { // `q.data` is a property that has an open getter, so we can NOT smartcast it to non-nullable MyData. q?.data?.s?.inc() ?: return q.data // good - q.data.s // should be bad - q.data.s.inc() // should be bad + q.data.s // should be bad + q.data.s.inc() // should be bad } fun test_7(q: Q?) { @@ -162,8 +162,8 @@ fun test_12(q: QImplWithCustomGetter?) { // `q.data` is a property that has an open getter, so we can NOT smartcast it to non-nullable MyData. if (q?.data?.s?.inc() != null) { q.data // good - q.data.s // should be bad - q.data.s.inc() // should be bad + q.data.s // should be bad + q.data.s.inc() // should be bad } } @@ -171,7 +171,7 @@ fun test_13(q: QImplMutable?) { // `q.data` is a property that is mutable, so we can NOT smartcast it to non-nullable MyData. if (q?.data?.s?.inc() != null) { q.data // good - q.data.s // should be bad - q.data.s.inc() // should be bad + q.data.s // should be bad + q.data.s.inc() // should be bad } } diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt index 2c97bb44d62..92d0a254536 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt @@ -465,6 +465,12 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") { parameter("variance") parameter("containingType") } + + val SMARTCAST_IMPOSSIBLE by error { + parameter("desiredType") + parameter("subject") + parameter("description") + } } val REFLECTION by object : DiagnosticGroup("Reflection") { diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index 664138a9b07..aa4527c7633 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -303,6 +303,7 @@ object FirErrors { val INCOMPATIBLE_TYPES_WARNING by warning2() val TYPE_VARIANCE_CONFLICT by error4(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT) val TYPE_VARIANCE_CONFLICT_IN_EXPANDED_TYPE by error4(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT) + val SMARTCAST_IMPOSSIBLE by error3() // Reflection val EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED by error1>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirFunctionReturnTypeMismatchChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirFunctionReturnTypeMismatchChecker.kt index 503eeee20d5..735a4d02f65 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirFunctionReturnTypeMismatchChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirFunctionReturnTypeMismatchChecker.kt @@ -10,13 +10,16 @@ import org.jetbrains.kotlin.fir.analysis.checkers.isSubtypeForTypeMismatch import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NULL_FOR_NONNULL_TYPE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.RETURN_TYPE_MISMATCH +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SMARTCAST_IMPOSSIBLE import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction +import org.jetbrains.kotlin.fir.expressions.FirExpressionWithSmartcast import org.jetbrains.kotlin.fir.expressions.FirReturnExpression import org.jetbrains.kotlin.fir.expressions.FirWhenExpression import org.jetbrains.kotlin.fir.expressions.isExhaustive import org.jetbrains.kotlin.fir.typeContext import org.jetbrains.kotlin.fir.types.* +import org.jetbrains.kotlin.types.SmartcastStability object FirFunctionReturnTypeMismatchChecker : FirReturnExpressionChecker() { override fun check(expression: FirReturnExpression, context: CheckerContext, reporter: DiagnosticReporter) { @@ -36,10 +39,23 @@ object FirFunctionReturnTypeMismatchChecker : FirReturnExpressionChecker() { if (resultExpression.isNullLiteral && functionReturnType.nullability == ConeNullability.NOT_NULL) { reporter.reportOn(resultExpression.source, NULL_FOR_NONNULL_TYPE, context) } else { - reporter.report( - RETURN_TYPE_MISMATCH.on(returnExpressionSource, functionReturnType, returnExpressionType, targetElement), - context - ) + if (resultExpression is FirExpressionWithSmartcast && resultExpression.smartcastStability != SmartcastStability.STABLE_VALUE && + isSubtypeForTypeMismatch(typeContext, subtype = resultExpression.smartcastType.coneType, supertype = functionReturnType) + ) { + reporter.reportOn( + returnExpressionSource, + SMARTCAST_IMPOSSIBLE, + functionReturnType, + resultExpression, + resultExpression.smartcastStability.description, + context + ) + } else { + reporter.report( + RETURN_TYPE_MISMATCH.on(returnExpressionSource, functionReturnType, returnExpressionType, targetElement), + context + ) + } } } } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt index 07c3d7ffbf3..929d70221f1 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt @@ -273,6 +273,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SEALED_SUPERTYPE_ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SECONDARY_CONSTRUCTOR_WITH_BODY_INSIDE_INLINE_CLASS import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SETTER_VISIBILITY_INCONSISTENT_WITH_PROPERTY_VISIBILITY import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SINGLETON_IN_SUPERTYPE +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SMARTCAST_IMPOSSIBLE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SUPERCLASS_NOT_ACCESSIBLE_FROM_INTERFACE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SUPERTYPES_FOR_ANNOTATION_CLASS import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SUPERTYPE_APPEARS_TWICE @@ -634,6 +635,13 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension { map.put(UPPER_BOUND_IS_EXTENSION_FUNCTION_TYPE, "Extension function type can not be used as an upper bound") map.put(INCOMPATIBLE_TYPES, "Incompatible types: {0} and {1}", RENDER_TYPE, RENDER_TYPE) map.put(INCOMPATIBLE_TYPES_WARNING, "Potentially incompatible types: {0} and {1}", RENDER_TYPE, RENDER_TYPE) + map.put( + SMARTCAST_IMPOSSIBLE, + "Smart cast to ''{0}'' is impossible, because ''{1}'' is a {2}", + RENDER_TYPE, + FIR, + TO_STRING + ) map.put( TYPE_VARIANCE_CONFLICT, diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt index 93c6faa5af0..04441913800 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt @@ -46,6 +46,15 @@ private fun ConeDiagnostic.toFirDiagnostic( val candidate = candidates.first { it.currentApplicability == CandidateApplicability.UNSAFE_CALL } val unsafeCall = candidate.diagnostics.firstIsInstance() mapUnsafeCallError(candidate, unsafeCall, source, qualifiedAccessSource) + } else if (this.applicability == CandidateApplicability.UNSTABLE_SMARTCAST) { + val unstableSmartcast = + this.candidates.first { it.currentApplicability == CandidateApplicability.UNSTABLE_SMARTCAST }.diagnostics.firstIsInstance() + FirErrors.SMARTCAST_IMPOSSIBLE.on( + unstableSmartcast.argument.source, + unstableSmartcast.targetType, + unstableSmartcast.argument, + unstableSmartcast.argument.smartcastStability.description + ) } else { FirErrors.NONE_APPLICABLE.on(source, this.candidates.map { it.symbol }) } @@ -134,8 +143,8 @@ private fun mapInapplicableCandidateError( source: FirSourceElement, qualifiedAccessSource: FirSourceElement?, ): List> { - // TODO: Need to distinguish SMARTCAST_IMPOSSIBLE - return diagnostic.candidate.diagnostics.filter { it.applicability == diagnostic.applicability }.mapNotNull { rootCause -> + val genericDiagnostic = FirErrors.INAPPLICABLE_CANDIDATE.on(source, diagnostic.candidate.symbol) + val diagnostics = diagnostic.candidate.diagnostics.filter { it.applicability == diagnostic.applicability }.mapNotNull { rootCause -> when (rootCause) { is VarargArgumentOutsideParentheses -> FirErrors.VARARG_OUTSIDE_PARENTHESES.on( rootCause.argument.source ?: qualifiedAccessSource @@ -166,9 +175,21 @@ private fun mapInapplicableCandidateError( is InfixCallOfNonInfixFunction -> FirErrors.INFIX_MODIFIER_REQUIRED.on(source, rootCause.function) is OperatorCallOfNonOperatorFunction -> FirErrors.OPERATOR_MODIFIER_REQUIRED.on(source, rootCause.function, rootCause.function.fir.name.asString()) - else -> null + is UnstableSmartCast -> FirErrors.SMARTCAST_IMPOSSIBLE.on( + rootCause.argument.source, + rootCause.targetType, + rootCause.argument, + rootCause.argument.smartcastStability.description + ) + else -> genericDiagnostic } - }.ifEmpty { listOf(FirErrors.INAPPLICABLE_CANDIDATE.on(source, diagnostic.candidate.symbol)) } + }.distinct() + return if (diagnostics.size > 1) { + // If there are more specific diagnostics, filter out the generic diagnostic. + diagnostics.filter { it != genericDiagnostic } + } else { + diagnostics + } } @OptIn(ExperimentalStdlibApi::class) 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 dc07ee5d493..abaea391d69 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 @@ -31,6 +31,7 @@ import org.jetbrains.kotlin.ir.util.classId import org.jetbrains.kotlin.ir.util.coerceToUnitIfNeeded import org.jetbrains.kotlin.ir.util.parentAsClass import org.jetbrains.kotlin.types.AbstractTypeChecker +import org.jetbrains.kotlin.types.SmartcastStability class Fir2IrImplicitCastInserter( private val components: Fir2IrComponents, diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ScopeUtils.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ScopeUtils.kt index 560d369abb6..3476998ac1e 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ScopeUtils.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ScopeUtils.kt @@ -10,9 +10,11 @@ import org.jetbrains.kotlin.fir.declarations.FirAnonymousObject import org.jetbrains.kotlin.fir.declarations.FirClass import org.jetbrains.kotlin.fir.declarations.FirRegularClass import org.jetbrains.kotlin.fir.declarations.FirResolvePhase +import org.jetbrains.kotlin.fir.expressions.FirExpressionWithSmartcast import org.jetbrains.kotlin.fir.resolve.substitution.substitutorByMap import org.jetbrains.kotlin.fir.resolve.transformers.ensureResolved import org.jetbrains.kotlin.fir.scopes.FakeOverrideTypeCalculator +import org.jetbrains.kotlin.fir.scopes.FirUnstableSmartcastTypeScope import org.jetbrains.kotlin.fir.scopes.FirTypeScope import org.jetbrains.kotlin.fir.scopes.impl.FirScopeWithFakeOverrideTypeCalculator import org.jetbrains.kotlin.fir.scopes.impl.FirStandardOverrideChecker @@ -25,6 +27,25 @@ import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl import org.jetbrains.kotlin.fir.types.impl.ConeTypeParameterTypeImpl import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.types.SmartcastStability + +fun FirExpressionWithSmartcast.smartcastScope( + useSiteSession: FirSession, + scopeSession: ScopeSession +): FirTypeScope? { + val smartcastType = smartcastType.coneType + val smartcastScope = smartcastType.scope(useSiteSession, scopeSession, FakeOverrideTypeCalculator.DoNothing) + if (smartcastStability == SmartcastStability.STABLE_VALUE) { + return smartcastScope + } + val originalScope = originalType.coneType.scope(useSiteSession, scopeSession, FakeOverrideTypeCalculator.DoNothing) + ?: return smartcastScope + + if (smartcastScope == null) { + return originalScope + } + return FirUnstableSmartcastTypeScope(smartcastType, smartcastScope, originalScope) +} fun ConeKotlinType.scope( useSiteSession: FirSession, diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/Arguments.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/Arguments.kt index 55a636e0315..2bab2ce415b 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/Arguments.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/Arguments.kt @@ -30,9 +30,9 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.StandardClassIds import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilder import org.jetbrains.kotlin.resolve.calls.inference.addSubtypeConstraintIfCompatible -import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintPosition import org.jetbrains.kotlin.resolve.calls.inference.model.SimpleConstraintSystemConstraintPosition import org.jetbrains.kotlin.types.AbstractTypeChecker +import org.jetbrains.kotlin.types.SmartcastStability import org.jetbrains.kotlin.types.model.CaptureStatus import org.jetbrains.kotlin.types.model.TypeSystemCommonSuperTypesContext import org.jetbrains.kotlin.utils.addToStdlib.runIf @@ -358,17 +358,7 @@ private fun checkApplicabilityForArgumentType( ) { if (expectedType == null) return - fun unstableSmartCastOrSubtypeError( - unstableType: ConeKotlinType?, - actualExpectedType: ConeKotlinType, - position: ConstraintPosition - ): ResolutionDiagnostic { - if (unstableType != null) { - if (csBuilder.addSubtypeConstraintIfCompatible(unstableType, actualExpectedType, position)) { - return UnstableSmartCast.ResolutionError(argument, unstableType) - } - } - + fun subtypeError(actualExpectedType: ConeKotlinType): ResolutionDiagnostic { if (argument.isNullLiteral && actualExpectedType.nullability == ConeNullability.NOT_NULL) { return NullForNotNullType(argument) } @@ -406,14 +396,17 @@ private fun checkApplicabilityForArgumentType( } if (!csBuilder.addSubtypeConstraintIfCompatible(argumentType, expectedType, position)) { + val smartcastExpression = argument as? FirExpressionWithSmartcast + if (smartcastExpression != null && smartcastExpression.smartcastStability != SmartcastStability.STABLE_VALUE) { + val unstableType = smartcastExpression.smartcastType.coneType + if (csBuilder.addSubtypeConstraintIfCompatible(unstableType, expectedType, position)) { + sink.reportDiagnostic(UnstableSmartCast(smartcastExpression, expectedType)) + return + } + } + if (!isReceiver) { - sink.reportDiagnosticIfNotNull( - unstableSmartCastOrSubtypeError( - unstableType = null, // TODO: handle unstable smartcasts - expectedType, - position - ) - ) + sink.reportDiagnosticIfNotNull(subtypeError(expectedType)) return } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/FirReceivers.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/FirReceivers.kt index 295a7d69eeb..ac21f72d6ec 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/FirReceivers.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/FirReceivers.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.fir.resolve.calls import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.diagnostics.ConeIntermediateDiagnostic import org.jetbrains.kotlin.fir.expressions.FirExpression +import org.jetbrains.kotlin.fir.expressions.FirExpressionWithSmartcast import org.jetbrains.kotlin.fir.expressions.FirThisReceiverExpression import org.jetbrains.kotlin.fir.expressions.builder.buildExpressionWithSmartcast import org.jetbrains.kotlin.fir.expressions.builder.buildThisReceiverExpression @@ -16,6 +17,7 @@ import org.jetbrains.kotlin.fir.renderWithType import org.jetbrains.kotlin.fir.resolve.ScopeSession import org.jetbrains.kotlin.fir.resolve.constructType import org.jetbrains.kotlin.fir.resolve.scope +import org.jetbrains.kotlin.fir.resolve.smartcastScope import org.jetbrains.kotlin.fir.resolvedTypeFromPrototype import org.jetbrains.kotlin.fir.scopes.FakeOverrideTypeCalculator import org.jetbrains.kotlin.fir.scopes.FirTypeScope @@ -57,7 +59,11 @@ abstract class AbstractExplicitReceiverValue : AbstractExplic class ExpressionReceiverValue( override val explicitReceiver: FirExpression -) : AbstractExplicitReceiverValue(), ReceiverValue +) : AbstractExplicitReceiverValue(), ReceiverValue { + override fun scope(useSiteSession: FirSession, scopeSession: ScopeSession): FirTypeScope? = + (receiverExpression as? FirExpressionWithSmartcast)?.smartcastScope(useSiteSession, scopeSession) + ?: type.scope(useSiteSession, scopeSession, FakeOverrideTypeCalculator.DoNothing) +} sealed class ImplicitReceiverValue>( val boundSymbol: S, diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ResolutionDiagnostic.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ResolutionDiagnostic.kt index 301e342c9d8..3a7f9658e10 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ResolutionDiagnostic.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ResolutionDiagnostic.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.fir.resolve.calls import org.jetbrains.kotlin.fir.declarations.FirFunction import org.jetbrains.kotlin.fir.declarations.FirValueParameter import org.jetbrains.kotlin.fir.expressions.FirExpression +import org.jetbrains.kotlin.fir.expressions.FirExpressionWithSmartcast import org.jetbrains.kotlin.fir.expressions.FirNamedArgumentExpression import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol import org.jetbrains.kotlin.fir.types.ConeKotlinType @@ -74,21 +75,7 @@ object LowerPriorityToPreserveCompatibilityDiagnostic : ResolutionDiagnostic(RES object CandidateChosenUsingOverloadResolutionByLambdaAnnotation : ResolutionDiagnostic(RESOLVED) -sealed class UnstableSmartCast( - val argument: FirExpression, - val targetType: ConeKotlinType, - applicability: CandidateApplicability -) : ResolutionDiagnostic(applicability) { - class ResolutionError( - argument: FirExpression, - targetType: ConeKotlinType, - ) : UnstableSmartCast(argument, targetType, MAY_THROW_RUNTIME_ERROR) - - class DiagnosticError( - argument: FirExpression, - targetType: ConeKotlinType, - ) : UnstableSmartCast(argument, targetType, RESOLVED_WITH_ERROR) -} +class UnstableSmartCast(val argument: FirExpressionWithSmartcast, val targetType: ConeKotlinType) : ResolutionDiagnostic(UNSTABLE_SMARTCAST) class ArgumentTypeMismatch( val expectedType: ConeKotlinType, 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 a462399fe23..ca015cddf83 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 @@ -26,6 +26,8 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind.* import org.jetbrains.kotlin.types.AbstractNullabilityChecker +import org.jetbrains.kotlin.types.AbstractTypeChecker +import org.jetbrains.kotlin.types.SmartcastStability abstract class ResolutionStage { abstract suspend fun check(candidate: Candidate, callInfo: CallInfo, sink: CheckerSink, context: ResolutionContext) @@ -105,6 +107,19 @@ object CheckDispatchReceiver : ResolutionStage() { } } + if (explicitReceiverExpression is FirExpressionWithSmartcast && explicitReceiverExpression.smartcastStability != SmartcastStability.STABLE_VALUE) { + val expectedDispatchReceiverType = (candidate.symbol.fir as? FirCallableMemberDeclaration)?.dispatchReceiverType + if (expectedDispatchReceiverType != null && + !AbstractTypeChecker.isSubtypeOf( + context.session.typeContext, + explicitReceiverExpression.originalType.coneType, + expectedDispatchReceiverType + ) + ) { + sink.yieldDiagnostic(UnstableSmartCast(explicitReceiverExpression, expectedDispatchReceiverType)) + } + } + val dispatchReceiverValueType = candidate.dispatchReceiverValue?.type ?: return if (!AbstractNullabilityChecker.isSubtypeOfAny(context.session.typeContext, dispatchReceiverValueType)) { diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/scopes/FirUnstableSmartcastTypeScope.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/scopes/FirUnstableSmartcastTypeScope.kt new file mode 100644 index 00000000000..2cefa9de96a --- /dev/null +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/scopes/FirUnstableSmartcastTypeScope.kt @@ -0,0 +1,106 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir.scopes + +import org.jetbrains.kotlin.fir.declarations.FirCallableMemberDeclaration +import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor +import org.jetbrains.kotlin.fir.symbols.impl.* +import org.jetbrains.kotlin.fir.types.ConeKotlinType +import org.jetbrains.kotlin.name.Name + +/** + * Special type scope for unstable smartcast. The purpose of this scope is only to report "SMARTCAST_IMPOSSIBLE" diagnostics. + * + * This scope will serve all candidates available in the original scope. In addition, it also serve all additional members that are + * available from the smartcast type. This way, these additional members can be resolved. Later in + * [org.jetbrains.kotlin.fir.resolve.calls.CheckDispatchReceiver], these additional members are rejected with "UnstableSmartcast" + * diagnostic, which surfaces as "SMARTCAST_IMPOSSIBLE" diagnostic. + */ +class FirUnstableSmartcastTypeScope( + private val smartcastType: ConeKotlinType, + private val smartcastScope: FirTypeScope, + private val originalScope: FirTypeScope +) : FirTypeScope(), FirContainingNamesAwareScope { + private val scopes = listOf(smartcastScope, originalScope) + override fun processClassifiersByNameWithSubstitution( + name: Name, + processor: (FirClassifierSymbol<*>, ConeSubstitutor) -> Unit + ) { + for (scope in scopes) { + scope.processClassifiersByNameWithSubstitution(name, processor) + } + } + + private inline fun processComposite( + process: FirTypeScope.(Name, (T) -> Unit) -> Unit, + name: Name, + noinline processor: (T) -> Unit + ) { + val unique = mutableSetOf() + for (scope in scopes) { + scope.process(name) { + if (unique.add(it)) { + processor(it) + } + } + } + } + + override fun processFunctionsByName(name: Name, processor: (FirNamedFunctionSymbol) -> Unit) { + return processComposite(FirScope::processFunctionsByName, name, processor) + } + + override fun processPropertiesByName(name: Name, processor: (FirVariableSymbol<*>) -> Unit) { + return processComposite(FirScope::processPropertiesByName, name, processor) + } + + private inline fun > processTypedComposite( + process: FirTypeScope.(N, (T, FirTypeScope) -> ProcessorAction) -> ProcessorAction, + name: N, + noinline processor: (T, FirTypeScope) -> ProcessorAction + ): ProcessorAction { + originalScope.process(name) { symbol, firTypeScope -> + processor(symbol, firTypeScope) + }.let { if (it == ProcessorAction.STOP) return ProcessorAction.STOP } + + smartcastScope.process(name) { symbol, firTypeScope -> + // Only process the symbol if the dispatcher type is exactly the smartcast type. This way, we don't add any additional + // symbols that already exists in the original scope. + if ((symbol.fir as? FirCallableMemberDeclaration)?.dispatchReceiverType == smartcastType) { + processor(symbol, firTypeScope) + } else { + ProcessorAction.NEXT + } + }.let { if (it == ProcessorAction.STOP) return ProcessorAction.STOP } + return ProcessorAction.NEXT + } + + override fun processDirectOverriddenFunctionsWithBaseScope( + functionSymbol: FirNamedFunctionSymbol, + processor: (FirNamedFunctionSymbol, FirTypeScope) -> ProcessorAction + ): ProcessorAction { + return processTypedComposite(FirTypeScope::processDirectOverriddenFunctionsWithBaseScope, functionSymbol, processor) + } + + override fun processDirectOverriddenPropertiesWithBaseScope( + propertySymbol: FirPropertySymbol, + processor: (FirPropertySymbol, FirTypeScope) -> ProcessorAction + ): ProcessorAction { + return processTypedComposite(FirTypeScope::processDirectOverriddenPropertiesWithBaseScope, propertySymbol, processor) + } + + override fun getCallableNames(): Set { + return scopes.flatMapTo(hashSetOf()) { it.getContainingCallableNamesIfPresent() } + } + + override fun getClassifierNames(): Set { + return scopes.flatMapTo(hashSetOf()) { it.getContainingClassifierNamesIfPresent() } + } + + override val scopeOwnerLookupNames: List by lazy(LazyThreadSafetyMode.PUBLICATION) { + scopes.flatMap { it.scopeOwnerLookupNames } + } +} diff --git a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/tower/CandidateApplicability.kt b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/tower/CandidateApplicability.kt index d6c6dc02548..7943b5fb18a 100644 --- a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/tower/CandidateApplicability.kt +++ b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/tower/CandidateApplicability.kt @@ -15,7 +15,7 @@ enum class CandidateApplicability { IMPOSSIBLE_TO_GENERATE, // access to outer class from nested RUNTIME_ERROR, // problems with visibility UNSAFE_CALL, // receiver nullability doesn't match - MAY_THROW_RUNTIME_ERROR, // unstable smart cast + UNSTABLE_SMARTCAST, // unstable smart cast CONVENTION_ERROR, // missing infix, operator etc RESOLVED_LOW_PRIORITY, RESOLVED_NEED_PRESERVE_COMPATIBILITY, // call resolved successfully, but using new features that changes resolve diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/model/KotlinCallDiagnostics.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/model/KotlinCallDiagnostics.kt index 11a592577f3..1aee781a4f3 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/model/KotlinCallDiagnostics.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/model/KotlinCallDiagnostics.kt @@ -161,7 +161,7 @@ sealed class UnstableSmartCast( class UnstableSmartCastResolutionError( argument: ExpressionKotlinCallArgument, targetType: UnwrappedType, -) : UnstableSmartCast(argument, targetType, MAY_THROW_RUNTIME_ERROR) +) : UnstableSmartCast(argument, targetType, UNSTABLE_SMARTCAST) class UnstableSmartCastDiagnosticError( argument: ExpressionKotlinCallArgument, @@ -218,7 +218,7 @@ class ArgumentTypeMismatchDiagnostic( val expectedType: UnwrappedType, val actualType: UnwrappedType, val expressionArgument: ExpressionKotlinCallArgument -) : KotlinCallDiagnostic(MAY_THROW_RUNTIME_ERROR) { +) : KotlinCallDiagnostic(UNSTABLE_SMARTCAST) { override fun report(reporter: DiagnosticReporter) { reporter.onCallArgument(expressionArgument, this) } diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/ImplicitScopeTower.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/ImplicitScopeTower.kt index 418d191202a..2f70131750e 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/ImplicitScopeTower.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/ImplicitScopeTower.kt @@ -120,7 +120,7 @@ object ErrorDescriptorDiagnostic : ResolutionDiagnostic(RESOLVED) // todo discus object LowPriorityDescriptorDiagnostic : ResolutionDiagnostic(RESOLVED_LOW_PRIORITY) object DynamicDescriptorDiagnostic : ResolutionDiagnostic(RESOLVED_LOW_PRIORITY) object ResolvedUsingNewFeatures : ResolutionDiagnostic(RESOLVED_NEED_PRESERVE_COMPATIBILITY) -object UnstableSmartCastDiagnostic : ResolutionDiagnostic(MAY_THROW_RUNTIME_ERROR) +object UnstableSmartCastDiagnostic : ResolutionDiagnostic(UNSTABLE_SMARTCAST) object HiddenExtensionRelatedToDynamicTypes : ResolutionDiagnostic(HIDDEN) object HiddenDescriptor : ResolutionDiagnostic(HIDDEN) diff --git a/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/unstableSmartcastWhenOpenGetterWithOverloading.fir.kt b/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/unstableSmartcastWhenOpenGetterWithOverloading.fir.kt deleted file mode 100644 index eef4481310c..00000000000 --- a/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/unstableSmartcastWhenOpenGetterWithOverloading.fir.kt +++ /dev/null @@ -1,15 +0,0 @@ -// !DIAGNOSTICS: -UNUSED_PARAMETER - -interface Ctx -class CtxImpl : Ctx { - fun doJob(a: Int) {} - fun doJob(s: String) {} -} - -open class Test(open val ctx: Ctx) { - fun test() { - when (ctx) { - is CtxImpl -> ctx.doJob(2) - } - } -} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/unstableSmartcastWhenOpenGetterWithOverloading.kt b/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/unstableSmartcastWhenOpenGetterWithOverloading.kt index 9470f9633c6..838a09497f6 100644 --- a/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/unstableSmartcastWhenOpenGetterWithOverloading.kt +++ b/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/unstableSmartcastWhenOpenGetterWithOverloading.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !DIAGNOSTICS: -UNUSED_PARAMETER interface Ctx diff --git a/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/unstableSmartcastWithOverloadedExtensions.fir.kt b/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/unstableSmartcastWithOverloadedExtensions.fir.kt deleted file mode 100644 index 8aa89b44288..00000000000 --- a/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/unstableSmartcastWithOverloadedExtensions.fir.kt +++ /dev/null @@ -1,14 +0,0 @@ -// !DIAGNOSTICS: -UNUSED_PARAMETER - -class A -class B - -fun A.foo(a: A) {} -fun A.foo(b: B) {} -var a: A? = null - -fun smartCastInterference(b: B) { - if (a != null) { - a.foo(b) - } -} diff --git a/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/unstableSmartcastWithOverloadedExtensions.kt b/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/unstableSmartcastWithOverloadedExtensions.kt index 8a05ee7fba5..dd7243cff22 100644 --- a/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/unstableSmartcastWithOverloadedExtensions.kt +++ b/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/unstableSmartcastWithOverloadedExtensions.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !DIAGNOSTICS: -UNUSED_PARAMETER class A diff --git a/compiler/testData/diagnostics/tests/smartCasts/inference/kt39010.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/inference/kt39010.fir.kt deleted file mode 100644 index 1c01fc7674d..00000000000 --- a/compiler/testData/diagnostics/tests/smartCasts/inference/kt39010.fir.kt +++ /dev/null @@ -1,11 +0,0 @@ -class A { - fun foo(): E = TODO() -} - -class B(var a: A<*>?) { - fun bar() { - if (a != null) { - a.foo() - } - } -} diff --git a/compiler/testData/diagnostics/tests/smartCasts/inference/kt39010.kt b/compiler/testData/diagnostics/tests/smartCasts/inference/kt39010.kt index 79b4b03581d..b9398fa2c9f 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/inference/kt39010.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/inference/kt39010.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL class A { fun foo(): E = TODO() } diff --git a/compiler/testData/diagnostics/tests/smartCasts/inference/kt39010_2.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/inference/kt39010_2.fir.kt deleted file mode 100644 index 29b5304ca7b..00000000000 --- a/compiler/testData/diagnostics/tests/smartCasts/inference/kt39010_2.fir.kt +++ /dev/null @@ -1,16 +0,0 @@ -open class A { -} - -class B : A() { - fun foo() {} -} - -interface KI { - val a: A<*> -} - -fun KI.bar() { - if (a is B) { - a.foo() - } -} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/smartCasts/inference/kt39010_2.kt b/compiler/testData/diagnostics/tests/smartCasts/inference/kt39010_2.kt index 92ad9e96636..ecdf447cc75 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/inference/kt39010_2.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/inference/kt39010_2.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL open class A { } diff --git a/compiler/testData/diagnostics/tests/smartCasts/intersectionScope/unstableSmartCast.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/intersectionScope/unstableSmartCast.fir.kt deleted file mode 100644 index e6af93cd7ea..00000000000 --- a/compiler/testData/diagnostics/tests/smartCasts/intersectionScope/unstableSmartCast.fir.kt +++ /dev/null @@ -1,37 +0,0 @@ -// !CHECK_TYPE - -interface A { - fun foo(): CharSequence? - fun baz(x: Any) {} -} - -interface B { - fun foo(): String - fun baz(x: Int): String ="" - fun baz(x: Int, y: Int) {} - - fun foobar(): CharSequence? -} - -interface C { - fun foo(): String - fun baz(x: Int): String ="" - fun baz(x: Int, y: Int) {} - - fun foobar(): String -} - -var x: A = null!! - -fun test() { - x.foo().checkType { _() } - - if (x is B && x is C) { - x.foo().checkType { _() } - x.baz("") - x.baz(1).checkType { _() } - x.baz(1, 2) - - x.foobar().checkType { _() } - } -} diff --git a/compiler/testData/diagnostics/tests/smartCasts/intersectionScope/unstableSmartCast.kt b/compiler/testData/diagnostics/tests/smartCasts/intersectionScope/unstableSmartCast.kt index e8db585a64d..bc5813c837f 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/intersectionScope/unstableSmartCast.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/intersectionScope/unstableSmartCast.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !CHECK_TYPE interface A { diff --git a/compiler/testData/diagnostics/tests/smartCasts/localDelegatedPropertyAfter.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/localDelegatedPropertyAfter.fir.kt index 13f0a640d8d..934a279a0bb 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/localDelegatedPropertyAfter.fir.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/localDelegatedPropertyAfter.fir.kt @@ -10,6 +10,6 @@ fun failsWithClassCastException() { val sometimesNotInt: Any? by AlternatingDelegate() if (sometimesNotInt is Int) { - sometimesNotInt.inc() + sometimesNotInt.inc() } -} \ No newline at end of file +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/localDelegatedPropertyBefore.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/localDelegatedPropertyBefore.fir.kt index 8777e5aea0a..13b63862b6b 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/localDelegatedPropertyBefore.fir.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/localDelegatedPropertyBefore.fir.kt @@ -10,6 +10,6 @@ fun failsWithClassCastException() { val sometimesNotInt: Any? by AlternatingDelegate() if (sometimesNotInt is Int) { - sometimesNotInt.inc() + sometimesNotInt.inc() } -} \ No newline at end of file +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/openInSealed.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/openInSealed.fir.kt index e8928c7d8f1..70a19484fba 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/openInSealed.fir.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/openInSealed.fir.kt @@ -2,7 +2,7 @@ sealed class My(open val x: Int?) { init { if (x != null) { // Should be error: property is open - x.hashCode() + x.hashCode() } } } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/smartCasts/propertyToNotNull.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/propertyToNotNull.fir.kt index f14a192a0df..cd502255331 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/propertyToNotNull.fir.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/propertyToNotNull.fir.kt @@ -7,7 +7,7 @@ class Immutable(val x: String?) { class Mutable(var y: String?) { fun foo(): String { - if (y != null) return y + if (y != null) return y return "" } } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/smartCasts/publicVals/customGetter.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/publicVals/customGetter.fir.kt deleted file mode 100644 index 6cbdc76ca22..00000000000 --- a/compiler/testData/diagnostics/tests/smartCasts/publicVals/customGetter.fir.kt +++ /dev/null @@ -1,13 +0,0 @@ -public class X { - private val x : String? = null - public val y: CharSequence? - get() = x?.subSequence(0, 1) - public fun fn(): Int { - if (y != null) - // With non-default getter smartcast is not possible - return y.length - else - return 0 - } -} - diff --git a/compiler/testData/diagnostics/tests/smartCasts/publicVals/customGetter.kt b/compiler/testData/diagnostics/tests/smartCasts/publicVals/customGetter.kt index 645344871e1..61f6ed0bc73 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/publicVals/customGetter.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/publicVals/customGetter.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL public class X { private val x : String? = null public val y: CharSequence? diff --git a/compiler/testData/diagnostics/tests/smartCasts/publicVals/open.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/publicVals/open.fir.kt deleted file mode 100644 index 4c34ecac3aa..00000000000 --- a/compiler/testData/diagnostics/tests/smartCasts/publicVals/open.fir.kt +++ /dev/null @@ -1,11 +0,0 @@ -public open class A() { - public open val foo: Int? = 1 -} - -infix fun Int.bar(i: Int) = i - -fun test() { - val p = A() - // For open value properties, smart casts should not work - if (p.foo is Int) p.foo bar 11 -} diff --git a/compiler/testData/diagnostics/tests/smartCasts/publicVals/open.kt b/compiler/testData/diagnostics/tests/smartCasts/publicVals/open.kt index 770e1fcca6a..3bfb8482af7 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/publicVals/open.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/publicVals/open.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL public open class A() { public open val foo: Int? = 1 } diff --git a/compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModule.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModule.fir.kt deleted file mode 100644 index 10449588bdd..00000000000 --- a/compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModule.fir.kt +++ /dev/null @@ -1,35 +0,0 @@ -// MODULE: m1 -// FILE: a.kt - -package a - -public class X { - public val x : String? = null -} - -// MODULE: m2(m1) -// FILE: b.kt - -package b - -import a.X - -public fun X.gav(): Int { - if (x != null) - // Smart cast is not possible if definition is in another module - return x.length - else - return 0 -} - -// FILE: c.kt - -package a - -public fun X.gav(): Int { - if (x != null) - // Even if it's in the same package - return x.length - else - return 0 -} diff --git a/compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModule.kt b/compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModule.kt index c5496f9ab03..ab085b21210 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModule.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModule.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // MODULE: m1 // FILE: a.kt diff --git a/compiler/testData/diagnostics/tests/smartCasts/publicVals/var.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/publicVals/var.fir.kt deleted file mode 100644 index 8b8e840c20a..00000000000 --- a/compiler/testData/diagnostics/tests/smartCasts/publicVals/var.fir.kt +++ /dev/null @@ -1,15 +0,0 @@ -public class X { - public var x : String? = null - private var y: String? = "abc" - public fun fn(): Int { - if (x != null) - // Smartcast is not possible for variable properties - return x.length - else if (y != null) - // Even if they are private - return y.length - else - return 0 - } -} - diff --git a/compiler/testData/diagnostics/tests/smartCasts/publicVals/var.kt b/compiler/testData/diagnostics/tests/smartCasts/publicVals/var.kt index c8ca4850b83..94cb475bdd9 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/publicVals/var.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/publicVals/var.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL public class X { public var x : String? = null private var y: String? = "abc" diff --git a/compiler/testData/diagnostics/tests/smartCasts/unstableToStable.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/unstableToStable.fir.kt deleted file mode 100644 index 472d38cca3b..00000000000 --- a/compiler/testData/diagnostics/tests/smartCasts/unstableToStable.fir.kt +++ /dev/null @@ -1,18 +0,0 @@ -class Foo(var x: Int?) { - init { - if (x != null) { - val y = x - // Error: x is not stable, Type(y) = Int? - x.hashCode() - y.hashCode() - if (y == x) { - // Still error - y.hashCode() - } - if (x == null && y != x) { - // Still error - y.hashCode() - } - } - } -} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/smartCasts/unstableToStable.kt b/compiler/testData/diagnostics/tests/smartCasts/unstableToStable.kt index 53baa677a91..5216bc63586 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/unstableToStable.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/unstableToStable.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL class Foo(var x: Int?) { init { if (x != null) { diff --git a/compiler/testData/diagnostics/tests/smartCasts/unstableToStableTypes.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/unstableToStableTypes.fir.kt deleted file mode 100644 index 4702c095847..00000000000 --- a/compiler/testData/diagnostics/tests/smartCasts/unstableToStableTypes.fir.kt +++ /dev/null @@ -1,22 +0,0 @@ -class Bar { - fun bar() {} -} - -class Foo(var x: Any) { - init { - if (x is Bar) { - val y = x - // Error: x is not stable, Type(y) = Any - x.bar() - y.bar() - if (y == x) { - // Still error - y.bar() - } - if (x !is Bar && y != x) { - // Still error - y.bar() - } - } - } -} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/smartCasts/unstableToStableTypes.kt b/compiler/testData/diagnostics/tests/smartCasts/unstableToStableTypes.kt index 5119d5d11b1..1a872d2f169 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/unstableToStableTypes.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/unstableToStableTypes.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL class Bar { fun bar() {} } diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/receiver.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/receiver.fir.kt index bf485ab0d1e..6fb91017f33 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/receiver.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/receiver.fir.kt @@ -28,7 +28,7 @@ class UnstableReceiver { x.inc() } else { - x.dec() + x.dec() } } } diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirDiagnosticsHandler.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirDiagnosticsHandler.kt index e49ce937d6f..6fe6e2bacc6 100644 --- a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirDiagnosticsHandler.kt +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirDiagnosticsHandler.kt @@ -9,7 +9,6 @@ import com.intellij.psi.PsiElement import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.checkers.diagnostics.factories.DebugInfoDiagnosticFactory1 import org.jetbrains.kotlin.checkers.utils.TypeOfCall -import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.rendering.Renderers import org.jetbrains.kotlin.fir.* import org.jetbrains.kotlin.fir.analysis.diagnostics.* @@ -43,6 +42,7 @@ import org.jetbrains.kotlin.test.model.TestFile import org.jetbrains.kotlin.test.model.TestModule import org.jetbrains.kotlin.test.services.* import org.jetbrains.kotlin.test.utils.AbstractTwoAttributesMetaInfoProcessor +import org.jetbrains.kotlin.types.SmartcastStability import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.utils.addIfNotNull @@ -176,7 +176,7 @@ class FirDiagnosticsHandler(testServices: TestServices) : FirAnalysisHandler(tes diagnosedRangesToDiagnosticNames: Map> ): FirDiagnosticWithParameters1? = DebugInfoDiagnosticFactory1.EXPRESSION_TYPE.createDebugInfoDiagnostic(element, diagnosedRangesToDiagnosticNames) { - element.typeRef.renderAsString((element as? FirExpressionWithSmartcast)?.originalType) + element.typeRef.renderAsString((element as? FirExpressionWithSmartcast)?.takeIf { it.smartcastStability == SmartcastStability.STABLE_VALUE }?.originalType) } private fun FirTypeRef.renderAsString(originalTypeRef: FirTypeRef?): String { diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/53.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/53.fir.kt index 84ee533bd4c..501fe32d597 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/53.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/53.fir.kt @@ -33,7 +33,7 @@ fun case_3(x: Int) = "" fun case_3(x: Int?) = 10 fun case_3() { if (case_3_prop != null) { - val z = case_3(case_3_prop) + val z = case_3(case_3_prop) z } } @@ -89,7 +89,7 @@ fun case_7(x: Int) = "" fun case_7(x: Int?) = 10 fun case_7() { if (case_7_prop != null) { - val z = case_7(case_7_prop) + val z = case_7(case_7_prop) z } } diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt index c684d733e7c..c0d366f98a9 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -1382,6 +1382,15 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirErrors.SMARTCAST_IMPOSSIBLE) { firDiagnostic -> + SmartcastImpossibleImpl( + firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.a), + firDiagnostic.b.source!!.psi as KtExpression, + firDiagnostic.c, + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } add(FirErrors.EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED) { firDiagnostic -> ExtensionInClassReferenceNotAllowedImpl( firSymbolBuilder.callableBuilder.buildCallableSymbol(firDiagnostic.a as FirCallableDeclaration), diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt index 99d600c0497..cb48e8a13a5 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt @@ -978,6 +978,13 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { abstract val containingType: KtType } + abstract class SmartcastImpossible : KtFirDiagnostic() { + override val diagnosticClass get() = SmartcastImpossible::class + abstract val desiredType: KtType + abstract val subject: KtExpression + abstract val description: String + } + abstract class ExtensionInClassReferenceNotAllowed : KtFirDiagnostic() { override val diagnosticClass get() = ExtensionInClassReferenceNotAllowed::class abstract val referencedDeclaration: KtCallableSymbol diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index 151597931e3..e6458906b79 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -1579,6 +1579,16 @@ internal class TypeVarianceConflictInExpandedTypeImpl( override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) } +internal class SmartcastImpossibleImpl( + override val desiredType: KtType, + override val subject: KtExpression, + override val description: String, + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.SmartcastImpossible(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + internal class ExtensionInClassReferenceNotAllowedImpl( override val referencedDeclaration: KtCallableSymbol, firDiagnostic: FirPsiDiagnostic<*>, diff --git a/idea/testData/checker/infos/SmartCasts.fir.kt b/idea/testData/checker/infos/SmartCasts.fir.kt index 503884ed227..e927bee71fa 100644 --- a/idea/testData/checker/infos/SmartCasts.fir.kt +++ b/idea/testData/checker/infos/SmartCasts.fir.kt @@ -234,13 +234,13 @@ class Mutable(var x: String?) { fun foo(): String { if (x is String) { - return x + return x } if (x != null) { - return x + return x } if (xx is String) { - return xx + return xx } return "" } @@ -248,7 +248,7 @@ class Mutable(var x: String?) { fun bar(other: Mutable): String { var y = other if (y.x is String) { - return y.x + return y.x } return "" }