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,109 @@
/*
* 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.descriptors.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.descriptors.Fe10AnalysisContext
import org.jetbrains.kotlin.analysis.api.descriptors.symbols.descriptorBased.base.toKtSymbol
import org.jetbrains.kotlin.analysis.api.descriptors.symbols.descriptorBased.base.toKtType
import org.jetbrains.kotlin.analysis.api.symbols.KtParameterSymbol
import org.jetbrains.kotlin.contracts.description.*
import org.jetbrains.kotlin.contracts.description.expressions.*
internal fun EffectDeclaration.effectDeclarationToAnalysisApi(analysisContext: Fe10AnalysisContext): KtContractEffectDeclaration =
accept(ContractDescriptionElementToAnalysisApi(analysisContext), Unit) as KtContractEffectDeclaration
private class ContractDescriptionElementToAnalysisApi(val analysisContext: Fe10AnalysisContext) :
ContractDescriptionVisitor<Any, Unit> {
override fun visitConditionalEffectDeclaration(
conditionalEffect: ConditionalEffectDeclaration,
data: Unit
): Any = KtContractConditionalContractEffectDeclaration(
conditionalEffect.effect.accept(),
conditionalEffect.condition.accept(),
)
override fun visitReturnsEffectDeclaration(
returnsEffect: ReturnsEffectDeclaration,
data: Unit
): KtContractReturnsContractEffectDeclaration =
when (val value = returnsEffect.value) {
ConstantReference.NULL ->
KtContractReturnsSpecificValueEffectDeclaration(KtContractConstantValue(KtContractConstantType.NULL, analysisContext.token))
ConstantReference.NOT_NULL -> KtContractReturnsNotNullEffectDeclaration(analysisContext.token)
ConstantReference.WILDCARD -> KtContractReturnsSuccessfullyEffectDeclaration(analysisContext.token)
is BooleanConstantReference -> KtContractReturnsSpecificValueEffectDeclaration(
KtContractConstantValue(
when (value) {
BooleanConstantReference.TRUE -> KtContractConstantType.TRUE
BooleanConstantReference.FALSE -> KtContractConstantType.FALSE
else -> error("Can't convert $value to the Analysis API")
},
analysisContext.token
)
)
else -> error("Can't convert $returnsEffect to the Analysis API")
}
override fun visitCallsEffectDeclaration(callsEffect: CallsEffectDeclaration, data: Unit): Any =
KtContractCallsInPlaceContractEffectDeclaration(callsEffect.variableReference.accept(), callsEffect.kind)
override fun visitLogicalOr(logicalOr: LogicalOr, data: Unit): Any = KtContractBinaryLogicExpression(
logicalOr.left.accept(),
logicalOr.right.accept(),
KtContractBinaryLogicExpression.KtLogicOperation.OR
)
override fun visitLogicalAnd(logicalAnd: LogicalAnd, data: Unit): Any = KtContractBinaryLogicExpression(
logicalAnd.left.accept(),
logicalAnd.right.accept(),
KtContractBinaryLogicExpression.KtLogicOperation.AND
)
override fun visitLogicalNot(logicalNot: LogicalNot, data: Unit): Any =
KtContractLogicalNotExpression(logicalNot.arg.accept())
override fun visitIsInstancePredicate(isInstancePredicate: IsInstancePredicate, data: Unit): Any =
KtContractIsInstancePredicateExpression(
isInstancePredicate.arg.accept(),
isInstancePredicate.type.toKtType(analysisContext),
isInstancePredicate.isNegated
)
override fun visitIsNullPredicate(isNullPredicate: IsNullPredicate, data: Unit): Any =
KtContractIsNullPredicateExpression(isNullPredicate.arg.accept(), isNullPredicate.isNegated)
override fun visitBooleanConstantDescriptor(
booleanConstantDescriptor: BooleanConstantReference,
data: Unit
): KtContractBooleanConstantExpression =
when (booleanConstantDescriptor) {
BooleanConstantReference.TRUE -> KtContractBooleanConstantExpression(true, analysisContext.token)
BooleanConstantReference.FALSE -> KtContractBooleanConstantExpression(false, analysisContext.token)
else -> error("Can't convert $booleanConstantDescriptor to Analysis API")
}
override fun visitVariableReference(variableReference: VariableReference, data: Unit): Any =
visitVariableReference(variableReference, ::KtContractParameterValue)
override fun visitBooleanVariableReference(
booleanVariableReference: BooleanVariableReference,
data: Unit
): Any = visitVariableReference(booleanVariableReference, ::KtContractBooleanValueParameterExpression)
private fun <T> visitVariableReference(
variableReference: VariableReference,
constructor: (KtParameterSymbol) -> T
): T = constructor(variableReference.descriptor.toKtSymbol(analysisContext) as KtParameterSymbol)
// 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> ContractDescriptionElement.accept() = accept(this@ContractDescriptionElementToAnalysisApi, Unit) as T
}
@@ -7,12 +7,15 @@ package org.jetbrains.kotlin.analysis.api.descriptors.symbols.descriptorBased
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.descriptors.Fe10AnalysisContext
import org.jetbrains.kotlin.analysis.api.descriptors.contracts.effectDeclarationToAnalysisApi
import org.jetbrains.kotlin.analysis.api.descriptors.symbols.calculateHashCode
import org.jetbrains.kotlin.analysis.api.descriptors.symbols.descriptorBased.base.*
import org.jetbrains.kotlin.analysis.api.descriptors.symbols.isEqualTo
import org.jetbrains.kotlin.analysis.api.descriptors.symbols.pointers.KtFe10DescFunctionLikeSymbolPointer
import org.jetbrains.kotlin.analysis.api.descriptors.symbols.pointers.KtFe10NeverRestoringSymbolPointer
import org.jetbrains.kotlin.analysis.api.descriptors.utils.cached
import org.jetbrains.kotlin.analysis.api.impl.base.util.kotlinFunctionInvokeCallableIds
import org.jetbrains.kotlin.analysis.api.lifetime.withValidityAssertion
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol
@@ -23,6 +26,7 @@ import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolKind
import org.jetbrains.kotlin.analysis.api.symbols.pointers.KtPsiBasedSymbolPointer
import org.jetbrains.kotlin.analysis.api.symbols.pointers.KtSymbolPointer
import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.contracts.description.ContractProviderKey
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor
import org.jetbrains.kotlin.name.CallableId
@@ -38,6 +42,12 @@ internal class KtFe10DescFunctionSymbol private constructor(
override val name: Name
get() = withValidityAssertion { descriptor.name }
override val contractEffects: List<KtContractEffectDeclaration> by cached {
descriptor.getUserData(ContractProviderKey)?.getContractDescription()?.effects
?.map { it.effectDeclarationToAnalysisApi(analysisContext) }
.orEmpty()
}
override val symbolKind: KtSymbolKind
get() = withValidityAssertion { descriptor.ktSymbolKind }
@@ -112,4 +122,4 @@ internal class KtFe10DescFunctionSymbol private constructor(
return KtFe10DescFunctionSymbol(descriptor, analysisContext)
}
}
}
}
@@ -7,13 +7,16 @@ package org.jetbrains.kotlin.analysis.api.descriptors.symbols.psiBased
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.descriptors.Fe10AnalysisContext
import org.jetbrains.kotlin.analysis.api.descriptors.Fe10AnalysisFacade.AnalysisMode
import org.jetbrains.kotlin.analysis.api.descriptors.symbols.calculateHashCode
import org.jetbrains.kotlin.analysis.api.descriptors.contracts.effectDeclarationToAnalysisApi
import org.jetbrains.kotlin.analysis.api.descriptors.symbols.descriptorBased.base.*
import org.jetbrains.kotlin.analysis.api.descriptors.symbols.isEqualTo
import org.jetbrains.kotlin.analysis.api.descriptors.symbols.pointers.KtFe10NeverRestoringSymbolPointer
import org.jetbrains.kotlin.analysis.api.descriptors.symbols.psiBased.base.*
import org.jetbrains.kotlin.analysis.api.descriptors.utils.ValidityAwareCachedValue
import org.jetbrains.kotlin.analysis.api.descriptors.utils.cached
import org.jetbrains.kotlin.analysis.api.impl.base.util.kotlinFunctionInvokeCallableIds
import org.jetbrains.kotlin.analysis.api.lifetime.withValidityAssertion
@@ -25,6 +28,8 @@ import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolKind
import org.jetbrains.kotlin.analysis.api.symbols.pointers.KtPsiBasedSymbolPointer
import org.jetbrains.kotlin.analysis.api.symbols.pointers.KtSymbolPointer
import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.contracts.description.ContractProviderKey
import org.jetbrains.kotlin.contracts.description.EffectDeclaration
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities
@@ -35,6 +40,7 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.isExtensionDeclaration
import org.jetbrains.kotlin.resolve.BindingContext
import kotlin.reflect.KProperty
internal class KtFe10PsiFunctionSymbol(
override val psi: KtNamedFunction,
@@ -45,6 +51,12 @@ internal class KtFe10PsiFunctionSymbol(
bindingContext[BindingContext.FUNCTION, psi]
}
override val contractEffects: List<KtContractEffectDeclaration> by cached {
descriptor?.getUserData(ContractProviderKey)?.getContractDescription()?.effects
?.map { it.effectDeclarationToAnalysisApi(analysisContext) }
.orEmpty()
}
override val isSuspend: Boolean
get() = withValidityAssertion { psi.hasModifier(KtTokens.SUSPEND_KEYWORD) }
@@ -9,11 +9,11 @@ import org.jetbrains.kotlin.analysis.api.KtTypeProjection
import org.jetbrains.kotlin.analysis.api.descriptors.Fe10AnalysisContext
import org.jetbrains.kotlin.analysis.api.descriptors.symbols.descriptorBased.base.ktNullability
import org.jetbrains.kotlin.analysis.api.descriptors.symbols.descriptorBased.base.toKtTypeProjection
import org.jetbrains.kotlin.analysis.api.lifetime.withValidityAssertion
import org.jetbrains.kotlin.analysis.api.descriptors.types.base.KtFe10Type
import org.jetbrains.kotlin.analysis.api.descriptors.types.base.asStringForDebugging
import org.jetbrains.kotlin.analysis.api.types.KtCapturedType
import org.jetbrains.kotlin.analysis.api.types.KtTypeNullability
import org.jetbrains.kotlin.analysis.api.lifetime.withValidityAssertion
import org.jetbrains.kotlin.resolve.calls.inference.CapturedType
internal class KtFe10CapturedType(
@@ -94,6 +94,118 @@ public class Fe10IdeNormalAnalysisSourceModuleSingleSymbolByPsiGenerated 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")