[K2] isUsedAsExpression analysis

This commit is contained in:
Kristoffer Andersen
2022-08-01 17:46:54 +02:00
committed by teamcity
parent d9701c71b7
commit f765457e51
673 changed files with 11862 additions and 10 deletions
@@ -5,18 +5,24 @@
package org.jetbrains.kotlin.analysis.api.fir.components
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.calls.KtSimpleVariableAccessCall
import org.jetbrains.kotlin.analysis.api.calls.KtSuccessCallInfo
import org.jetbrains.kotlin.analysis.api.components.KtExpressionInfoProvider
import org.jetbrains.kotlin.analysis.api.fir.KtFirAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeToken
import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getOrBuildFirSafe
import org.jetbrains.kotlin.diagnostics.WhenMissingCase
import org.jetbrains.kotlin.fir.declarations.FirErrorFunction
import org.jetbrains.kotlin.fir.expressions.FirReturnExpression
import org.jetbrains.kotlin.fir.expressions.FirWhenExpression
import org.jetbrains.kotlin.fir.resolve.transformers.FirWhenExhaustivenessTransformer
import org.jetbrains.kotlin.psi.KtReturnExpression
import org.jetbrains.kotlin.psi.KtWhenExpression
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.unwrapParenthesesLabelsAndAnnotations
internal class KtFirExpressionInfoProvider(
override val analysisSession: KtFirAnalysisSession,
@@ -33,4 +39,398 @@ internal class KtFirExpressionInfoProvider(
val firWhenExpression = whenExpression.getOrBuildFirSafe<FirWhenExpression>(analysisSession.firResolveSession) ?: return emptyList()
return FirWhenExhaustivenessTransformer.computeAllMissingCases(analysisSession.firResolveSession.useSiteFirSession, firWhenExpression)
}
override fun isUsedAsExpression(expression: KtExpression): Boolean =
isUsed(expression)
/**
* [isUsed] and [doesParentUseChild] are defined in mutual recursion,
* climbing up the syntax tree, passing control back and forth between the
* two.
*
* Whether an expression is used is defined by the context in which it
* appears. E.g. a "statement" in a block is considered used if it is the
* last expression in that block AND the block itself is used -- a
* recursive call to `isUsed`, one level higher in the syntax tree.
*
* The methods are _conservative_, erring on the side of answering `true`.
*/
private fun isUsed(psiElement: PsiElement): Boolean {
return when (psiElement) {
/**
* DECLARATIONS
*/
// Inner PSI of KtLambdaExpressions. Used if the containing KtLambdaExpression is.
is KtFunctionLiteral ->
doesParentUseChild(psiElement.parent, psiElement)
// KtNamedFunction includes `fun() { ... }` lambda syntax. No other
// "named" functions can be expressions.
is KtNamedFunction ->
doesParentUseChild(psiElement.parent, psiElement)
// No other declarations are considered expressions
is KtDeclaration ->
false
/**
* EXPRESSIONS
*/
// A handful of expression are never considered used:
// - Everything of type `Nothing`
is KtThrowExpression ->
false
is KtReturnExpression ->
false
is KtBreakExpression ->
false
is KtContinueExpression ->
false
// - Loops
is KtLoopExpression ->
false
// - The `this` in `constructor(x: Int) : this(x)`
is KtConstructorDelegationReferenceExpression ->
false
// - Administrative node for EnumEntries. Never used as expression.
is KtEnumEntrySuperclassReferenceExpression ->
false
// - The "reference" in a constructor call. E.g. `C` in `C()`
is KtConstructorCalleeExpression ->
false
// - Labels themselves: `@label` in return`@label` or `label@`while...
is KtLabelReferenceExpression ->
false
// - The operation symbol itself in binary and unary operations: `!!`, `+`...
is KtOperationReferenceExpression ->
false
// All other expressions are used if their parent expression uses them.
else ->
doesParentUseChild(psiElement.parent, psiElement)
}
}
private fun doesParentUseChild(parent: PsiElement, child: PsiElement): Boolean {
return when (parent) {
/**
* NON-EXPRESSION PARENTS
*/
// KtValueArguments are a container for call-sites, and use exactly the
// argument expression they wrap.
is KtValueArgument ->
parent.getArgumentExpression() == child
// KtContainerNode are containers used in `KtIfExpressions`, and should be regarded
// as parentheses for the purpose of this analysis.
is KtContainerNode ->
// !!!!CAUTION!!!! Not `parentUse(parent.parent, _parent_)`
// Here we assume the parent (e.g. If condition) statement
// ignores the ContainerNode when accessing child
doesParentUseChild(parent.parent, child)
// KtWhenEntry/WhenCondition are containers used in KtWhenExpressions, ard
// should be regarded as parentheses.
is KtWhenEntry ->
(parent.expression == child && isUsed(parent.parent)) || child in parent.conditions
is KtWhenCondition ->
doesParentUseChild(parent.parent, parent)
// Type parameters, return types and other annotations are all contained in KtUserType,
// and are never considered used as expressions
is KtUserType ->
false
// Only top-level named declarations have KtFile/KtScript Parents, and are never considered used
is KtFile ->
false
is KtScript ->
false
// Only class members have KtClassBody parents, and are never considered used
is KtClassBody ->
false
// $_ and ${_} contexts use their inner expression
is KtStringTemplateEntry ->
parent.expression == child
// Catch blocks are used if the parent-try uses the catch block
is KtCatchClause ->
doesParentUseChild(parent.parent, parent)
// Finally blocks are never used
is KtFinallySection ->
false
!is KtExpression ->
error("Unhandled Non-KtExpression parent of KtExpression: ${parent::class}")
/**
* EXPRESSIONS
*/
// Enum entries, type parameters, lamda expressions and script
// initializers never use any child expressions.
is KtEnumEntry ->
false
is KtTypeParameter ->
false
is KtLambdaExpression ->
false
is KtScriptInitializer ->
false
// The last expression of a block is considered used iff the block itself is used.
is KtBlockExpression ->
parent.statements.lastOrNull() == child && isUsed(parent)
// Destructuring declarations use their initializer.
is KtDestructuringDeclaration ->
parent.initializer == child
// Backing field declarations use their initializer.
is KtBackingField ->
parent.initializer == child
// Property accessors can use their bodies if not blocks.
is KtPropertyAccessor ->
parent.bodyExpression == child && doesPropertyAccessorUseBody(parent, child)
// Lambdas do not use their expression-blocks if they are inferred
// to be of unit type
is KtFunctionLiteral ->
parent.bodyBlockExpression == child && !returnsUnit(parent)
// Named functions do not use their bodies if the function itself returns unit
// UNLESS it's an expression body/lambda of type Unit.
/** See [doesNamedFunctionUseBody] */
is KtNamedFunction ->
doesNamedFunctionUseBody(parent, child)
// Function parameter declarations use their default value expressions.
is KtParameter ->
parent.defaultValue == child
// Variable declarations use their initializer.
is KtVariableDeclaration ->
parent.initializer == child
// Binary expressions always use both operands.
is KtBinaryExpression ->
parent.left == child || parent.right == child
// Binary expressions with type RHS always use its operand.
is KtBinaryExpressionWithTypeRHS ->
parent.left == child
// Is expressions always use their LHS.
is KtIsExpression ->
parent.leftHandSide == child
// Unary expressions always use its operand.
is KtUnaryExpression ->
parent.baseExpression == child
// Qualified expressions always use its receiver. The selector is
// used iff the qualified expression is.
is KtQualifiedExpression ->
parent.receiverExpression == child || (parent.selectorExpression == child && isUsed(parent))
// Array accesses use both receiver and index.
is KtArrayAccessExpression ->
child in parent.indexExpressions || parent.arrayExpression == child
// Calls use only the callee directly -- arguments are wrapped in a
// KtValueArgument container
is KtCallExpression ->
parent.calleeExpression == child && doesCallExpressionUseCallee(child)
// Collection literals use each of its constituent expressions.
is KtCollectionLiteralExpression ->
child in parent.getInnerExpressions()
// Annotations are regarded as parentheses. The annotation itself is never used.
is KtAnnotatedExpression ->
parent.baseExpression == child && isUsed(parent)
/** See [doesDoubleColonUseLHS] */
is KtDoubleColonExpression ->
parent.lhs == child && doesDoubleColonUseLHS(child)
// Parentheses are ignored for this analysis.
is KtParenthesizedExpression ->
doesParentUseChild(parent.parent, parent)
// When expressions use the subject expression _unless_ the first branch in the
// when is an `else`.
is KtWhenExpression ->
parent.subjectExpression == child && parent.entries.firstOrNull()?.isElse == false
// Throw expressions use the expression thrown.
is KtThrowExpression ->
parent.thrownExpression == child
// Body and catch blocks of try-catch expressions are used if the try-catch itself
// is used.
is KtTryExpression ->
(parent.tryBlock == child || child in parent.catchClauses) && isUsed(parent)
// If expressions always use their condition, and the branches are used if the
// If itself is used as an expression.
is KtIfExpression ->
parent.condition == child ||
((parent.then == child ||
parent.`else` == child) && isUsed(parent))
// For expressions use their loop range expression.
is KtForExpression ->
parent.loopRange == child
// While, DoWhile loops use their conditions, not their bodies
is KtWhileExpressionBase ->
parent.condition == child
// Return expressions use the return value
is KtReturnExpression ->
parent.returnedExpression == child
// Labels are regarded as parentheses for this analysis. The label itself is never used.
is KtLabeledExpression ->
parent.baseExpression == child && isUsed(parent)
// No children.
is KtConstantExpression ->
false
// no children of class and script initializers are used
is KtAnonymousInitializer ->
false
// no child expressions of primary constructors.
is KtPrimaryConstructor ->
false // error?
// no children of secondary constructs are used.
is KtSecondaryConstructor ->
false
// KtClass, KtObjectDeclaration, KtTypeAlias has no expression children
is KtClassLikeDeclaration ->
false // has no expression children
// Simple names do not have expression children
// Labels, operations, references by name
is KtSimpleNameExpression ->
false
// this/super in constructor delegations. No expression children
is KtConstructorDelegationReferenceExpression ->
false
// Object Literal expressions use none of its children.
is KtObjectLiteralExpression ->
false
// break, continue, super, this do not have children
is KtBreakExpression ->
false
is KtContinueExpression ->
false
is KtSuperExpression ->
false
is KtThisExpression ->
false
// No direct expression children
is KtStringTemplateExpression ->
false
else ->
error("Unhandled KtElement subtype: ${parent::class}")
}
}
}
/**
* The left hand side of a `::` is regarded as used unless it refers to a type.
* We decide that the LHS is a type reference by checking if the left hand
* side is a (qualified) name, and, in case it _is_, resolving that name.
*
* If it resolves to a non-class declaration, it does _not_ refer to a type.
*/
private fun doesDoubleColonUseLHS(lhs: PsiElement): Boolean {
val reference = when (val inner = lhs.unwrapParenthesesLabelsAndAnnotations()) {
is KtReferenceExpression ->
inner.mainReference
is KtDotQualifiedExpression ->
(inner.selectorExpression as? KtReferenceExpression)?.mainReference ?: return true
else ->
return true
}
val resolution = reference.resolve()
return resolution != null && resolution !is KtClass
}
/**
* Invocations of _statically named_ callables is not considered a use. E.g.
* consider
*
* 1) fun f() { 54 }; f()
* 2) val f = { 54 }; f()
*
* in which the `f` in 2) is regarded as used and `f` in 1) is not.
*/
private fun doesCallExpressionUseCallee(callee: PsiElement): Boolean {
return callee !is KtReferenceExpression ||
analyze(callee) {
isSimpleVariableAccessCall(callee)
}
}
/**
* The body of setters are always used. The body of getters are only used if they are expression bodies.
*/
private fun doesPropertyAccessorUseBody(propertyAccessor: KtPropertyAccessor, body: PsiElement): Boolean {
return propertyAccessor.isSetter || (propertyAccessor.isGetter && body !is KtBlockExpression)
}
/**
* Named functions do not consider their bodies used if the function itself
* returns Unit UNLESS the function body is an expression body and the body is
* of type Unit.
*/
private fun doesNamedFunctionUseBody(namedFunction: KtNamedFunction, body: PsiElement): Boolean =
when {
!returnsUnit(namedFunction) ->
true
namedFunction.bodyBlockExpression == body ->
false
namedFunction.bodyExpression == body ->
analyze(namedFunction) {
(body as KtExpression).getKtType()?.isUnit == true
}
else ->
false
}
private fun KtAnalysisSession.isSimpleVariableAccessCall(reference: KtReferenceExpression): Boolean =
when (val resolution = reference.resolveCall()) {
is KtSuccessCallInfo ->
resolution.call is KtSimpleVariableAccessCall
else ->
false
}
private fun returnsUnit(declaration: KtDeclaration): Boolean {
return analyze(declaration) {
declaration.getReturnKtType().isUnit
}
}
@@ -96,7 +96,12 @@ internal class KtFirExpressionTypeProvider(
}
override fun getReturnTypeForKtDeclaration(declaration: KtDeclaration): KtType {
val firDeclaration = declaration.getOrBuildFirOfType<FirCallableDeclaration>(firResolveSession)
val firDeclaration = when {
declaration is KtNamedFunction && declaration.name == null ->
declaration.getOrBuildFirOfType<FirAnonymousFunctionExpression>(firResolveSession).anonymousFunction
else ->
declaration.getOrBuildFirOfType<FirCallableDeclaration>(firResolveSession)
}
return firDeclaration.returnTypeRef.coneType.asKtType()
}
@@ -184,6 +184,12 @@ public class FirIdeDependentAnalysisSourceModuleHLExpressionTypeTestGenerated ex
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/returnExpression.kt");
}
@Test
@TestMetadata("singleExpressionLambdaBody.kt")
public void testSingleExpressionLambdaBody() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/singleExpressionLambdaBody.kt");
}
@Test
@TestMetadata("smartcast_asCallArg.kt")
public void testSmartcast_asCallArg() throws Exception {
@@ -184,6 +184,12 @@ public class FirIdeNormalAnalysisSourceModuleHLExpressionTypeTestGenerated exten
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/returnExpression.kt");
}
@Test
@TestMetadata("singleExpressionLambdaBody.kt")
public void testSingleExpressionLambdaBody() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/singleExpressionLambdaBody.kt");
}
@Test
@TestMetadata("smartcast_asCallArg.kt")
public void testSmartcast_asCallArg() throws Exception {