Skip to content

Instantly share code, notes, and snippets.

@belyaev-mikhail
Created August 23, 2021 16:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save belyaev-mikhail/ad7ca72ffd8ed7dcee24cbb648ed2887 to your computer and use it in GitHub Desktop.
Save belyaev-mikhail/ad7ca72ffd8ed7dcee24cbb648ed2887 to your computer and use it in GitHub Desktop.
package ru.spbstu.wheels
import kotlinx.warnings.Warnings
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.properties.ReadOnlyProperty
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
abstract class GetterAndSetterBuilderEmpty<T> {
@PublishedApi
internal var getter: (() -> T)? = null
@PublishedApi
internal var setter: ((T) -> Unit)? = null
object DoneMarker
}
@OptIn(ExperimentalContracts::class)
fun <T> GetterAndSetterBuilderEmpty<T>.set(body: (T) -> Unit) {
contract { returns() implies (this@set is SetCalled) }
setter = body
}
@OptIn(ExperimentalContracts::class)
fun <T> GetterAndSetterBuilderEmpty<T>.get(body: () -> T) {
contract { returns() implies (this@get is GetCalled) }
getter = body
}
@Deprecated("You must call both get{} and set{} before signaling that you're done",
level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("get {}\nset {}"))
inline val <T> GetterAndSetterBuilderEmpty<T>.done: GetterAndSetterBuilderEmpty.DoneMarker
get() = GetterAndSetterBuilderEmpty.DoneMarker
inline val <GS, T> GS.done: GetterAndSetterBuilderEmpty.DoneMarker where GS: GetterAndSetterBuilderEmpty<T>, GS: GetCalled, GS: SetCalled
get() = GetterAndSetterBuilderEmpty.DoneMarker
interface GetCalled {
@Deprecated("Should only call get{} once", level = DeprecationLevel.ERROR)
fun <T> get(body: () -> T) {}
}
interface SetCalled {
@Deprecated("Should only call set{} once", level = DeprecationLevel.ERROR)
fun <T> set(body: (T) -> Unit) {}
}
class GetterAndSetterBuilder<T>: GetterAndSetterBuilderEmpty<T>(), GetCalled, SetCalled
object Delegates {
inline fun <T> getAndSet(crossinline body: GetterAndSetterBuilderEmpty<T>.() -> GetterAndSetterBuilderEmpty.DoneMarker) = object : ReadWriteProperty<Any?, T> {
val builder = GetterAndSetterBuilder<T>().apply{ body() }
init {
check(builder.getter != null) { "You need to call get {}" }
check(builder.setter != null) { "You need to call set {}" }
}
val getter = builder.getter!!
val setter = builder.setter!!
override fun getValue(thisRef: Any?, property: KProperty<*>): T = getter()
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) = setter(value)
}
inline fun <T> get(crossinline body: () -> T) = ReadOnlyProperty<Any?, T> { _, _ -> body() }
}
sealed class OneShot<out T> {
internal abstract val value: T
class Value<out T>(override val value: T): OneShot<T>()
object Dead: OneShot<Nothing>() {
@Deprecated("Should only call get() once", level = DeprecationLevel.ERROR,
replaceWith = ReplaceWith("")
)
fun get(): Nothing = value
override val value: Nothing
get() = error("")
}
}
@OptIn(ExperimentalContracts::class)
fun <T> OneShot<T>.get(): T {
contract { returns() implies (this@get is OneShot.Dead) }
return value
}
fun <T> OneShot(value: T): OneShot<T> = OneShot.Value(value)
interface SimpleDelegate<T> {
fun get(): T
fun set(value: T)
}
inline operator fun <T, D: SimpleDelegate<T>> D.getValue(thisRef: Any?, property: KProperty<*>): T =
get()
inline operator fun <T, D: SimpleDelegate<T>> D.setValue(thisRef: Any?, property: KProperty<*>, value: T) =
set(value)
class LateInit<T>: SimpleDelegate<T> {
companion object {
private val UNINIT = Any()
}
private var actualVar: Any? = UNINIT
override fun get(): T =
@Suppress(Warnings.UNCHECKED_CAST)
if (actualVar == UNINIT) throw IllegalStateException("Lateinit property is not initialized")
else actualVar as T
override fun set(value: T) {
if (actualVar != UNINIT) throw IllegalStateException("Lateinit property assigned twice")
actualVar = value
}
}
fun main() {
var x: String by Delegates.getAndSet {
set { }
get { "" }
//get { "" }
done
}
val ff = OneShot(2)
ff.get()
//ff.get()
var xx: Int by LateInit()
xx = 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment