Render contracts in kotlinp, fix minor bug in kotlinx-metadata-jvm

This commit is contained in:
Alexander Udalov
2019-05-07 15:04:00 +02:00
parent 2a3786e3f8
commit fd00999771
6 changed files with 280 additions and 3 deletions
@@ -5,6 +5,7 @@
- [`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
- 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
## 0.0.5
@@ -384,11 +384,10 @@ private fun writeEffectExpression(c: WriteContext, output: (ProtoBuf.Expression.
}
override fun visitConstantValue(value: Any?) {
@Suppress("UNUSED_VARIABLE") // force exhaustive when
val unused = when (value) {
when (value) {
true -> t.constantValue = ProtoBuf.Expression.ConstantValue.TRUE
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)")
}
}
@@ -21,6 +21,7 @@ private fun visitFunction(settings: KotlinpSettings, sb: StringBuilder, flags: F
val versionRequirements = mutableListOf<String>()
var jvmSignature: JvmMemberSignature? = null
var lambdaClassOriginName: String? = null
var contract: String? = null
override fun visitReceiverParameterType(flags: Flags): KmTypeVisitor? =
printType(flags) { receiverParameterType = it }
@@ -39,6 +40,9 @@ private fun visitFunction(settings: KotlinpSettings, sb: StringBuilder, flags: F
override fun visitVersionRequirement(): KmVersionRequirementVisitor? =
printVersionRequirement { versionRequirements.add(it) }
override fun visitContract(): KmContractVisitor? =
printContract { contract = it }
override fun visitExtensions(type: KmExtensionType): KmFunctionExtensionVisitor? {
if (type != JvmFunctionExtensionVisitor.TYPE) return null
return object : JvmFunctionExtensionVisitor() {
@@ -79,6 +83,9 @@ private fun visitFunction(settings: KotlinpSettings, sb: StringBuilder, flags: F
sb.append(": ").append(returnType)
}
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> {
fun print(klass: T): String
}
@@ -34,6 +34,11 @@ public class KotlinpTestGenerated extends AbstractKotlinpTest {
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")
public void testLambda() throws Exception {
runTest("libraries/tools/kotlinp/testData/Lambda.kt");
+66
View File
@@ -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
View File
@@ -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
}
}