Last active
December 13, 2020 17:57
-
-
Save Aidanvii7/5c80a6008a934a95b579a307db8e19bb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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() | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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