From 97ea9015072f3019c32da97fe2c1d026dfaac35e Mon Sep 17 00:00:00 2001 From: Mark Punzalan Date: Tue, 1 Jun 2021 22:55:23 +0000 Subject: [PATCH] FIR IDE: Add isDefinitelyNull() and isDefinitelyNotNull() to KtExpressionTypeProvider. --- .../components/KtExpressionTypeProvider.kt | 34 +++++++++++++++++- .../components/KtFirExpressionTypeProvider.kt | 36 +++++++++++++++---- 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtExpressionTypeProvider.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtExpressionTypeProvider.kt index 3a9486dc06f..81a8af85532 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtExpressionTypeProvider.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtExpressionTypeProvider.kt @@ -13,8 +13,9 @@ import org.jetbrains.kotlin.psi.KtExpression abstract class KtExpressionTypeProvider : KtAnalysisSessionComponent() { abstract fun getReturnTypeForKtDeclaration(declaration: KtDeclaration): KtType abstract fun getKtExpressionType(expression: KtExpression): KtType - abstract fun getExpectedType(expression: PsiElement): KtType? + abstract fun isDefinitelyNull(expression: KtExpression): Boolean + abstract fun isDefinitelyNotNull(expression: KtExpression): Boolean } interface KtExpressionTypeProviderMixIn : KtAnalysisSessionMixIn { @@ -30,4 +31,35 @@ interface KtExpressionTypeProviderMixIn : KtAnalysisSessionMixIn { */ fun PsiElement.getExpectedType(): KtType? = analysisSession.expressionTypeProvider.getExpectedType(this) + + /** + * Returns `true` if this expression is definitely null, based on declared nullability and smart cast types derived from + * data-flow analysis facts. Examples: + * ``` + * fun foo(t: T, nt: T?, s: String, ns: String?) { + * t // t.isDefinitelyNull() == false && t.isDefinitelyNotNull() == true + * nt // nt.isDefinitelyNull() == false && nt.isDefinitelyNotNull() == false + * s // s.isDefinitelyNull() == false && s.isDefinitelyNotNull() == true + * ns // ns.isDefinitelyNull() == false && ns.isDefinitelyNotNull() == false + * + * if (ns != null) { + * ns // ns.isDefinitelyNull() == false && ns.isDefinitelyNotNull() == true + * } else { + * ns // ns.isDefinitelyNull() == true && ns.isDefinitelyNotNull() == false + * } + * + * ns!! // From this point on: ns.isDefinitelyNull() == false && ns.isDefinitelyNotNull() == true + * } + * ``` + * Note that only nullability from "stable" smart cast types is considered. The + * [spec](https://kotlinlang.org/spec/type-inference.html#smart-cast-sink-stability) provides an explanation on smart cast stability. + */ + fun KtExpression.isDefinitelyNull(): Boolean = + analysisSession.expressionTypeProvider.isDefinitelyNull(this) + + /** + * Returns `true` if this expression is definitely not null. See [isDefinitelyNull] for examples. + */ + fun KtExpression.isDefinitelyNotNull(): Boolean = + analysisSession.expressionTypeProvider.isDefinitelyNotNull(this) } diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirExpressionTypeProvider.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirExpressionTypeProvider.kt index 32fdfa40a98..90c675c17d2 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirExpressionTypeProvider.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirExpressionTypeProvider.kt @@ -7,24 +7,22 @@ package org.jetbrains.kotlin.idea.frontend.api.fir.components import com.intellij.psi.PsiElement import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration -import org.jetbrains.kotlin.fir.expressions.FirExpression -import org.jetbrains.kotlin.fir.expressions.FirFunctionCall -import org.jetbrains.kotlin.fir.expressions.FirStatement -import org.jetbrains.kotlin.fir.expressions.argumentMapping +import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.psi import org.jetbrains.kotlin.fir.references.FirErrorNamedReference import org.jetbrains.kotlin.fir.references.FirNamedReference import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnresolvedNameError +import org.jetbrains.kotlin.fir.typeContext import org.jetbrains.kotlin.fir.types.ConeClassErrorType import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.fir.types.coneType import org.jetbrains.kotlin.idea.fir.low.level.api.api.getOrBuildFir import org.jetbrains.kotlin.idea.fir.low.level.api.api.getOrBuildFirOfType import org.jetbrains.kotlin.idea.fir.low.level.api.api.getOrBuildFirSafe -import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken import org.jetbrains.kotlin.idea.frontend.api.components.KtExpressionTypeProvider import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken import org.jetbrains.kotlin.idea.frontend.api.types.KtErrorType import org.jetbrains.kotlin.idea.frontend.api.types.KtType import org.jetbrains.kotlin.idea.frontend.api.withValidityAssertion @@ -142,6 +140,30 @@ internal class KtFirExpressionTypeProvider( private fun PsiElement.isIfCondition() = unwrapQualified { ifExpr, cond -> ifExpr.condition == cond } != null + + override fun isDefinitelyNull(expression: KtExpression): Boolean = + getDefiniteNullability(expression) == DefiniteNullability.DEFINITELY_NULL + + override fun isDefinitelyNotNull(expression: KtExpression): Boolean = + getDefiniteNullability(expression) == DefiniteNullability.DEFINITELY_NOT_NULL + + private fun getDefiniteNullability(expression: KtExpression): DefiniteNullability = withValidityAssertion { + // TODO: Check stability of smartcast (pending PR 4382) + return when (val fir = expression.getOrBuildFir(analysisSession.firResolveState)) { + is FirExpressionWithSmartcastToNull -> DefiniteNullability.DEFINITELY_NULL + is FirExpression -> { + // Note: This includes FirExpressionWithSmartcast + with(analysisSession.rootModuleSession.typeContext) { + if (!fir.typeRef.coneType.isNullableType()) { + DefiniteNullability.DEFINITELY_NOT_NULL + } else { + DefiniteNullability.UNKNOWN + } + } + } + else -> DefiniteNullability.UNKNOWN + } + } } private data class KtCallWithArgument(val call: KtCallExpression, val argument: KtExpression) @@ -165,4 +187,6 @@ private val PsiElement.nonContainerParent: PsiElement? get() = when (val parent = parent) { is KtContainerNode -> parent.parent else -> parent - } \ No newline at end of file + } + +private enum class DefiniteNullability { DEFINITELY_NULL, DEFINITELY_NOT_NULL, UNKNOWN }