[cls] write contracts information to cls stubs

^ KTIJ-24665
this information would be used to create resolved FirElements from stubs,
so no ProtoBuf would be kept in memory
This commit is contained in:
Anna Kozlova
2023-04-05 17:12:30 +02:00
committed by Space Team
parent 9d4db72d72
commit 4fe239375f
44 changed files with 1182 additions and 534 deletions
@@ -0,0 +1,77 @@
// JVM_FILE_NAME: ContractsKt
@file:OptIn(ExperimentalContracts::class)
package test
import kotlin.contracts.*
fun myRequire(x: Boolean) {
contract {
returns() implies x
}
}
fun <R> call_InPlace(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
fun isNull(obj: Any?): Boolean {
contract {
returns(true) implies (obj != null)
}
return obj != null
}
fun isNotNull(foo: Any?): Any? {
contract {
returnsNotNull() implies (foo != null)
}
return foo
}
fun isString(foo: Any?): String? {
contract {
returnsNotNull() implies (foo is String)
}
return foo as? String
}
fun isNotString(foo: Any?): String? {
contract {
returnsNotNull() implies (foo !is String)
}
return if (foo is String) null else "not a string"
}
fun String?.asSafe(): String? {
contract {
returnsNotNull() implies (this@asSafe != null)
}
return this
}
fun isStringCheck(x: Any?): Any? {
contract {
returns(true) implies (x is Comparable<*> || x is CharSequence)
}
return x is String
}
fun isStringOrNumber(x: Any?): Any? {
contract {
returns(true) implies (x is Comparable<*> && (x is CharSequence || x is Number))
}
return x is String || x is Int
}
inline fun <reified T : Number> T?.test0(): Boolean {
contract {
returns(true) implies (this@test0 is T)
}
return this is T
}
@@ -0,0 +1,173 @@
PsiJetFileStubImpl[package=test]
KotlinStub$PACKAGE_DIRECTIVE
KotlinStub$REFERENCE_EXPRESSION[referencedName=test]
KotlinStub$IMPORT_LIST
KotlinStub$FUN[fqName=test.call_InPlace, hasBlockBody=true, hasBody=true, hasTypeParameterListBeforeFunctionName=true, isExtension=false, isTopLevel=true, mayHaveContract=true, name=call_InPlace]
effect:CallsInPlace(param(0), EXACTLY_ONCE)
KotlinStub$MODIFIER_LIST[public]
KotlinStub$TYPE_PARAMETER_LIST
KotlinStub$TYPE_PARAMETER[fqName=null, isInVariance=false, isOutVariance=false, name=R]
KotlinStub$VALUE_PARAMETER_LIST
KotlinStub$VALUE_PARAMETER[fqName=null, hasDefaultValue=false, hasValOrVar=false, isMutable=false, name=block]
KotlinStub$TYPE_REFERENCE
KotlinStub$FUNCTION_TYPE
KotlinStub$VALUE_PARAMETER_LIST
KotlinStub$TYPE_REFERENCE
KotlinStub$USER_TYPE
KotlinStub$REFERENCE_EXPRESSION[referencedName=R]
KotlinStub$TYPE_REFERENCE
KotlinStub$USER_TYPE
KotlinStub$REFERENCE_EXPRESSION[referencedName=R]
KotlinStub$FUN[fqName=test.isNotNull, hasBlockBody=true, hasBody=true, hasTypeParameterListBeforeFunctionName=false, isExtension=false, isTopLevel=true, mayHaveContract=true, name=isNotNull]
effect:Returns(NOT_NULL) -> param(0) != null
KotlinStub$MODIFIER_LIST[public]
KotlinStub$VALUE_PARAMETER_LIST
KotlinStub$VALUE_PARAMETER[fqName=null, hasDefaultValue=false, hasValOrVar=false, isMutable=false, name=foo]
KotlinStub$TYPE_REFERENCE
KotlinStub$NULLABLE_TYPE
KotlinStub$USER_TYPE
KotlinStub$USER_TYPE
KotlinStub$REFERENCE_EXPRESSION[referencedName=kotlin]
KotlinStub$REFERENCE_EXPRESSION[referencedName=Any]
KotlinStub$TYPE_REFERENCE
KotlinStub$NULLABLE_TYPE
KotlinStub$USER_TYPE
KotlinStub$USER_TYPE
KotlinStub$REFERENCE_EXPRESSION[referencedName=kotlin]
KotlinStub$REFERENCE_EXPRESSION[referencedName=Any]
KotlinStub$FUN[fqName=test.isNotString, hasBlockBody=true, hasBody=true, hasTypeParameterListBeforeFunctionName=false, isExtension=false, isTopLevel=true, mayHaveContract=true, name=isNotString]
effect:Returns(NOT_NULL) -> param(0) !is KotlinClassTypeBean(classId=kotlin/String, arguments=[], nullable=false)
KotlinStub$MODIFIER_LIST[public]
KotlinStub$VALUE_PARAMETER_LIST
KotlinStub$VALUE_PARAMETER[fqName=null, hasDefaultValue=false, hasValOrVar=false, isMutable=false, name=foo]
KotlinStub$TYPE_REFERENCE
KotlinStub$NULLABLE_TYPE
KotlinStub$USER_TYPE
KotlinStub$USER_TYPE
KotlinStub$REFERENCE_EXPRESSION[referencedName=kotlin]
KotlinStub$REFERENCE_EXPRESSION[referencedName=Any]
KotlinStub$TYPE_REFERENCE
KotlinStub$NULLABLE_TYPE
KotlinStub$USER_TYPE
KotlinStub$USER_TYPE
KotlinStub$REFERENCE_EXPRESSION[referencedName=kotlin]
KotlinStub$REFERENCE_EXPRESSION[referencedName=String]
KotlinStub$FUN[fqName=test.isNull, hasBlockBody=true, hasBody=true, hasTypeParameterListBeforeFunctionName=false, isExtension=false, isTopLevel=true, mayHaveContract=true, name=isNull]
effect:Returns(TRUE) -> param(0) != null
KotlinStub$MODIFIER_LIST[public]
KotlinStub$VALUE_PARAMETER_LIST
KotlinStub$VALUE_PARAMETER[fqName=null, hasDefaultValue=false, hasValOrVar=false, isMutable=false, name=obj]
KotlinStub$TYPE_REFERENCE
KotlinStub$NULLABLE_TYPE
KotlinStub$USER_TYPE
KotlinStub$USER_TYPE
KotlinStub$REFERENCE_EXPRESSION[referencedName=kotlin]
KotlinStub$REFERENCE_EXPRESSION[referencedName=Any]
KotlinStub$TYPE_REFERENCE
KotlinStub$USER_TYPE
KotlinStub$USER_TYPE
KotlinStub$REFERENCE_EXPRESSION[referencedName=kotlin]
KotlinStub$REFERENCE_EXPRESSION[referencedName=Boolean]
KotlinStub$FUN[fqName=test.isString, hasBlockBody=true, hasBody=true, hasTypeParameterListBeforeFunctionName=false, isExtension=false, isTopLevel=true, mayHaveContract=true, name=isString]
effect:Returns(NOT_NULL) -> param(0) is KotlinClassTypeBean(classId=kotlin/String, arguments=[], nullable=false)
KotlinStub$MODIFIER_LIST[public]
KotlinStub$VALUE_PARAMETER_LIST
KotlinStub$VALUE_PARAMETER[fqName=null, hasDefaultValue=false, hasValOrVar=false, isMutable=false, name=foo]
KotlinStub$TYPE_REFERENCE
KotlinStub$NULLABLE_TYPE
KotlinStub$USER_TYPE
KotlinStub$USER_TYPE
KotlinStub$REFERENCE_EXPRESSION[referencedName=kotlin]
KotlinStub$REFERENCE_EXPRESSION[referencedName=Any]
KotlinStub$TYPE_REFERENCE
KotlinStub$NULLABLE_TYPE
KotlinStub$USER_TYPE
KotlinStub$USER_TYPE
KotlinStub$REFERENCE_EXPRESSION[referencedName=kotlin]
KotlinStub$REFERENCE_EXPRESSION[referencedName=String]
KotlinStub$FUN[fqName=test.isStringCheck, hasBlockBody=true, hasBody=true, hasTypeParameterListBeforeFunctionName=false, isExtension=false, isTopLevel=true, mayHaveContract=true, name=isStringCheck]
effect:Returns(TRUE) -> param(0) is KotlinClassTypeBean(classId=kotlin/Comparable, arguments=[KotlinTypeArgumentBean(projectionKind=STAR, type=null)], nullable=false) || param(0) is KotlinClassTypeBean(classId=kotlin/CharSequence, arguments=[], nullable=false)
KotlinStub$MODIFIER_LIST[public]
KotlinStub$VALUE_PARAMETER_LIST
KotlinStub$VALUE_PARAMETER[fqName=null, hasDefaultValue=false, hasValOrVar=false, isMutable=false, name=x]
KotlinStub$TYPE_REFERENCE
KotlinStub$NULLABLE_TYPE
KotlinStub$USER_TYPE
KotlinStub$USER_TYPE
KotlinStub$REFERENCE_EXPRESSION[referencedName=kotlin]
KotlinStub$REFERENCE_EXPRESSION[referencedName=Any]
KotlinStub$TYPE_REFERENCE
KotlinStub$NULLABLE_TYPE
KotlinStub$USER_TYPE
KotlinStub$USER_TYPE
KotlinStub$REFERENCE_EXPRESSION[referencedName=kotlin]
KotlinStub$REFERENCE_EXPRESSION[referencedName=Any]
KotlinStub$FUN[fqName=test.isStringOrNumber, hasBlockBody=true, hasBody=true, hasTypeParameterListBeforeFunctionName=false, isExtension=false, isTopLevel=true, mayHaveContract=true, name=isStringOrNumber]
effect:Returns(TRUE) -> param(0) is KotlinClassTypeBean(classId=kotlin/Comparable, arguments=[KotlinTypeArgumentBean(projectionKind=STAR, type=null)], nullable=false) && param(0) is KotlinClassTypeBean(classId=kotlin/CharSequence, arguments=[], nullable=false) || param(0) is KotlinClassTypeBean(classId=kotlin/Number, arguments=[], nullable=false)
KotlinStub$MODIFIER_LIST[public]
KotlinStub$VALUE_PARAMETER_LIST
KotlinStub$VALUE_PARAMETER[fqName=null, hasDefaultValue=false, hasValOrVar=false, isMutable=false, name=x]
KotlinStub$TYPE_REFERENCE
KotlinStub$NULLABLE_TYPE
KotlinStub$USER_TYPE
KotlinStub$USER_TYPE
KotlinStub$REFERENCE_EXPRESSION[referencedName=kotlin]
KotlinStub$REFERENCE_EXPRESSION[referencedName=Any]
KotlinStub$TYPE_REFERENCE
KotlinStub$NULLABLE_TYPE
KotlinStub$USER_TYPE
KotlinStub$USER_TYPE
KotlinStub$REFERENCE_EXPRESSION[referencedName=kotlin]
KotlinStub$REFERENCE_EXPRESSION[referencedName=Any]
KotlinStub$FUN[fqName=test.myRequire, hasBlockBody=true, hasBody=true, hasTypeParameterListBeforeFunctionName=false, isExtension=false, isTopLevel=true, mayHaveContract=true, name=myRequire]
effect:Returns(WILDCARD) -> param(0)
KotlinStub$MODIFIER_LIST[public]
KotlinStub$VALUE_PARAMETER_LIST
KotlinStub$VALUE_PARAMETER[fqName=null, hasDefaultValue=false, hasValOrVar=false, isMutable=false, name=x]
KotlinStub$TYPE_REFERENCE
KotlinStub$USER_TYPE
KotlinStub$USER_TYPE
KotlinStub$REFERENCE_EXPRESSION[referencedName=kotlin]
KotlinStub$REFERENCE_EXPRESSION[referencedName=Boolean]
KotlinStub$TYPE_REFERENCE
KotlinStub$USER_TYPE
KotlinStub$USER_TYPE
KotlinStub$REFERENCE_EXPRESSION[referencedName=kotlin]
KotlinStub$REFERENCE_EXPRESSION[referencedName=Unit]
KotlinStub$FUN[fqName=test.asSafe, hasBlockBody=true, hasBody=true, hasTypeParameterListBeforeFunctionName=false, isExtension=true, isTopLevel=true, mayHaveContract=true, name=asSafe]
effect:Returns(NOT_NULL) -> param(-1) != null
KotlinStub$MODIFIER_LIST[public]
KotlinStub$TYPE_REFERENCE
KotlinStub$NULLABLE_TYPE
KotlinStub$USER_TYPE
KotlinStub$USER_TYPE
KotlinStub$REFERENCE_EXPRESSION[referencedName=kotlin]
KotlinStub$REFERENCE_EXPRESSION[referencedName=String]
KotlinStub$VALUE_PARAMETER_LIST
KotlinStub$TYPE_REFERENCE
KotlinStub$NULLABLE_TYPE
KotlinStub$USER_TYPE
KotlinStub$USER_TYPE
KotlinStub$REFERENCE_EXPRESSION[referencedName=kotlin]
KotlinStub$REFERENCE_EXPRESSION[referencedName=String]
KotlinStub$FUN[fqName=test.test0, hasBlockBody=true, hasBody=true, hasTypeParameterListBeforeFunctionName=true, isExtension=true, isTopLevel=true, mayHaveContract=true, name=test0]
effect:Returns(TRUE) -> param(-1) is KotlinTypeParameterTypeBean(typeParameterName=T, nullable=false, definitelyNotNull=false)
KotlinStub$MODIFIER_LIST[public inline]
KotlinStub$TYPE_PARAMETER_LIST
KotlinStub$TYPE_PARAMETER[fqName=null, isInVariance=false, isOutVariance=false, name=T]
KotlinStub$MODIFIER_LIST[reified]
KotlinStub$TYPE_REFERENCE
KotlinStub$USER_TYPE
KotlinStub$USER_TYPE
KotlinStub$REFERENCE_EXPRESSION[referencedName=kotlin]
KotlinStub$REFERENCE_EXPRESSION[referencedName=Number]
KotlinStub$TYPE_REFERENCE
KotlinStub$NULLABLE_TYPE
KotlinStub$USER_TYPE
KotlinStub$REFERENCE_EXPRESSION[referencedName=T]
KotlinStub$VALUE_PARAMETER_LIST
KotlinStub$TYPE_REFERENCE
KotlinStub$USER_TYPE
KotlinStub$USER_TYPE
KotlinStub$REFERENCE_EXPRESSION[referencedName=kotlin]
KotlinStub$REFERENCE_EXPRESSION[referencedName=Boolean]
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.analysis.decompiler.stub.files
import com.intellij.psi.stubs.StubElement
import com.intellij.util.indexing.FileContentImpl
import org.jetbrains.kotlin.analysis.decompiler.stub.file.KotlinClsStubBuilder
import org.jetbrains.kotlin.contracts.description.*
import org.jetbrains.kotlin.psi.KtProjectionKind
import org.jetbrains.kotlin.psi.stubs.impl.*
import org.jetbrains.kotlin.test.KotlinTestUtils
@@ -25,11 +26,22 @@ abstract class AbstractAdditionalStubInfoTest : AbstractDecompiledClassTest() {
private fun extractAdditionInfo(stub: StubElement<*>, builder: StringBuilder, level: Int) {
builder.append(stub.toString())
if (stub is KotlinUserTypeStubImpl) {
val upperBound = stub.upperBound
if (upperBound != null) {
builder.append(" ft: ")
appendFlexibleTypeInfo(builder, upperBound)
when (stub) {
is KotlinUserTypeStubImpl -> {
val upperBound = stub.upperBound
if (upperBound != null) {
builder.append(" ft: ")
appendFlexibleTypeInfo(builder, upperBound)
}
}
is KotlinFunctionStubImpl -> {
val contract = stub.contract
if (contract != null) {
for (element in contract) {
builder.append("\n" + " ".repeat(level)).append("effect:")
element.accept(KotlinContractRenderer(builder), null)
}
}
}
}
for (child in stub.childrenStubs) {
@@ -77,4 +89,53 @@ abstract class AbstractAdditionalStubInfoTest : AbstractDecompiledClassTest() {
}
}
}
}
class KotlinContractRenderer(private val buffer: StringBuilder) : KtContractDescriptionVisitor<Unit, Nothing?, KotlinTypeBean, Nothing?>() {
override fun visitConditionalEffectDeclaration(conditionalEffect: KtConditionalEffectDeclaration<KotlinTypeBean, Nothing?>, data: Nothing?) {
conditionalEffect.effect.accept(this, data)
buffer.append(" -> ")
conditionalEffect.condition.accept(this, data)
}
override fun visitReturnsEffectDeclaration(returnsEffect: KtReturnsEffectDeclaration<KotlinTypeBean, Nothing?>, data: Nothing?) {
buffer.append("Returns(")
returnsEffect.value.accept(this, data)
buffer.append(")")
}
override fun visitCallsEffectDeclaration(callsEffect: KtCallsEffectDeclaration<KotlinTypeBean, Nothing?>, data: Nothing?) {
buffer.append("CallsInPlace(")
callsEffect.valueParameterReference.accept(this, data)
buffer.append(", ${callsEffect.kind})")
}
override fun visitLogicalBinaryOperationContractExpression(binaryLogicExpression: KtBinaryLogicExpression<KotlinTypeBean, Nothing?>, data: Nothing?) {
binaryLogicExpression.left.accept(this, data)
buffer.append(" ${binaryLogicExpression.kind.token} ")
binaryLogicExpression.right.accept(this, data)
}
override fun visitLogicalNot(logicalNot: KtLogicalNot<KotlinTypeBean, Nothing?>, data: Nothing?) {
logicalNot.arg.accept(this, data)
}
override fun visitIsInstancePredicate(isInstancePredicate: KtIsInstancePredicate<KotlinTypeBean, Nothing?>, data: Nothing?) {
isInstancePredicate.arg.accept(this, data)
buffer.append(" ${if (isInstancePredicate.isNegated) "!" else ""}is ${isInstancePredicate.type}")
}
override fun visitIsNullPredicate(isNullPredicate: KtIsNullPredicate<KotlinTypeBean, Nothing?>, data: Nothing?) {
isNullPredicate.arg.accept(this, data)
buffer.append(" ${if (isNullPredicate.isNegated) "!=" else "=="} null")
}
override fun visitConstantDescriptor(constantReference: KtConstantReference<KotlinTypeBean, Nothing?>, data: Nothing?) {
buffer.append(constantReference.name)
}
override fun visitValueParameterReference(valueParameterReference: KtValueParameterReference<KotlinTypeBean, Nothing?>, data: Nothing?) {
buffer.append("param(").append(valueParameterReference.parameterIndex).append(")")
}
}
@@ -30,6 +30,12 @@ public class AdditionalStubInfoTestGenerated extends AbstractAdditionalStubInfoT
runTest("analysis/decompiled/decompiler-to-file-stubs/testData/additionalClsStubInfo/AnnotatedFlexibleTypes/");
}
@Test
@TestMetadata("Contracts")
public void testContracts() throws Exception {
runTest("analysis/decompiled/decompiler-to-file-stubs/testData/additionalClsStubInfo/Contracts/");
}
@Test
@TestMetadata("OuterClassesWithFlexibleArgs")
public void testOuterClassesWithFlexibleArgs() throws Exception {
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.resolve.DataClassResolver
import org.jetbrains.kotlin.serialization.deserialization.AnnotatedCallableKind
import org.jetbrains.kotlin.serialization.deserialization.ProtoContainer
import org.jetbrains.kotlin.serialization.deserialization.getName
import org.jetbrains.kotlin.utils.addToStdlib.runIf
fun createPackageDeclarationsStubs(
parentStub: StubElement<out PsiElement>,
@@ -173,6 +174,7 @@ private class FunctionClsStubBuilder(
// Note that arguments passed to stubs here and elsewhere are based on what stabs would be generated based on decompiled code
// As functions are never decompiled to fun f() = 1 form, hasBlockBody is always true
// This info is anyway irrelevant for the purposes these stubs are used
val hasContract = functionProto.hasContract()
return KotlinFunctionStubImpl(
parent,
callableName.ref(),
@@ -182,7 +184,10 @@ private class FunctionClsStubBuilder(
hasBlockBody = true,
hasBody = Flags.MODALITY.get(functionProto.flags) != Modality.ABSTRACT,
hasTypeParameterListBeforeFunctionName = functionProto.typeParameterList.isNotEmpty(),
mayHaveContract = functionProto.hasContract()
mayHaveContract = hasContract,
runIf(hasContract) {
ClsContractBuilder(c, typeStubBuilder).loadContract(functionProto)
}
)
}
}
@@ -0,0 +1,55 @@
/*
* Copyright 2010-2023 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.decompiler.stub
import org.jetbrains.kotlin.contracts.description.KtBooleanValueParameterReference
import org.jetbrains.kotlin.contracts.description.KtConstantReference
import org.jetbrains.kotlin.contracts.description.KtEffectDeclaration
import org.jetbrains.kotlin.contracts.description.KtValueParameterReference
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.isInstanceType
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.psi.stubs.impl.*
import org.jetbrains.kotlin.psi.stubs.impl.KotlinContractEffectType.Companion.IGNORE_REFERENCE_PARAMETER_NAME
import org.jetbrains.kotlin.serialization.deserialization.ProtoBufContractDeserializer
import org.jetbrains.kotlin.serialization.deserialization.getClassId
class ClsContractBuilder(private val c: ClsStubBuilderContext, private val typeStubBuilder: TypeClsStubBuilder) :
ProtoBufContractDeserializer<KotlinTypeBean, Nothing?, ProtoBuf.Function>() {
fun loadContract(proto: ProtoBuf.Function): List<KtEffectDeclaration<KotlinTypeBean, Nothing?>>? {
return proto.contract.effectList.map { loadPossiblyConditionalEffect(it, proto) ?: return null }
}
override fun extractVariable(valueParameterIndex: Int, owner: ProtoBuf.Function): KtValueParameterReference<KotlinTypeBean, Nothing?> {
val type = if (valueParameterIndex < 0) {
owner.receiverType
} else owner.valueParameterList[valueParameterIndex].type
return if (type.hasClassName() && c.nameResolver.getClassId(type.className) == StandardClassIds.Boolean) {
KtBooleanValueParameterReference(valueParameterIndex, name = IGNORE_REFERENCE_PARAMETER_NAME)
} else KtValueParameterReference(valueParameterIndex, name = IGNORE_REFERENCE_PARAMETER_NAME)
}
override fun extractType(proto: ProtoBuf.Expression): KotlinTypeBean? {
return typeStubBuilder.createKotlinTypeBean(proto.isInstanceType(c.typeTable))
}
override fun loadConstant(value: ProtoBuf.Expression.ConstantValue): KtConstantReference<KotlinTypeBean, Nothing?> {
return when (value) {
ProtoBuf.Expression.ConstantValue.TRUE -> KotlinContractConstantValues.TRUE
ProtoBuf.Expression.ConstantValue.FALSE -> KotlinContractConstantValues.FALSE
ProtoBuf.Expression.ConstantValue.NULL -> KotlinContractConstantValues.NULL
}
}
override fun getNotNull(): KtConstantReference<KotlinTypeBean, Nothing?> {
return KotlinContractConstantValues.NOT_NULL
}
override fun getWildcard(): KtConstantReference<KotlinTypeBean, Nothing?> {
return KotlinContractConstantValues.WILDCARD
}
}
@@ -150,7 +150,7 @@ class TypeClsStubBuilder(private val c: ClsStubBuilderContext) {
}
}
private fun createKotlinTypeBean(
fun createKotlinTypeBean(
type: Type?
): KotlinTypeBean? {
if (type == null) return null