Render contracts in kotlinp, fix minor bug in kotlinx-metadata-jvm
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
- [`KT-31308`](https://youtrack.jetbrains.com/issue/KT-31308) Add module name extensions to kotlinx-metadata-jvm
|
- [`KT-31308`](https://youtrack.jetbrains.com/issue/KT-31308) Add module name extensions to kotlinx-metadata-jvm
|
||||||
- [`KT-31338`](https://youtrack.jetbrains.com/issue/KT-31338) Retain "is moved from interface companion" property flag in kotlinx-metadata-jvm
|
- [`KT-31338`](https://youtrack.jetbrains.com/issue/KT-31338) Retain "is moved from interface companion" property flag in kotlinx-metadata-jvm
|
||||||
- Breaking change: JvmPropertyExtensionVisitor.visit has a new parameter `jvmFlags: Flags`
|
- Breaking change: JvmPropertyExtensionVisitor.visit has a new parameter `jvmFlags: Flags`
|
||||||
|
- Correctly write "null" constant value in effect expression of a contract
|
||||||
- Rename `desc` parameters to `signature` in JvmFunctionExtensionVisitor, JvmPropertyExtensionVisitor, JvmConstructorExtensionVisitor
|
- Rename `desc` parameters to `signature` in JvmFunctionExtensionVisitor, JvmPropertyExtensionVisitor, JvmConstructorExtensionVisitor
|
||||||
|
|
||||||
## 0.0.5
|
## 0.0.5
|
||||||
|
|||||||
@@ -384,11 +384,10 @@ private fun writeEffectExpression(c: WriteContext, output: (ProtoBuf.Expression.
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visitConstantValue(value: Any?) {
|
override fun visitConstantValue(value: Any?) {
|
||||||
@Suppress("UNUSED_VARIABLE") // force exhaustive when
|
when (value) {
|
||||||
val unused = when (value) {
|
|
||||||
true -> t.constantValue = ProtoBuf.Expression.ConstantValue.TRUE
|
true -> t.constantValue = ProtoBuf.Expression.ConstantValue.TRUE
|
||||||
false -> t.constantValue = ProtoBuf.Expression.ConstantValue.FALSE
|
false -> t.constantValue = ProtoBuf.Expression.ConstantValue.FALSE
|
||||||
null -> null
|
null -> t.constantValue = ProtoBuf.Expression.ConstantValue.NULL
|
||||||
else -> throw IllegalArgumentException("Only true, false or null constant values are allowed for effects (was=$value)")
|
else -> throw IllegalArgumentException("Only true, false or null constant values are allowed for effects (was=$value)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ private fun visitFunction(settings: KotlinpSettings, sb: StringBuilder, flags: F
|
|||||||
val versionRequirements = mutableListOf<String>()
|
val versionRequirements = mutableListOf<String>()
|
||||||
var jvmSignature: JvmMemberSignature? = null
|
var jvmSignature: JvmMemberSignature? = null
|
||||||
var lambdaClassOriginName: String? = null
|
var lambdaClassOriginName: String? = null
|
||||||
|
var contract: String? = null
|
||||||
|
|
||||||
override fun visitReceiverParameterType(flags: Flags): KmTypeVisitor? =
|
override fun visitReceiverParameterType(flags: Flags): KmTypeVisitor? =
|
||||||
printType(flags) { receiverParameterType = it }
|
printType(flags) { receiverParameterType = it }
|
||||||
@@ -39,6 +40,9 @@ private fun visitFunction(settings: KotlinpSettings, sb: StringBuilder, flags: F
|
|||||||
override fun visitVersionRequirement(): KmVersionRequirementVisitor? =
|
override fun visitVersionRequirement(): KmVersionRequirementVisitor? =
|
||||||
printVersionRequirement { versionRequirements.add(it) }
|
printVersionRequirement { versionRequirements.add(it) }
|
||||||
|
|
||||||
|
override fun visitContract(): KmContractVisitor? =
|
||||||
|
printContract { contract = it }
|
||||||
|
|
||||||
override fun visitExtensions(type: KmExtensionType): KmFunctionExtensionVisitor? {
|
override fun visitExtensions(type: KmExtensionType): KmFunctionExtensionVisitor? {
|
||||||
if (type != JvmFunctionExtensionVisitor.TYPE) return null
|
if (type != JvmFunctionExtensionVisitor.TYPE) return null
|
||||||
return object : JvmFunctionExtensionVisitor() {
|
return object : JvmFunctionExtensionVisitor() {
|
||||||
@@ -79,6 +83,9 @@ private fun visitFunction(settings: KotlinpSettings, sb: StringBuilder, flags: F
|
|||||||
sb.append(": ").append(returnType)
|
sb.append(": ").append(returnType)
|
||||||
}
|
}
|
||||||
sb.appendln()
|
sb.appendln()
|
||||||
|
if (contract != null) {
|
||||||
|
sb.appendln(" $contract")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,6 +555,134 @@ private fun StringBuilder.appendDeclarationContainerExtensions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun printContract(output: (String) -> Unit): KmContractVisitor =
|
||||||
|
object : KmContractVisitor() {
|
||||||
|
val effects = mutableListOf<String>()
|
||||||
|
|
||||||
|
override fun visitEffect(type: KmEffectType, invocationKind: KmEffectInvocationKind?): KmEffectVisitor =
|
||||||
|
printEffect(type, invocationKind) { effects.add(it) }
|
||||||
|
|
||||||
|
override fun visitEnd() {
|
||||||
|
output(buildString {
|
||||||
|
appendln("contract {")
|
||||||
|
for (effect in effects) {
|
||||||
|
appendln(" $effect")
|
||||||
|
}
|
||||||
|
append(" }")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun printEffect(type: KmEffectType, invocationKind: KmEffectInvocationKind?, output: (String) -> Unit): KmEffectVisitor =
|
||||||
|
object : KmEffectVisitor() {
|
||||||
|
var argument: String? = null
|
||||||
|
var conclusion: String? = null
|
||||||
|
|
||||||
|
override fun visitConstructorArgument(): KmEffectExpressionVisitor =
|
||||||
|
printEffectExpression {
|
||||||
|
// If there are several arguments, only the first is taken, see ContractDeserializerImpl.deserializeSimpleEffect
|
||||||
|
if (argument == null) {
|
||||||
|
argument = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitConclusionOfConditionalEffect(): KmEffectExpressionVisitor =
|
||||||
|
printEffectExpression { conclusion = it }
|
||||||
|
|
||||||
|
override fun visitEnd() {
|
||||||
|
output(buildString {
|
||||||
|
when (type) {
|
||||||
|
KmEffectType.RETURNS_CONSTANT -> {
|
||||||
|
append("returns(")
|
||||||
|
if (argument != null) {
|
||||||
|
append(argument)
|
||||||
|
}
|
||||||
|
append(")")
|
||||||
|
}
|
||||||
|
KmEffectType.CALLS -> {
|
||||||
|
append("callsInPlace($argument")
|
||||||
|
if (invocationKind != null) {
|
||||||
|
append(", InvocationKind.${invocationKind.name}")
|
||||||
|
}
|
||||||
|
append(")")
|
||||||
|
}
|
||||||
|
KmEffectType.RETURNS_NOT_NULL -> {
|
||||||
|
append("returnsNotNull()")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (conclusion != null) {
|
||||||
|
append(" implies ($conclusion)")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun printEffectExpression(output: (String) -> Unit): KmEffectExpressionVisitor =
|
||||||
|
object : KmEffectExpressionVisitor() {
|
||||||
|
var flags: Flags = 0
|
||||||
|
var parameterIndex: Int? = null
|
||||||
|
var constantValue: List<Any?>? = null // Single-element list
|
||||||
|
var isInstanceType: String? = null
|
||||||
|
var andArguments = mutableListOf<String>()
|
||||||
|
var orArguments = mutableListOf<String>()
|
||||||
|
|
||||||
|
override fun visit(flags: Flags, parameterIndex: Int?) {
|
||||||
|
this.flags = flags
|
||||||
|
this.parameterIndex = parameterIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitConstantValue(value: Any?) {
|
||||||
|
constantValue = listOf(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitIsInstanceType(flags: Flags): KmTypeVisitor =
|
||||||
|
printType(flags) { isInstanceType = it }
|
||||||
|
|
||||||
|
override fun visitAndArgument(): KmEffectExpressionVisitor =
|
||||||
|
printEffectExpression { andArguments.add(it) }
|
||||||
|
|
||||||
|
override fun visitOrArgument(): KmEffectExpressionVisitor =
|
||||||
|
printEffectExpression { orArguments.add(it) }
|
||||||
|
|
||||||
|
override fun visitEnd() {
|
||||||
|
output(buildString {
|
||||||
|
append(
|
||||||
|
when {
|
||||||
|
constantValue != null -> constantValue!!.single().toString()
|
||||||
|
parameterIndex != null -> "p#$parameterIndex"
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (isInstanceType != null) {
|
||||||
|
append(" ")
|
||||||
|
if (Flag.EffectExpression.IS_NEGATED(flags)) append("!")
|
||||||
|
append("is $isInstanceType")
|
||||||
|
}
|
||||||
|
if (Flag.EffectExpression.IS_NULL_CHECK_PREDICATE(flags)) {
|
||||||
|
append(if (Flag.EffectExpression.IS_NEGATED(flags)) " != " else " == ")
|
||||||
|
append("null")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orArguments.isEmpty()) {
|
||||||
|
for (andArgument in andArguments) {
|
||||||
|
if (!isEmpty()) append(" && ")
|
||||||
|
append(wrapIfNeeded(andArgument))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (andArguments.isEmpty()) {
|
||||||
|
for (orArgument in orArguments) {
|
||||||
|
if (!isEmpty()) append(" || ")
|
||||||
|
append(wrapIfNeeded(orArgument))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun wrapIfNeeded(s: String): String =
|
||||||
|
// A simple heuristic to avoid wrapping into unnecessary parentheses
|
||||||
|
if ('&' in s || '|' in s) "($s)" else s
|
||||||
|
}
|
||||||
|
|
||||||
interface AbstractPrinter<in T : KotlinClassMetadata> {
|
interface AbstractPrinter<in T : KotlinClassMetadata> {
|
||||||
fun print(klass: T): String
|
fun print(klass: T): String
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -34,6 +34,11 @@ public class KotlinpTestGenerated extends AbstractKotlinpTest {
|
|||||||
runTest("libraries/tools/kotlinp/testData/Annotations.kt");
|
runTest("libraries/tools/kotlinp/testData/Annotations.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("Contracts.kt")
|
||||||
|
public void testContracts() throws Exception {
|
||||||
|
runTest("libraries/tools/kotlinp/testData/Contracts.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("Lambda.kt")
|
@TestMetadata("Lambda.kt")
|
||||||
public void testLambda() throws Exception {
|
public void testLambda() throws Exception {
|
||||||
runTest("libraries/tools/kotlinp/testData/Lambda.kt");
|
runTest("libraries/tools/kotlinp/testData/Lambda.kt");
|
||||||
|
|||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
@file:UseExperimental(ExperimentalContracts::class)
|
||||||
|
|
||||||
|
import kotlin.contracts.InvocationKind
|
||||||
|
import kotlin.contracts.contract
|
||||||
|
import kotlin.contracts.ExperimentalContracts
|
||||||
|
|
||||||
|
fun returnsTrue(condition: Boolean) {
|
||||||
|
contract {
|
||||||
|
returns(true) implies (condition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun returnsNull(condition: Boolean) {
|
||||||
|
contract {
|
||||||
|
returns(null) implies (condition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun returnsNotNull(condition: Boolean) {
|
||||||
|
contract {
|
||||||
|
returnsNotNull() implies (condition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Any?.receiverIsNotNull(): Boolean {
|
||||||
|
contract {
|
||||||
|
returns(true) implies (this@receiverIsNotNull != null)
|
||||||
|
}
|
||||||
|
return this != null
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun callsInPlaceAtMostOnce(block: () -> Unit) {
|
||||||
|
contract {
|
||||||
|
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun callsInPlaceUnknown(block: () -> Unit) {
|
||||||
|
contract {
|
||||||
|
callsInPlace(block, InvocationKind.UNKNOWN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun conjunction(a: Boolean, b: Boolean, c: Boolean) {
|
||||||
|
contract {
|
||||||
|
returns() implies (a && !b && c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun disjunction(a: Boolean, b: Boolean, c: Boolean) {
|
||||||
|
contract {
|
||||||
|
returns() implies (a || !b || c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun complexBoolean(a: Any?, b: Any?, c: Any?, d: Any?) {
|
||||||
|
contract {
|
||||||
|
returns() implies ((a != null && c != null) || (b == null && d != null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun negatedIsAndConjunction(a: Any?, b: Boolean, c: Any?) {
|
||||||
|
contract {
|
||||||
|
returns() implies (a !is List<*> && b && c == null)
|
||||||
|
}
|
||||||
|
}
|
||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
// ContractsKt.class
|
||||||
|
// ------------------------------------------
|
||||||
|
package {
|
||||||
|
|
||||||
|
// signature: callsInPlaceAtMostOnce(Lkotlin/jvm/functions/Function0;)V
|
||||||
|
public final inline fun callsInPlaceAtMostOnce(block: kotlin/Function0<kotlin/Unit>): kotlin/Unit
|
||||||
|
contract {
|
||||||
|
callsInPlace(p#1, InvocationKind.AT_MOST_ONCE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// signature: callsInPlaceUnknown(Lkotlin/jvm/functions/Function0;)V
|
||||||
|
public final inline fun callsInPlaceUnknown(block: kotlin/Function0<kotlin/Unit>): kotlin/Unit
|
||||||
|
contract {
|
||||||
|
callsInPlace(p#1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// signature: complexBoolean(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
|
||||||
|
public final fun complexBoolean(a: kotlin/Any?, b: kotlin/Any?, c: kotlin/Any?, d: kotlin/Any?): kotlin/Unit
|
||||||
|
contract {
|
||||||
|
returns() implies ((p#1 != null && p#3 != null) || (p#2 == null && p#4 != null))
|
||||||
|
}
|
||||||
|
|
||||||
|
// signature: conjunction(ZZZ)V
|
||||||
|
public final fun conjunction(a: kotlin/Boolean, b: kotlin/Boolean, c: kotlin/Boolean): kotlin/Unit
|
||||||
|
contract {
|
||||||
|
returns() implies (p#1 && p#2 && p#3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// signature: disjunction(ZZZ)V
|
||||||
|
public final fun disjunction(a: kotlin/Boolean, b: kotlin/Boolean, c: kotlin/Boolean): kotlin/Unit
|
||||||
|
contract {
|
||||||
|
returns() implies (p#1 || p#2 || p#3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// signature: negatedIsAndConjunction(Ljava/lang/Object;ZLjava/lang/Object;)V
|
||||||
|
public final fun negatedIsAndConjunction(a: kotlin/Any?, b: kotlin/Boolean, c: kotlin/Any?): kotlin/Unit
|
||||||
|
contract {
|
||||||
|
returns() implies (p#1 !is kotlin/collections/List<*> && p#2 && p#3 == null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// signature: returnsNotNull(Z)V
|
||||||
|
public final fun returnsNotNull(condition: kotlin/Boolean): kotlin/Unit
|
||||||
|
contract {
|
||||||
|
returnsNotNull() implies (p#1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// signature: returnsNull(Z)V
|
||||||
|
public final fun returnsNull(condition: kotlin/Boolean): kotlin/Unit
|
||||||
|
contract {
|
||||||
|
returns(null) implies (p#1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// signature: returnsTrue(Z)V
|
||||||
|
public final fun returnsTrue(condition: kotlin/Boolean): kotlin/Unit
|
||||||
|
contract {
|
||||||
|
returns(true) implies (p#1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// signature: receiverIsNotNull(Ljava/lang/Object;)Z
|
||||||
|
public final fun kotlin/Any?.receiverIsNotNull(): kotlin/Boolean
|
||||||
|
contract {
|
||||||
|
returns(true) implies (p#0 != null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// META-INF/test-module.kotlin_module
|
||||||
|
// ------------------------------------------
|
||||||
|
module {
|
||||||
|
package <root> {
|
||||||
|
ContractsKt
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user