From 1a32fdf6d7ffcdda30aec4a025a798840f3f3eed Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Wed, 1 Jul 2020 18:47:44 +0300 Subject: [PATCH] Add EXACTLY_ONCE contract to functions that call their lambda parameter once KT-35972 --- .../src/main/kotlin/kotlin/test/Assertions.kt | 12 +++++++-- .../stdlib/js/src/kotlinx/dom/Builders.kt | 12 ++++++--- .../stdlib/jvm/src/kotlin/concurrent/Locks.kt | 7 +++-- .../stdlib/src/kotlin/text/StringBuilder.kt | 16 +++++++---- libraries/stdlib/src/kotlin/time/Duration.kt | 27 ++++++++++++------- 5 files changed, 53 insertions(+), 21 deletions(-) diff --git a/libraries/kotlin.test/common/src/main/kotlin/kotlin/test/Assertions.kt b/libraries/kotlin.test/common/src/main/kotlin/kotlin/test/Assertions.kt index 850d994ff12..dac22928c8c 100644 --- a/libraries/kotlin.test/common/src/main/kotlin/kotlin/test/Assertions.kt +++ b/libraries/kotlin.test/common/src/main/kotlin/kotlin/test/Assertions.kt @@ -29,7 +29,10 @@ val asserter: Asserter internal var _asserter: Asserter? = null /** Asserts that the given [block] returns `true`. */ -fun assertTrue(message: String? = null, block: () -> Boolean): Unit = assertTrue(block(), message) +fun assertTrue(message: String? = null, block: () -> Boolean) { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + assertTrue(block(), message) +} /** Asserts that the expression is `true` with an optional [message]. */ fun assertTrue(actual: Boolean, message: String? = null) { @@ -38,7 +41,10 @@ fun assertTrue(actual: Boolean, message: String? = null) { } /** Asserts that the given [block] returns `false`. */ -fun assertFalse(message: String? = null, block: () -> Boolean): Unit = assertFalse(block(), message) +fun assertFalse(message: String? = null, block: () -> Boolean) { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + assertFalse(block(), message) +} /** Asserts that the expression is `false` with an optional [message]. */ fun assertFalse(actual: Boolean, message: String? = null) { @@ -105,11 +111,13 @@ fun fail(message: String? = null, cause: Throwable? = null): Nothing { /** Asserts that given function [block] returns the given [expected] value. */ fun <@OnlyInputTypes T> expect(expected: T, block: () -> T) { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } assertEquals(expected, block()) } /** Asserts that given function [block] returns the given [expected] value and use the given [message] if it fails. */ fun <@OnlyInputTypes T> expect(expected: T, message: String?, block: () -> T) { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } assertEquals(expected, block(), message) } diff --git a/libraries/stdlib/js/src/kotlinx/dom/Builders.kt b/libraries/stdlib/js/src/kotlinx/dom/Builders.kt index a194035f732..95ac70a1604 100644 --- a/libraries/stdlib/js/src/kotlinx/dom/Builders.kt +++ b/libraries/stdlib/js/src/kotlinx/dom/Builders.kt @@ -6,19 +6,25 @@ package kotlinx.dom import org.w3c.dom.* +import kotlin.contracts.* /** * Creates a new element with the specified [name]. * * The element is initialized with the specified [init] function. */ -public fun Document.createElement(name: String, init: Element.() -> Unit): Element = createElement(name).apply(init) +public fun Document.createElement(name: String, init: Element.() -> Unit): Element { + contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) } + return createElement(name).apply(init) +} /** * Appends a newly created element with the specified [name] to this element. * * The element is initialized with the specified [init] function. */ -public fun Element.appendElement(name: String, init: Element.() -> Unit): Element = - ownerDocument!!.createElement(name, init).also { appendChild(it) } +public fun Element.appendElement(name: String, init: Element.() -> Unit): Element { + contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) } + return ownerDocument!!.createElement(name, init).also { appendChild(it) } +} diff --git a/libraries/stdlib/jvm/src/kotlin/concurrent/Locks.kt b/libraries/stdlib/jvm/src/kotlin/concurrent/Locks.kt index 969bc8bd3b7..da6728ec88c 100644 --- a/libraries/stdlib/jvm/src/kotlin/concurrent/Locks.kt +++ b/libraries/stdlib/jvm/src/kotlin/concurrent/Locks.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 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. */ @@ -8,7 +8,7 @@ package kotlin.concurrent import java.util.concurrent.locks.Lock import java.util.concurrent.locks.ReentrantReadWriteLock -import java.util.concurrent.CountDownLatch +import kotlin.contracts.* /** * Executes the given [action] under this lock. @@ -16,6 +16,7 @@ import java.util.concurrent.CountDownLatch */ @kotlin.internal.InlineOnly public inline fun Lock.withLock(action: () -> T): T { + contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } lock() try { return action() @@ -30,6 +31,7 @@ public inline fun Lock.withLock(action: () -> T): T { */ @kotlin.internal.InlineOnly public inline fun ReentrantReadWriteLock.read(action: () -> T): T { + contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } val rl = readLock() rl.lock() try { @@ -54,6 +56,7 @@ public inline fun ReentrantReadWriteLock.read(action: () -> T): T { */ @kotlin.internal.InlineOnly public inline fun ReentrantReadWriteLock.write(action: () -> T): T { + contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } val rl = readLock() val readCount = if (writeHoldCount == 0) readHoldCount else 0 diff --git a/libraries/stdlib/src/kotlin/text/StringBuilder.kt b/libraries/stdlib/src/kotlin/text/StringBuilder.kt index faef1ff6aaa..66cf873a1d1 100644 --- a/libraries/stdlib/src/kotlin/text/StringBuilder.kt +++ b/libraries/stdlib/src/kotlin/text/StringBuilder.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 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. */ @@ -8,6 +8,8 @@ package kotlin.text +import kotlin.contracts.* + /** * A mutable sequence of characters. * @@ -399,8 +401,10 @@ public inline fun StringBuilder.append(obj: Any?): StringBuilder = this.append(o * and then converting it to [String]. */ @kotlin.internal.InlineOnly -public inline fun buildString(builderAction: StringBuilder.() -> Unit): String = - StringBuilder().apply(builderAction).toString() +public inline fun buildString(builderAction: StringBuilder.() -> Unit): String { + contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) } + return StringBuilder().apply(builderAction).toString() +} /** * Builds new string by populating newly created [StringBuilder] initialized with the given [capacity] @@ -408,8 +412,10 @@ public inline fun buildString(builderAction: StringBuilder.() -> Unit): String = */ @SinceKotlin("1.1") @kotlin.internal.InlineOnly -public inline fun buildString(capacity: Int, builderAction: StringBuilder.() -> Unit): String = - StringBuilder(capacity).apply(builderAction).toString() +public inline fun buildString(capacity: Int, builderAction: StringBuilder.() -> Unit): String { + contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) } + return StringBuilder(capacity).apply(builderAction).toString() +} /** * Appends all arguments to the given StringBuilder. diff --git a/libraries/stdlib/src/kotlin/time/Duration.kt b/libraries/stdlib/src/kotlin/time/Duration.kt index 517c1a8c623..a1aef5ac8dc 100644 --- a/libraries/stdlib/src/kotlin/time/Duration.kt +++ b/libraries/stdlib/src/kotlin/time/Duration.kt @@ -1,10 +1,11 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 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 kotlin.time +import kotlin.contracts.* import kotlin.math.abs @OptIn(ExperimentalTime::class) @@ -104,8 +105,10 @@ public inline class Duration internal constructor(internal val value: Double) : * If the value doesn't fit in [Int] range, i.e. it's greater than [Int.MAX_VALUE] or less than [Int.MIN_VALUE], * it is coerced into that range. */ - public inline fun toComponents(action: (days: Int, hours: Int, minutes: Int, seconds: Int, nanoseconds: Int) -> T): T = - action(inDays.toInt(), hoursComponent, minutesComponent, secondsComponent, nanosecondsComponent) + public inline fun toComponents(action: (days: Int, hours: Int, minutes: Int, seconds: Int, nanoseconds: Int) -> T): T { + contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } + return action(inDays.toInt(), hoursComponent, minutesComponent, secondsComponent, nanosecondsComponent) + } /** * Splits this duration into hours, minutes, seconds, and nanoseconds and executes the given [action] with these components. @@ -118,8 +121,10 @@ public inline class Duration internal constructor(internal val value: Double) : * If the value doesn't fit in [Int] range, i.e. it's greater than [Int.MAX_VALUE] or less than [Int.MIN_VALUE], * it is coerced into that range. */ - public inline fun toComponents(action: (hours: Int, minutes: Int, seconds: Int, nanoseconds: Int) -> T): T = - action(inHours.toInt(), minutesComponent, secondsComponent, nanosecondsComponent) + public inline fun toComponents(action: (hours: Int, minutes: Int, seconds: Int, nanoseconds: Int) -> T): T { + contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } + return action(inHours.toInt(), minutesComponent, secondsComponent, nanosecondsComponent) + } /** * Splits this duration into minutes, seconds, and nanoseconds and executes the given [action] with these components. @@ -131,8 +136,10 @@ public inline class Duration internal constructor(internal val value: Double) : * If the value doesn't fit in [Int] range, i.e. it's greater than [Int.MAX_VALUE] or less than [Int.MIN_VALUE], * it is coerced into that range. */ - public inline fun toComponents(action: (minutes: Int, seconds: Int, nanoseconds: Int) -> T): T = - action(inMinutes.toInt(), secondsComponent, nanosecondsComponent) + public inline fun toComponents(action: (minutes: Int, seconds: Int, nanoseconds: Int) -> T): T { + contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } + return action(inMinutes.toInt(), secondsComponent, nanosecondsComponent) + } /** * Splits this duration into seconds, and nanoseconds and executes the given [action] with these components. @@ -143,8 +150,10 @@ public inline class Duration internal constructor(internal val value: Double) : * If the value doesn't fit in [Long] range, i.e. it's greater than [Long.MAX_VALUE] or less than [Long.MIN_VALUE], * it is coerced into that range. */ - public inline fun toComponents(action: (seconds: Long, nanoseconds: Int) -> T): T = - action(inSeconds.toLong(), nanosecondsComponent) + public inline fun toComponents(action: (seconds: Long, nanoseconds: Int) -> T): T { + contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } + return action(inSeconds.toLong(), nanosecondsComponent) + } @PublishedApi internal val hoursComponent: Int get() = (inHours % 24).toInt()