Skip to content

Commit

Permalink
Add and fix contracts for inline functions (#3535)
Browse files Browse the repository at this point in the history
  • Loading branch information
kyay10 authored Nov 22, 2024
1 parent 5026cfd commit a203862
Show file tree
Hide file tree
Showing 29 changed files with 2,084 additions and 613 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
@file:OptIn(ExperimentalContracts::class)

package arrow.atomic

import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

/**
* [Atomic] value of [V].
*
Expand Down Expand Up @@ -44,31 +50,55 @@ public var <T> Atomic<T>.value: T
/**
* Infinite loop that reads this atomic variable and performs the specified [action] on its value.
*/
public inline fun <V> Atomic<V>.loop(action: (V) -> Unit): Nothing { while(true) { action(value) } }
public inline fun <V> Atomic<V>.loop(action: (V) -> Unit): Nothing {
contract { callsInPlace(action, InvocationKind.AT_LEAST_ONCE) }
do { action(value) } while(true)
}

public inline fun <V> Atomic<V>.tryUpdate(function: (V) -> V): Boolean = tryUpdate(function) { _, _ -> }
public inline fun <V> Atomic<V>.tryUpdate(function: (V) -> V): Boolean {
contract { callsInPlace(function, InvocationKind.EXACTLY_ONCE) }
return tryUpdate(function) { _, _ -> }
}

public inline fun <V> Atomic<V>.update(function: (V) -> V): Unit = update(function) { _, _ -> }
public inline fun <V> Atomic<V>.update(function: (V) -> V) {
contract { callsInPlace(function, InvocationKind.AT_LEAST_ONCE) }
update(function) { _, _ -> }
}

/**
* Updates variable atomically using the specified [function] of its value and returns its old value.
*/
public inline fun <V> Atomic<V>.getAndUpdate(function: (V) -> V): V = update(function) { old, _ -> old }
public inline fun <V> Atomic<V>.getAndUpdate(function: (V) -> V): V {
contract { callsInPlace(function, InvocationKind.AT_LEAST_ONCE) }
return update(function) { old, _ -> old }
}

/**
* Updates variable atomically using the specified [function] of its value and returns its new value.
*/
public inline fun <V> Atomic<V>.updateAndGet(function: (V) -> V): V = update(function) { _, new -> new }
public inline fun <V> Atomic<V>.updateAndGet(function: (V) -> V): V {
contract { callsInPlace(function, InvocationKind.AT_LEAST_ONCE) }
return update(function) { _, new -> new }
}

@PublishedApi
internal inline fun <V, U: V, R> Atomic<V>.update(function: (V) -> U, transform: (old: V, new: U) -> R): R {
while (true) {
tryUpdate(function) { old, new -> return transform(old, new) }
contract {
callsInPlace(function, InvocationKind.AT_LEAST_ONCE)
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}
loop { cur ->
val upd = function(value)
if(compareAndSet(cur, upd)) return transform(cur, upd)
}
}

@PublishedApi
internal inline fun <V, U: V> Atomic<V>.tryUpdate(function: (V) -> U, onUpdated: (old: V, new: U) -> Unit): Boolean {
contract {
callsInPlace(function, InvocationKind.EXACTLY_ONCE)
callsInPlace(onUpdated, InvocationKind.AT_MOST_ONCE)
}
val cur = value
val upd = function(cur)
return compareAndSet(cur, upd).also { if (it) onUpdated(cur, upd) }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
@file:OptIn(ExperimentalContracts::class)

package arrow.atomic

import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

public class AtomicBoolean(value: Boolean) {
private val inner = AtomicInt(value.toInt())

Expand Down Expand Up @@ -28,31 +34,55 @@ public class AtomicBoolean(value: Boolean) {
/**
* Infinite loop that reads this atomic variable and performs the specified [action] on its value.
*/
public inline fun AtomicBoolean.loop(action: (Boolean) -> Unit): Nothing { while(true) { action(value) } }
public inline fun AtomicBoolean.loop(action: (Boolean) -> Unit): Nothing {
contract { callsInPlace(action, InvocationKind.AT_LEAST_ONCE) }
do { action(value) } while(true)
}

public inline fun AtomicBoolean.tryUpdate(function: (Boolean) -> Boolean): Boolean = tryUpdate(function) { _, _ -> }
public inline fun AtomicBoolean.tryUpdate(function: (Boolean) -> Boolean): Boolean {
contract { callsInPlace(function, InvocationKind.EXACTLY_ONCE) }
return tryUpdate(function) { _, _ -> }
}

public inline fun AtomicBoolean.update(function: (Boolean) -> Boolean): Unit = update(function) { _, _ -> }
public inline fun AtomicBoolean.update(function: (Boolean) -> Boolean) {
contract { callsInPlace(function, InvocationKind.AT_LEAST_ONCE) }
update(function) { _, _ -> }
}

/**
* Updates variable atomically using the specified [function] of its value and returns its old value.
*/
public inline fun AtomicBoolean.getAndUpdate(function: (Boolean) -> Boolean): Boolean = update(function) { old, _ -> old }
public inline fun AtomicBoolean.getAndUpdate(function: (Boolean) -> Boolean): Boolean {
contract { callsInPlace(function, InvocationKind.AT_LEAST_ONCE) }
return update(function) { old, _ -> old }
}

/**
* Updates variable atomically using the specified [function] of its value and returns its new value.
*/
public inline fun AtomicBoolean.updateAndGet(function: (Boolean) -> Boolean): Boolean = update(function) { _, new -> new }
public inline fun AtomicBoolean.updateAndGet(function: (Boolean) -> Boolean): Boolean {
contract { callsInPlace(function, InvocationKind.AT_LEAST_ONCE) }
return update(function) { _, new -> new }
}

@PublishedApi
internal inline fun <R> AtomicBoolean.update(function: (Boolean) -> Boolean, transform: (old: Boolean, new: Boolean) -> R): R {
while (true) {
tryUpdate(function) { old, new -> return transform(old, new) }
contract {
callsInPlace(function, InvocationKind.AT_LEAST_ONCE)
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}
loop { cur ->
val upd = function(value)
if(compareAndSet(cur, upd)) return transform(cur, upd)
}
}

@PublishedApi
internal inline fun AtomicBoolean.tryUpdate(function: (Boolean) -> Boolean, onUpdated: (old: Boolean, new: Boolean) -> Unit): Boolean {
contract {
callsInPlace(function, InvocationKind.EXACTLY_ONCE)
callsInPlace(onUpdated, InvocationKind.AT_MOST_ONCE)
}
val cur = value
val upd = function(cur)
return compareAndSet(cur, upd).also { if (it) onUpdated(cur, upd) }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
@file:OptIn(ExperimentalContracts::class)

package arrow.atomic

import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

public expect class AtomicInt(initialValue: Int) {

public fun get(): Int
Expand All @@ -23,31 +29,55 @@ public var AtomicInt.value: Int
/**
* Infinite loop that reads this atomic variable and performs the specified [action] on its value.
*/
public inline fun AtomicInt.loop(action: (Int) -> Unit): Nothing { while(true) { action(value) } }
public inline fun AtomicInt.loop(action: (Int) -> Unit): Nothing {
contract { callsInPlace(action, InvocationKind.AT_LEAST_ONCE) }
do { action(value) } while(true)
}

public inline fun AtomicInt.tryUpdate(function: (Int) -> Int): Boolean = tryUpdate(function) { _, _ -> }
public inline fun AtomicInt.tryUpdate(function: (Int) -> Int): Boolean {
contract { callsInPlace(function, InvocationKind.EXACTLY_ONCE) }
return tryUpdate(function) { _, _ -> }
}

public inline fun AtomicInt.update(function: (Int) -> Int) : Unit = update(function) { _, _ -> }
public inline fun AtomicInt.update(function: (Int) -> Int) {
contract { callsInPlace(function, InvocationKind.AT_LEAST_ONCE) }
update(function) { _, _ -> }
}

/**
* Updates variable atomically using the specified [function] of its value and returns its old value.
*/
public inline fun AtomicInt.getAndUpdate(function: (Int) -> Int): Int = update(function) { old, _ -> old }
public inline fun AtomicInt.getAndUpdate(function: (Int) -> Int): Int {
contract { callsInPlace(function, InvocationKind.AT_LEAST_ONCE) }
return update(function) { old, _ -> old }
}

/**
* Updates variable atomically using the specified [function] of its value and returns its new value.
*/
public inline fun AtomicInt.updateAndGet(function: (Int) -> Int): Int = update(function) { _, new -> new }
public inline fun AtomicInt.updateAndGet(function: (Int) -> Int): Int {
contract { callsInPlace(function, InvocationKind.AT_LEAST_ONCE) }
return update(function) { _, new -> new }
}

@PublishedApi
internal inline fun <R> AtomicInt.update(function: (Int) -> Int, transform: (old: Int, new: Int) -> R): R {
while (true) {
tryUpdate(function) { old, new -> return transform(old, new) }
contract {
callsInPlace(function, InvocationKind.AT_LEAST_ONCE)
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}
loop { cur ->
val upd = function(value)
if(compareAndSet(cur, upd)) return transform(cur, upd)
}
}

@PublishedApi
internal inline fun AtomicInt.tryUpdate(function: (Int) -> Int, onUpdated: (old: Int, new: Int) -> Unit): Boolean {
contract {
callsInPlace(function, InvocationKind.EXACTLY_ONCE)
callsInPlace(onUpdated, InvocationKind.AT_MOST_ONCE)
}
val cur = value
val upd = function(cur)
return compareAndSet(cur, upd).also { if (it) onUpdated(cur, upd) }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
@file:OptIn(ExperimentalContracts::class)

package arrow.atomic

import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

public expect class AtomicLong(initialValue: Long) {

public fun get(): Long
Expand All @@ -23,31 +29,55 @@ public var AtomicLong.value: Long
/**
* Infinite loop that reads this atomic variable and performs the specified [action] on its value.
*/
public inline fun AtomicLong.loop(action: (Long) -> Unit): Nothing { while(true) { action(value) } }
public inline fun AtomicLong.loop(action: (Long) -> Unit): Nothing {
contract { callsInPlace(action, InvocationKind.AT_LEAST_ONCE) }
do { action(value) } while(true)
}

public inline fun AtomicLong.tryUpdate(function: (Long) -> Long): Boolean = tryUpdate(function) { _, _ -> }
public inline fun AtomicLong.tryUpdate(function: (Long) -> Long): Boolean {
contract { callsInPlace(function, InvocationKind.EXACTLY_ONCE) }
return tryUpdate(function) { _, _ -> }
}

public inline fun AtomicLong.update(function: (Long) -> Long): Unit = update(function) { _, _ -> }
public inline fun AtomicLong.update(function: (Long) -> Long) {
contract { callsInPlace(function, InvocationKind.AT_LEAST_ONCE) }
update(function) { _, _ -> }
}

/**
* Updates variable atomically using the specified [function] of its value and returns its old value.
*/
public inline fun AtomicLong.getAndUpdate(function: (Long) -> Long): Long = update(function) { old, _ -> old }
public inline fun AtomicLong.getAndUpdate(function: (Long) -> Long): Long {
contract { callsInPlace(function, InvocationKind.AT_LEAST_ONCE) }
return update(function) { old, _ -> old }
}

/**
* Updates variable atomically using the specified [function] of its value and returns its new value.
*/
public inline fun AtomicLong.updateAndGet(function: (Long) -> Long): Long = update(function) { _, new -> new }
public inline fun AtomicLong.updateAndGet(function: (Long) -> Long): Long {
contract { callsInPlace(function, InvocationKind.AT_LEAST_ONCE) }
return update(function) { _, new -> new }
}

@PublishedApi
internal inline fun <R> AtomicLong.update(function: (Long) -> Long, transform: (old: Long, new: Long) -> R): R {
while (true) {
tryUpdate(function) { old, new -> return transform(old, new) }
contract {
callsInPlace(function, InvocationKind.AT_LEAST_ONCE)
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}
loop { cur ->
val upd = function(value)
if(compareAndSet(cur, upd)) return transform(cur, upd)
}
}

@PublishedApi
internal inline fun AtomicLong.tryUpdate(function: (Long) -> Long, onUpdated: (old: Long, new: Long) -> Unit): Boolean {
contract {
callsInPlace(function, InvocationKind.EXACTLY_ONCE)
callsInPlace(onUpdated, InvocationKind.AT_MOST_ONCE)
}
val cur = value
val upd = function(cur)
return compareAndSet(cur, upd).also { if (it) onUpdated(cur, upd) }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
@file:OptIn(ExperimentalContracts::class)

package arrow

import arrow.atomic.Atomic
import arrow.atomic.update
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.coroutines.cancellation.CancellationException

/**
Expand Down Expand Up @@ -61,6 +66,7 @@ import kotlin.coroutines.cancellation.CancellationException
* <!--- KNIT example-autocloseable-02.kt -->
*/
public inline fun <A> autoCloseScope(block: AutoCloseScope.() -> A): A {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
val scope = DefaultAutoCloseScope()
var throwable: Throwable? = null
return try {
Expand Down
Loading

0 comments on commit a203862

Please sign in to comment.