FIR IDE: Add isDefinitelyNull() and isDefinitelyNotNull() to

KtExpressionTypeProvider.
This commit is contained in:
Mark Punzalan
2021-06-01 22:55:23 +00:00
committed by teamcityserver
parent c6427d57f1
commit 97ea901507
2 changed files with 63 additions and 7 deletions
@@ -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 <T : Any> 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)
}
@@ -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<KtIfExpression> { 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
}
}
private enum class DefiniteNullability { DEFINITELY_NULL, DEFINITELY_NOT_NULL, UNKNOWN }