1/2 analysis-api: Initial implementation for contracts

Review: https://jetbrains.team/p/kt/reviews/7652

I need this API for KTIJ-22692
This commit is contained in:
Nikita Bobko
2022-11-04 20:40:14 +01:00
parent 6e637e94ca
commit b2397f033b
70 changed files with 2610 additions and 4 deletions
@@ -0,0 +1,124 @@
/*
* 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.fir.contracts
import org.jetbrains.kotlin.analysis.api.contracts.description.*
import org.jetbrains.kotlin.analysis.api.contracts.description.KtContractConstantValue.KtContractConstantType
import org.jetbrains.kotlin.analysis.api.contracts.description.KtContractReturnsContractEffectDeclaration.*
import org.jetbrains.kotlin.analysis.api.contracts.description.booleans.*
import org.jetbrains.kotlin.analysis.api.fir.KtSymbolByFirBuilder
import org.jetbrains.kotlin.analysis.api.fir.symbols.KtFirFunctionSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtParameterSymbol
import org.jetbrains.kotlin.fir.contracts.description.*
import org.jetbrains.kotlin.fir.expressions.LogicOperationKind
internal fun ConeEffectDeclaration.coneEffectDeclarationToAnalysisApi(
builder: KtSymbolByFirBuilder,
firFunctionSymbol: KtFirFunctionSymbol
): KtContractEffectDeclaration =
accept(ConeContractDescriptionElementToAnalysisApi(builder, firFunctionSymbol), Unit) as KtContractEffectDeclaration
private class ConeContractDescriptionElementToAnalysisApi(
private val builder: KtSymbolByFirBuilder,
private val firFunctionSymbol: KtFirFunctionSymbol
) : ConeContractDescriptionVisitor<Any, Unit>() {
override fun visitConditionalEffectDeclaration(
conditionalEffect: ConeConditionalEffectDeclaration,
data: Unit
): KtContractConditionalContractEffectDeclaration = KtContractConditionalContractEffectDeclaration(
conditionalEffect.effect.accept(),
conditionalEffect.condition.accept()
)
override fun visitReturnsEffectDeclaration(
returnsEffect: ConeReturnsEffectDeclaration,
data: Unit
): KtContractReturnsContractEffectDeclaration =
when (val value = returnsEffect.value) {
ConeConstantReference.NULL ->
KtContractReturnsSpecificValueEffectDeclaration(KtContractConstantValue(KtContractConstantType.NULL, builder.token))
ConeConstantReference.NOT_NULL -> KtContractReturnsNotNullEffectDeclaration(builder.token)
ConeConstantReference.WILDCARD -> KtContractReturnsSuccessfullyEffectDeclaration(builder.token)
is ConeBooleanConstantReference -> KtContractReturnsSpecificValueEffectDeclaration(
KtContractConstantValue(
when (value) {
ConeBooleanConstantReference.TRUE -> KtContractConstantType.TRUE
ConeBooleanConstantReference.FALSE -> KtContractConstantType.FALSE
else -> error("Can't convert $value to the Analysis API")
},
builder.token
)
)
else -> error("Can't convert $returnsEffect to the Analysis API")
}
override fun visitCallsEffectDeclaration(callsEffect: ConeCallsEffectDeclaration, data: Unit): KtContractCallsInPlaceContractEffectDeclaration =
KtContractCallsInPlaceContractEffectDeclaration(
callsEffect.valueParameterReference.accept(),
callsEffect.kind
)
override fun visitLogicalBinaryOperationContractExpression(
binaryLogicExpression: ConeBinaryLogicExpression,
data: Unit
): KtContractBinaryLogicExpression = KtContractBinaryLogicExpression(
binaryLogicExpression.left.accept(),
binaryLogicExpression.right.accept(),
when (binaryLogicExpression.kind) {
LogicOperationKind.AND -> KtContractBinaryLogicExpression.KtLogicOperation.AND
LogicOperationKind.OR -> KtContractBinaryLogicExpression.KtLogicOperation.OR
}
)
override fun visitLogicalNot(logicalNot: ConeLogicalNot, data: Unit): KtContractLogicalNotExpression =
KtContractLogicalNotExpression(logicalNot.arg.accept())
override fun visitIsInstancePredicate(isInstancePredicate: ConeIsInstancePredicate, data: Unit): KtContractIsInstancePredicateExpression =
KtContractIsInstancePredicateExpression(
isInstancePredicate.arg.accept(),
builder.typeBuilder.buildKtType(isInstancePredicate.type),
isInstancePredicate.isNegated
)
override fun visitIsNullPredicate(isNullPredicate: ConeIsNullPredicate, data: Unit): KtContractIsNullPredicateExpression =
KtContractIsNullPredicateExpression(isNullPredicate.arg.accept(), isNullPredicate.isNegated)
override fun visitBooleanConstantDescriptor(
booleanConstantDescriptor: ConeBooleanConstantReference,
data: Unit
): KtContractBooleanConstantExpression =
when (booleanConstantDescriptor) {
ConeBooleanConstantReference.TRUE -> KtContractBooleanConstantExpression(true, builder.token)
ConeBooleanConstantReference.FALSE -> KtContractBooleanConstantExpression(false, builder.token)
else -> error("Can't convert $booleanConstantDescriptor to Analysis API")
}
override fun visitValueParameterReference(
valueParameterReference: ConeValueParameterReference,
data: Unit
): Any = visitValueParameterReference(valueParameterReference, ::KtContractParameterValue)
override fun visitBooleanValueParameterReference(
booleanValueParameterReference: ConeBooleanValueParameterReference,
data: Unit
): Any =
visitValueParameterReference(booleanValueParameterReference, ::KtContractBooleanValueParameterExpression)
private fun <T> visitValueParameterReference(
valueParameterReference: ConeValueParameterReference,
constructor: (KtParameterSymbol) -> T
): T = constructor(
if (valueParameterReference.parameterIndex == -1) firFunctionSymbol.receiverParameter
?: error("$firFunctionSymbol should contain a receiver")
else firFunctionSymbol.valueParameters[valueParameterReference.parameterIndex]
)
// Util function to avoid hard coding names of the classes. Type inference will do a better job figuring out the best type to cast to.
// This visitor isn't type-safe anyway
private inline fun <reified T> ConeContractDescriptionElement.accept() =
accept(this@ConeContractDescriptionElementToAnalysisApi, Unit) as T
}
@@ -8,8 +8,10 @@ package org.jetbrains.kotlin.analysis.api.fir.symbols
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.base.KtContextReceiver
import org.jetbrains.kotlin.analysis.api.contracts.description.KtContractEffectDeclaration
import org.jetbrains.kotlin.analysis.api.fir.KtSymbolByFirBuilder
import org.jetbrains.kotlin.analysis.api.fir.annotations.KtFirAnnotationListForDeclaration
import org.jetbrains.kotlin.analysis.api.fir.contracts.coneEffectDeclarationToAnalysisApi
import org.jetbrains.kotlin.analysis.api.fir.findPsi
import org.jetbrains.kotlin.analysis.api.fir.symbols.pointers.FirCallableSignature
import org.jetbrains.kotlin.analysis.api.fir.symbols.pointers.KtFirMemberFunctionSymbolPointer
@@ -32,6 +34,7 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LLFirResolveSession
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.fir.containingClassLookupTag
import org.jetbrains.kotlin.fir.contracts.FirEffectDeclaration
import org.jetbrains.kotlin.fir.declarations.utils.*
import org.jetbrains.kotlin.fir.resolve.getHasStableParameterNames
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
@@ -51,6 +54,13 @@ internal class KtFirFunctionSymbol(
override val isBuiltinFunctionInvoke: Boolean
get() = withValidityAssertion { callableIdIfNonLocal in kotlinFunctionInvokeCallableIds }
override val contractEffects: List<KtContractEffectDeclaration> by cached {
firSymbol.resolvedContractDescription?.effects
?.map(FirEffectDeclaration::effect)
?.map { it.coneEffectDeclarationToAnalysisApi(builder, this) }
.orEmpty()
}
override val returnType: KtType get() = withValidityAssertion { firSymbol.returnType(builder) }
override val receiverParameter: KtReceiverParameterSymbol? get() = withValidityAssertion { firSymbol.receiver(builder) }
override val contextReceivers: List<KtContextReceiver> by cached { firSymbol.createContextReceivers(builder) }
@@ -94,6 +94,118 @@ public class FirIdeNormalAnalysisSourceModuleSingleSymbolByPsiGenerated extends
runTest("analysis/analysis-api/testData/symbols/singleSymbolByPsi/setterWithAnnotations.kt");
}
@Nested
@TestMetadata("analysis/analysis-api/testData/symbols/singleSymbolByPsi/contracts")
@TestDataPath("$PROJECT_ROOT")
public class Contracts {
@Test
public void testAllFilesPresentInContracts() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/symbols/singleSymbolByPsi/contracts"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@Test
@TestMetadata("booleanConstReferenceInImplies.kt")
public void testBooleanConstReferenceInImplies() throws Exception {
runTest("analysis/analysis-api/testData/symbols/singleSymbolByPsi/contracts/booleanConstReferenceInImplies.kt");
}
@Test
@TestMetadata("booleanExprContract.kt")
public void testBooleanExprContract() throws Exception {
runTest("analysis/analysis-api/testData/symbols/singleSymbolByPsi/contracts/booleanExprContract.kt");
}
@Test
@TestMetadata("callsInPlaceAtLeastOnceContract.kt")
public void testCallsInPlaceAtLeastOnceContract() throws Exception {
runTest("analysis/analysis-api/testData/symbols/singleSymbolByPsi/contracts/callsInPlaceAtLeastOnceContract.kt");
}
@Test
@TestMetadata("callsInPlaceAtMostOnceContract.kt")
public void testCallsInPlaceAtMostOnceContract() throws Exception {
runTest("analysis/analysis-api/testData/symbols/singleSymbolByPsi/contracts/callsInPlaceAtMostOnceContract.kt");
}
@Test
@TestMetadata("callsInPlaceExactlyOnceContract.kt")
public void testCallsInPlaceExactlyOnceContract() throws Exception {
runTest("analysis/analysis-api/testData/symbols/singleSymbolByPsi/contracts/callsInPlaceExactlyOnceContract.kt");
}
@Test
@TestMetadata("callsInPlaceUnknownContract.kt")
public void testCallsInPlaceUnknownContract() throws Exception {
runTest("analysis/analysis-api/testData/symbols/singleSymbolByPsi/contracts/callsInPlaceUnknownContract.kt");
}
@Test
@TestMetadata("invalidContractParameterPassedToReturns.kt")
public void testInvalidContractParameterPassedToReturns() throws Exception {
runTest("analysis/analysis-api/testData/symbols/singleSymbolByPsi/contracts/invalidContractParameterPassedToReturns.kt");
}
@Test
@TestMetadata("isInstancePredicateContract.kt")
public void testIsInstancePredicateContract() throws Exception {
runTest("analysis/analysis-api/testData/symbols/singleSymbolByPsi/contracts/isInstancePredicateContract.kt");
}
@Test
@TestMetadata("logicalNotContract.kt")
public void testLogicalNotContract() throws Exception {
runTest("analysis/analysis-api/testData/symbols/singleSymbolByPsi/contracts/logicalNotContract.kt");
}
@Test
@TestMetadata("referenceBooleanReceiverInContract.kt")
public void testReferenceBooleanReceiverInContract() throws Exception {
runTest("analysis/analysis-api/testData/symbols/singleSymbolByPsi/contracts/referenceBooleanReceiverInContract.kt");
}
@Test
@TestMetadata("referenceReceiverInContract.kt")
public void testReferenceReceiverInContract() throws Exception {
runTest("analysis/analysis-api/testData/symbols/singleSymbolByPsi/contracts/referenceReceiverInContract.kt");
}
@Test
@TestMetadata("returnsContract.kt")
public void testReturnsContract() throws Exception {
runTest("analysis/analysis-api/testData/symbols/singleSymbolByPsi/contracts/returnsContract.kt");
}
@Test
@TestMetadata("returnsFalseContract.kt")
public void testReturnsFalseContract() throws Exception {
runTest("analysis/analysis-api/testData/symbols/singleSymbolByPsi/contracts/returnsFalseContract.kt");
}
@Test
@TestMetadata("returnsNotNullContract.kt")
public void testReturnsNotNullContract() throws Exception {
runTest("analysis/analysis-api/testData/symbols/singleSymbolByPsi/contracts/returnsNotNullContract.kt");
}
@Test
@TestMetadata("returnsNullContract.kt")
public void testReturnsNullContract() throws Exception {
runTest("analysis/analysis-api/testData/symbols/singleSymbolByPsi/contracts/returnsNullContract.kt");
}
@Test
@TestMetadata("returnsTrueContract.kt")
public void testReturnsTrueContract() throws Exception {
runTest("analysis/analysis-api/testData/symbols/singleSymbolByPsi/contracts/returnsTrueContract.kt");
}
@Test
@TestMetadata("twoContracts.kt")
public void testTwoContracts() throws Exception {
runTest("analysis/analysis-api/testData/symbols/singleSymbolByPsi/contracts/twoContracts.kt");
}
}
@Nested
@TestMetadata("analysis/analysis-api/testData/symbols/singleSymbolByPsi/errors")
@TestDataPath("$PROJECT_ROOT")