Skip to content

Instantly share code, notes, and snippets.

@Aidanvii7
Last active December 13, 2020 17:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Aidanvii7/5c80a6008a934a95b579a307db8e19bb to your computer and use it in GitHub Desktop.
Save Aidanvii7/5c80a6008a934a95b579a307db8e19bb to your computer and use it in GitHub Desktop.
@file:OptIn(ExperimentalTypeInference::class)
@file:Suppress("unused")
import kotlin.experimental.ExperimentalTypeInference
inline fun <reified R> R.unless(@BuilderInference block: Guarded<R>.() -> Any?): R =
try {
val result = Guarded<R>().block()
result as? R ?: this
} catch (e: Guarded.BreakGuardWithResult) {
e.result as R
}
inline fun guardBlock(@BuilderInference block: Guard.() -> Unit) {
try {
Guard().block()
} catch (e: Guard.BreakGuard) {
// ignored
}
}
inline fun <reified R> guard(@BuilderInference block: Guarded<R>.() -> R): R =
try {
Guarded<R>().block()
} catch (e: Guarded.BreakGuardWithResult) {
e.result as R
}
private val IGNORED = Any()
inline class Guarded<R>(private val ignored: Any = IGNORED) {
inline fun breakGuardWith(@BuilderInference provider: () -> R): Nothing = throw BreakGuardWithResult(provider())
class BreakGuardWithResult(val result: Any?) : Throwable()
}
inline class Guard(private val ignored: Any = IGNORED) {
fun breakGuard(): Nothing = throw BreakGuard
object BreakGuard : Throwable()
}
inline fun <R> guard(
onGuardBreak: () -> R,
@BuilderInference block: Guard.() -> R,
): R =
try {
Guard().block()
} catch (ignored: Guard.BreakGuard) {
onGuardBreak()
}
inline fun Guard.breakGuardAlso(block: () -> Unit): Nothing {
block()
breakGuard()
}
import org.amshove.kluent.`should be equal to`
import org.amshove.kluent.`should be false`
import org.amshove.kluent.`should be null`
import org.amshove.kluent.`should be true`
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
internal class GuardUtilsTest {
@Test
@DisplayName("breaks guard with result on failure")
fun guardResultTest() {
var banana: String? = null
val apple: String? = null
val result = guard {
banana ?: breakGuardWith { "banana was null" }
banana = "banana" // should never reach
apple ?: breakGuardWith { "apple was null" } // should never reach
"$banana:$apple" // should never reach
}
result `should be equal to` "banana was null"
banana.`should be null`()
}
@Test
@DisplayName("breaks guard with result on failure in nested guard function")
fun guardResultNestedTest() {
var banana: String? = null
val apple: String? = null
val orange: String? = null
val result = guard {
val result = getResult(banana, apple, orange)
banana = "banana" // should never reach
result // should never reach
}
result `should be equal to` "fruit was null"
banana.`should be null`()
}
private fun Guarded<String>.getResult(vararg nullableFruitNames: String?): String {
return nullableFruitNames.map { nullableValue ->
nullableValue ?: breakGuardWith { "fruit was null" }
}.let { values ->
buildString {
values.forEach { value ->
append("$value:")
}
}
}
}
@Test
@DisplayName("breaks guard on failure")
fun guardBlockTest() {
var banana: String? = null
guardBlock {
breakGuard()
banana = "banana" // should never reach
}
banana.`should be null`()
}
@Test
@DisplayName("breaks guard on failure in nested guard function")
fun guardBlockNestedTest() {
var banana: String? = null
guardBlock {
forceBreak()
banana = "banana" // should never reach
}
banana.`should be null`()
}
private fun Guard.forceBreak() {
breakGuard()
}
@Test
@DisplayName("guard unless completes normally when no guard is thrown")
fun guardUnlessNormalTest() {
true.unless {
// complete normally without a result
}.`should be true`()
}
@Test
@DisplayName("guard unless completes with different value when the return value type matches the receiver type")
fun guardUnlessReturnsTest() {
true.unless {
false // complete normally with a result
}.`should be false`()
}
@Test
@DisplayName("guard unless completes with break value and stops execution")
fun guardUnlessBreaksTest() {
var banana: String? = null
1.unless {
breakGuardWith { 2 } // complete exceptionally with a result
banana = "banana" // should never reach
3 // should never reach
} `should be equal to` 2
banana.`should be null`()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment