[K2] isUsedAsExpression analysis
This commit is contained in:
committed by
teamcity
parent
d9701c71b7
commit
f765457e51
+7
-4
@@ -15,11 +15,9 @@ import org.jetbrains.kotlin.analysis.api.lifetime.withValidityAssertion
|
||||
import org.jetbrains.kotlin.analysis.utils.printer.parentOfType
|
||||
import org.jetbrains.kotlin.cfg.WhenChecker
|
||||
import org.jetbrains.kotlin.diagnostics.WhenMissingCase
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||
import org.jetbrains.kotlin.psi.KtReturnExpression
|
||||
import org.jetbrains.kotlin.psi.KtWhenExpression
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
|
||||
|
||||
internal class KtFe10ExpressionInfoProvider(
|
||||
override val analysisSession: KtFe10AnalysisSession
|
||||
@@ -40,4 +38,9 @@ internal class KtFe10ExpressionInfoProvider(
|
||||
val bindingContext = analysisContext.analyze(whenExpression)
|
||||
return WhenChecker.getMissingCases(whenExpression, bindingContext)
|
||||
}
|
||||
|
||||
override fun isUsedAsExpression(expression: KtExpression): Boolean {
|
||||
val bindingContext = analysisContext.analyze(expression)
|
||||
return expression.isUsedAsExpression(bindingContext)
|
||||
}
|
||||
}
|
||||
+2004
File diff suppressed because it is too large
Load Diff
+6
@@ -184,6 +184,12 @@ public class Fe10IdeNormalAnalysisSourceModuleHLExpressionTypeTestGenerated exte
|
||||
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 {
|
||||
|
||||
+403
-3
@@ -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
|
||||
}
|
||||
}
|
||||
+6
-1
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
+2004
File diff suppressed because it is too large
Load Diff
+2004
File diff suppressed because it is too large
Load Diff
+6
@@ -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 {
|
||||
|
||||
+6
@@ -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 {
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2010-2022 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.analysis.api.impl.base.test.cases.components.expressionInfoProvider
|
||||
|
||||
import org.jetbrains.kotlin.analysis.test.framework.base.AbstractAnalysisApiSingleFileTest
|
||||
import org.jetbrains.kotlin.analysis.test.framework.services.expressionMarkerProvider
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.test.model.TestModule
|
||||
import org.jetbrains.kotlin.test.services.TestServices
|
||||
import org.jetbrains.kotlin.test.services.assertions
|
||||
|
||||
abstract class AbstractIsUsedAsExpressionTest : AbstractAnalysisApiSingleFileTest() {
|
||||
override fun doTestByFileStructure(ktFile: KtFile, module: TestModule, testServices: TestServices) {
|
||||
|
||||
val expression = testServices.expressionMarkerProvider.getSelectedElementOfType<KtExpression>(ktFile).let {
|
||||
if (it is KtBlockExpression && it.statements.size == 1 && it.textRange == it.statements.single().textRange) {
|
||||
it.statements.single()
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
val actual = StringBuilder();
|
||||
|
||||
analyseForTest(expression) {
|
||||
actual.appendLine("expression: $expression")
|
||||
actual.appendLine("text: ${expression.text}")
|
||||
actual.appendLine("isUsedAsExpression: ${expression.isUsedAsExpression()}")
|
||||
}
|
||||
|
||||
testServices.assertions.assertEqualsToTestDataFileSibling(actual.toString())
|
||||
}
|
||||
}
|
||||
+8
-1
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.analysis.api.components.KtTypeRendererOptions
|
||||
import org.jetbrains.kotlin.analysis.test.framework.services.expressionMarkerProvider
|
||||
import org.jetbrains.kotlin.analysis.test.framework.base.AbstractAnalysisApiSingleFileTest
|
||||
import org.jetbrains.kotlin.analysis.test.framework.utils.executeOnPooledThreadInReadAction
|
||||
import org.jetbrains.kotlin.psi.KtBlockExpression
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtValueArgument
|
||||
@@ -18,7 +19,13 @@ import org.jetbrains.kotlin.test.services.assertions
|
||||
|
||||
abstract class AbstractHLExpressionTypeTest : AbstractAnalysisApiSingleFileTest() {
|
||||
override fun doTestByFileStructure(ktFile: KtFile, module: TestModule, testServices: TestServices) {
|
||||
val selected = testServices.expressionMarkerProvider.getSelectedElement(ktFile)
|
||||
val selected = testServices.expressionMarkerProvider.getSelectedElement(ktFile).let {
|
||||
if (it is KtBlockExpression && it.statements.size == 1 && it.textRange == it.statements.single().textRange) {
|
||||
it.statements.single()
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
val expression = when (selected) {
|
||||
is KtExpression -> selected
|
||||
is KtValueArgument -> selected.getArgumentExpression()
|
||||
|
||||
+2004
File diff suppressed because it is too large
Load Diff
+6
@@ -184,6 +184,12 @@ public class FirStandaloneNormalAnalysisSourceModuleHLExpressionTypeTestGenerate
|
||||
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 {
|
||||
|
||||
+26
@@ -8,12 +8,14 @@ package org.jetbrains.kotlin.analysis.api.components
|
||||
import org.jetbrains.kotlin.analysis.api.lifetime.withValidityAssertion
|
||||
import org.jetbrains.kotlin.diagnostics.WhenMissingCase
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtReturnExpression
|
||||
import org.jetbrains.kotlin.psi.KtWhenExpression
|
||||
|
||||
public abstract class KtExpressionInfoProvider : KtAnalysisSessionComponent() {
|
||||
public abstract fun getReturnExpressionTargetSymbol(returnExpression: KtReturnExpression): KtCallableSymbol?
|
||||
public abstract fun getWhenMissingCases(whenExpression: KtWhenExpression): List<WhenMissingCase>
|
||||
public abstract fun isUsedAsExpression(expression: KtExpression): Boolean
|
||||
}
|
||||
|
||||
public interface KtExpressionInfoProviderMixIn : KtAnalysisSessionMixIn {
|
||||
@@ -22,4 +24,28 @@ public interface KtExpressionInfoProviderMixIn : KtAnalysisSessionMixIn {
|
||||
|
||||
public fun KtWhenExpression.getMissingCases(): List<WhenMissingCase> =
|
||||
withValidityAssertion { analysisSession.expressionInfoProvider.getWhenMissingCases(this) }
|
||||
|
||||
/**
|
||||
* Compute if the value of a given expression is possibly used. Or,
|
||||
* conversely, compute whether the value of an expression is *not* safe to
|
||||
* discard.
|
||||
*
|
||||
* E.g. `x` in the following examples *are* used (`x.isUsedAsExpression() == true`)
|
||||
* - `if (x) { ... } else { ... }`
|
||||
* - `val a = x`
|
||||
* - `x + 8`
|
||||
* - `when (x) { 1 -> ...; else -> ... }
|
||||
*
|
||||
* E.g. `x` in the following example is definitely *not* used (`x.isUsedAsExpression() == false`)
|
||||
* - `run { x; println(50) }`
|
||||
* - `when (x) { else -> ... }`
|
||||
*
|
||||
* **Note!** This is a conservative check, not a control-flow analysis.
|
||||
* E.g. `x` in the following example *is possibly used*, even though the
|
||||
* value is never consumed at runtime.
|
||||
* - `x + try { throw Exception() } finally { return }`
|
||||
*
|
||||
*/
|
||||
public fun KtExpression.isUsedAsExpression(): Boolean =
|
||||
withValidityAssertion { analysisSession.expressionInfoProvider.isUsedAsExpression(this) }
|
||||
}
|
||||
analysis/analysis-api/testData/components/expressionInfoProvider/isUsedAsExpression/annotatedExpr.kt
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
fun main(args: Array<String>) {
|
||||
val x = args + (<expr>@OptIn(Deprecated::class) args</expr>)
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: ANNOTATED_EXPRESSION
|
||||
text: @OptIn(Deprecated::class) args
|
||||
isUsedAsExpression: true
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun main(args: Array<String>) {
|
||||
val x = args + (@<expr>OptIn</expr>(Deprecated::class) args)
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: CONSTRUCTOR_CALLEE
|
||||
text: OptIn
|
||||
isUsedAsExpression: false
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun main(args: Array<String>) {
|
||||
val x = args + (@OptIn(Deprecated::class) <expr>args</expr>)
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: REFERENCE_EXPRESSION
|
||||
text: args
|
||||
isUsedAsExpression: true
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun main(args: Array<String>) {
|
||||
(@OptIn(Deprecated::class) <expr>args</expr>)
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: REFERENCE_EXPRESSION
|
||||
text: args
|
||||
isUsedAsExpression: false
|
||||
analysis/analysis-api/testData/components/expressionInfoProvider/isUsedAsExpression/annotationArg.kt
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
@OptIn(Deprecated::class, <expr>Deprecated::class</expr>)
|
||||
@SuppressWarnings(["unsued", "foo"])
|
||||
fun test() {
|
||||
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: CLASS_LITERAL_EXPRESSION
|
||||
text: Deprecated::class
|
||||
isUsedAsExpression: true
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
@OptIn(Deprecated::class, Deprecated::class)
|
||||
@SuppressWarnings(<expr>["unsued", "foo"]</expr>)
|
||||
fun test() {
|
||||
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: COLLECTION_LITERAL_EXPRESSION
|
||||
text: ["unsued", "foo"]
|
||||
isUsedAsExpression: true
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
@OptIn(Deprecated::class, Deprecated::class)
|
||||
@SuppressWarnings(["unsued", <expr>"foo"</expr>])
|
||||
fun test() {
|
||||
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: STRING_TEMPLATE
|
||||
text: "foo"
|
||||
isUsedAsExpression: true
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
@OptIn(Deprecated::class, Deprecated::class)
|
||||
@<expr>SuppressWarnings</expr>(["unsued", "foo"])
|
||||
fun test() {
|
||||
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: CONSTRUCTOR_CALLEE
|
||||
text: SuppressWarnings
|
||||
isUsedAsExpression: false
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
fun println(x: Boolean) {}
|
||||
|
||||
fun test(b: Boolean): Int {
|
||||
println(<expr>b</expr>)
|
||||
return 54
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
expression: REFERENCE_EXPRESSION
|
||||
text: b
|
||||
isUsedAsExpression: true
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
fun main(args: Array<String>) {
|
||||
val s = <expr>args[0]</expr>
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
expression: ARRAY_ACCESS_EXPRESSION
|
||||
text: args[0]
|
||||
isUsedAsExpression: true
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun main(args: Array<String>, i: Int) {
|
||||
val s = <expr>args[i + i]</expr>
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: ARRAY_ACCESS_EXPRESSION
|
||||
text: args[i + i]
|
||||
isUsedAsExpression: true
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun main(args: Array<String>, i: Int) {
|
||||
val s = args[<expr>i + i</expr>]
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: BINARY_EXPRESSION
|
||||
text: i + i
|
||||
isUsedAsExpression: true
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun main(args: Array<String>) {
|
||||
val s = args[<expr>0</expr>]
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: INTEGER_CONSTANT
|
||||
text: 0
|
||||
isUsedAsExpression: true
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun main(args: Array<String>) {
|
||||
val s = <expr>args</expr>[0]
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: REFERENCE_EXPRESSION
|
||||
text: args
|
||||
isUsedAsExpression: true
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun test(a: Array<Int>) {
|
||||
<expr>(a + a)[0]++</expr>
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: POSTFIX_EXPRESSION
|
||||
text: (a + a)[0]++
|
||||
isUsedAsExpression: false
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun test(a: Array<Int>) {
|
||||
<expr>(a + a)</expr>[0]++
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: PARENTHESIZED
|
||||
text: (a + a)
|
||||
isUsedAsExpression: true
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun test(a: Array<Int>) {
|
||||
(<expr>a + a</expr>)[0]++
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: BINARY_EXPRESSION
|
||||
text: a + a
|
||||
isUsedAsExpression: true
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun test(a: Array<Int>) {
|
||||
<expr>a[0]</expr>++
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: ARRAY_ACCESS_EXPRESSION
|
||||
text: a[0]
|
||||
isUsedAsExpression: true
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun main(args: Array<String>) {
|
||||
<expr>args[0]</expr>
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: ARRAY_ACCESS_EXPRESSION
|
||||
text: args[0]
|
||||
isUsedAsExpression: false
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
fun test(v: Any?) {
|
||||
<expr>(v as String)</expr>.length
|
||||
}
|
||||
analysis/analysis-api/testData/components/expressionInfoProvider/isUsedAsExpression/asExpression.txt
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
expression: PARENTHESIZED
|
||||
text: (v as String)
|
||||
isUsedAsExpression: true
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun test(v: Any?) {
|
||||
<expr>(v as String)?.length</expr>
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: SAFE_ACCESS_EXPRESSION
|
||||
text: (v as String)?.length
|
||||
isUsedAsExpression: false
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun test(v: Any?) {
|
||||
<expr>(v as String)</expr>?.length
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: PARENTHESIZED
|
||||
text: (v as String)
|
||||
isUsedAsExpression: true
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun test(v: Any?) {
|
||||
(v as String)?.<expr>length</expr>
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: REFERENCE_EXPRESSION
|
||||
text: length
|
||||
isUsedAsExpression: false
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun test(v: Any?) {
|
||||
val x = (v as String)?.<expr>length</expr>
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: REFERENCE_EXPRESSION
|
||||
text: length
|
||||
isUsedAsExpression: true
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun test(v: Any?) {
|
||||
val x = <expr>(v as String)?.length</expr>
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: SAFE_ACCESS_EXPRESSION
|
||||
text: (v as String)?.length
|
||||
isUsedAsExpression: true
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun test(v: Any?) {
|
||||
(<expr>v as String</expr>).length
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: BINARY_WITH_TYPE
|
||||
text: v as String
|
||||
isUsedAsExpression: true
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun test(v: Any?) {
|
||||
(<expr>v</expr> as String).length
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: REFERENCE_EXPRESSION
|
||||
text: v
|
||||
isUsedAsExpression: true
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun test(v: Any?) {
|
||||
<expr>(v as? String)</expr>?.length
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: PARENTHESIZED
|
||||
text: (v as? String)
|
||||
isUsedAsExpression: true
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun test(v: Any?) {
|
||||
(<expr>v as? String</expr>)?.length
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: BINARY_WITH_TYPE
|
||||
text: v as? String
|
||||
isUsedAsExpression: true
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun test(v: Any?) {
|
||||
(v as? String)?.<expr>let {
|
||||
it.length
|
||||
}</expr>
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
expression: CALL_EXPRESSION
|
||||
text: let {
|
||||
it.length
|
||||
}
|
||||
isUsedAsExpression: false
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun test(v: Any?) {
|
||||
(v as? String)?.let <expr>{
|
||||
it.length
|
||||
}</expr>
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
expression: LAMBDA_EXPRESSION
|
||||
text: {
|
||||
it.length
|
||||
}
|
||||
isUsedAsExpression: true
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun test(v: Any?) {
|
||||
val x = (v as? String)?.let <expr>{
|
||||
it.length
|
||||
}</expr>
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
expression: LAMBDA_EXPRESSION
|
||||
text: {
|
||||
it.length
|
||||
}
|
||||
isUsedAsExpression: true
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun test(v: Any?) {
|
||||
(v as? String)?.let { <expr>it</expr> ->
|
||||
it.length
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: VALUE_PARAMETER
|
||||
text: it
|
||||
isUsedAsExpression: false
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun test(v: Any?) {
|
||||
(v as? String)?.let{
|
||||
<expr>it</expr>.length
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: REFERENCE_EXPRESSION
|
||||
text: it
|
||||
isUsedAsExpression: true
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun test(v: Any?) {
|
||||
(v as? String)?.<expr>let</expr> {
|
||||
it.length
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: REFERENCE_EXPRESSION
|
||||
text: let
|
||||
isUsedAsExpression: false
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun test(v: Any?) {
|
||||
val x = (v as? String)?.<expr>let {
|
||||
it.length
|
||||
}</expr>
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
expression: CALL_EXPRESSION
|
||||
text: let {
|
||||
it.length
|
||||
}
|
||||
isUsedAsExpression: true
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun test(v: Any?) {
|
||||
(v as <expr>String</expr>).length
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: REFERENCE_EXPRESSION
|
||||
text: String
|
||||
isUsedAsExpression: false
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun test(v: Any?) {
|
||||
val x = <expr>(v as String)</expr>.length
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: PARENTHESIZED
|
||||
text: (v as String)
|
||||
isUsedAsExpression: true
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
class C {
|
||||
<expr>val names: List<String> // clearly tells the type of the property upfront
|
||||
field: MutableList<String> = mutableListOf()</expr>
|
||||
}
|
||||
analysis/analysis-api/testData/components/expressionInfoProvider/isUsedAsExpression/backingField.txt
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
expression: PROPERTY
|
||||
text: val names: List<String> // clearly tells the type of the property upfront
|
||||
field: MutableList<String> = mutableListOf()
|
||||
isUsedAsExpression: false
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
class C {
|
||||
val names: List<String> // clearly tells the type of the property upfront
|
||||
<expr>field: MutableList<String> = mutableListOf()</expr>
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: BACKING_FIELD
|
||||
text: field: MutableList<String> = mutableListOf()
|
||||
isUsedAsExpression: false
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
class C {
|
||||
val names: List<String> // clearly tells the type of the property upfront
|
||||
field: MutableList<String> = <expr>mutableListOf()</expr>
|
||||
}
|
||||
|
||||
// IGNORE_FE10
|
||||
// RHS of initializer is deemed unused by FE1.0
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
expression: CALL_EXPRESSION
|
||||
text: mutableListOf()
|
||||
isUsedAsExpression: true
|
||||
analysis/analysis-api/testData/components/expressionInfoProvider/isUsedAsExpression/boolean_else.txt
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
expression: WHEN
|
||||
text: when(b) {
|
||||
else -> {}
|
||||
}
|
||||
isUsedAsExpression: false
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
fun test(b: Boolean) {
|
||||
while (true) {
|
||||
if (<expr>break</expr>) {
|
||||
"OK"
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
expression: BREAK
|
||||
text: break
|
||||
isUsedAsExpression: false
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
fun test(b: Boolean): Int {
|
||||
val n: Int = <expr>b</expr>.hashCode()
|
||||
return n * 2
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user