From 2a29e4cc88ae70aa0f4d7dde92a2c56b918db74c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Mon, 3 Jun 2024 09:11:25 +0200 Subject: [PATCH] k2 + non-fatal catching --- .gitignore | 1 + CHANGELOG.md | 7 +++- build.gradle.kts | 37 +++++++++---------- gradle.properties | 4 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../kotlin/at/asitplus/KmmResult.kt | 22 ++++++++++- src/commonTest/kotlin/KmmResultTest.kt | 27 ++++++++++++++ 7 files changed, 75 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 5a0d13b..44c4dc2 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ build/ local.properties !**/src/main/**/build/ !**/src/test/**/build/ +.kotlin ### STS ### .apt_generated diff --git a/CHANGELOG.md b/CHANGELOG.md index a49f91c..e76467b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,4 +26,9 @@ ### 1.5.4 - Add `transform()` function to map results without nesting - Add `mapCatching()` -- Implement `equals()` and `hashCode()` \ No newline at end of file +- Implement `equals()` and `hashCode()` + +## 1.6.0 +- Kotlin 2.0 +- Failure re-throws any fatal and coroutine-related exceptions +- `catching` function, modelling stdlib's `runCatching`, directly returning a `KmmResult` \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 3dec3b9..42d879e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,13 +2,13 @@ import io.gitlab.arturbosch.detekt.Detekt import org.gradle.kotlin.dsl.support.listFilesOrdered plugins { - kotlin("multiplatform") version "1.9.10" + kotlin("multiplatform") version "2.0.0" id("maven-publish") id("signing") id("io.github.gradle-nexus.publish-plugin") version "1.3.0" - id("org.jetbrains.dokka") version "1.8.20" - id("org.jetbrains.kotlinx.kover") version "0.7.3" - id("io.gitlab.arturbosch.detekt") version "1.23.1" + id("org.jetbrains.dokka") version "1.9.20" + id("org.jetbrains.kotlinx.kover") version "0.8.0" + id("io.gitlab.arturbosch.detekt") version "1.23.6" } val artifactVersion: String by extra @@ -108,15 +108,12 @@ kotlin { xcf.add(this) } } - ios() { - binaries.framework { - baseName = "KmmResult" - embedBitcode("bitcode") - xcf.add(this) - } - } - iosSimulatorArm64 { - binaries.framework { + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64() + ).forEach { + it.binaries.framework { baseName = "KmmResult" embedBitcode("bitcode") xcf.add(this) @@ -134,6 +131,7 @@ kotlin { } withJava() //for Java Interop tests } + js(IR) { browser { testTask { enabled = false } } nodejs() @@ -143,16 +141,17 @@ kotlin { mingwX64() sourceSets { - @Suppress("UNUSED_VARIABLE") val commonMain by getting + commonMain.dependencies { + implementation("io.arrow-kt:arrow-core:1.2.4") + } - @Suppress("UNUSED_VARIABLE") val commonTest by getting { - dependencies { - implementation(kotlin("test")) - } + commonTest.dependencies { + implementation(kotlin("test")) } } + tasks.withType().configureEach { reports { xml.required.set(true) @@ -164,7 +163,7 @@ kotlin { } dependencies { - detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.21.0") + detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.6") } repositories { diff --git a/gradle.properties b/gradle.properties index 5276ac4..83f03d6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,6 @@ kotlin.code.style=official kotlin.mpp.enableCInteropCommonization=true -kotlin.native.binary.memoryModel=experimental -kotlin.native.binary.freezing=disabled kotlin.mpp.stability.nowarn=true kotlin.native.ignoreDisabledTargets=true -artifactVersion = 1.5.4 \ No newline at end of file +artifactVersion = 1.6.0 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37aef8d..2617362 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/commonMain/kotlin/at/asitplus/KmmResult.kt b/src/commonMain/kotlin/at/asitplus/KmmResult.kt index bbc72b6..f987552 100644 --- a/src/commonMain/kotlin/at/asitplus/KmmResult.kt +++ b/src/commonMain/kotlin/at/asitplus/KmmResult.kt @@ -6,19 +6,31 @@ package at.asitplus +import arrow.core.nonFatalOrThrow +import at.asitplus.KmmResult.Companion.wrap import kotlin.experimental.ExperimentalObjCRefinement import kotlin.jvm.JvmStatic import kotlin.native.HiddenFromObjC /** + * Swift-Friendly variant of stdlib's [Result]. * For easy use under iOS, we need a class like `Result` * that is not a `value` class (which is unsupported in Kotlin/Native) + * + * Trying to create a failure case + * re-throws any fatal exceptions, such as `OutOfMemoryError`. + * Relies on [Arrow](https://arrow-kt.io)'s [nonFatalOrThrow](https://apidocs.arrow-kt.io/arrow-core/arrow.core/non-fatal-or-throw.html) internally. */ -class KmmResult private constructor( +class KmmResult +private constructor( private val delegate: Result, @Suppress("UNUSED_PARAMETER") unusedButPreventsSignatureClashes: Boolean ) { + init { + delegate.exceptionOrNull()?.nonFatalOrThrow() + } + /** * Creates a success result from the given [value] */ @@ -26,6 +38,8 @@ class KmmResult private constructor( /** * Creates a failure result from the given [failure] + * Trying to create a failure case re-throws any fatal exceptions, such as `OutOfMemoryError`. + * Relies on [Arrow](https://arrow-kt.io)'s [nonFatalOrThrow](https://apidocs.arrow-kt.io/arrow-core/arrow.core/non-fatal-or-throw.html) internally. */ constructor(failure: Throwable) : this(Result.failure(failure), false) @@ -182,3 +196,9 @@ class KmmResult private constructor( fun Result.wrap(): KmmResult = KmmResult(this, false) } } + +/** + * Non-fatal-only-catching version of stdlib's [runCatching], directly returning a [KmmResult]- + * Re-throws any fatal exceptions, such as `OutOfMemoryError`. Relies on [Arrow](https://arrow-kt.io)'s [nonFatalOrThrow](https://apidocs.arrow-kt.io/arrow-core/arrow.core/non-fatal-or-throw.html) internally. + */ +inline fun catching(block: () -> R): KmmResult = runCatching { block() }.wrap() diff --git a/src/commonTest/kotlin/KmmResultTest.kt b/src/commonTest/kotlin/KmmResultTest.kt index 311a3ac..5e6e67f 100644 --- a/src/commonTest/kotlin/KmmResultTest.kt +++ b/src/commonTest/kotlin/KmmResultTest.kt @@ -2,10 +2,37 @@ package at.asitplus import at.asitplus.KmmResult.Companion.success import at.asitplus.KmmResult.Companion.wrap +import kotlin.coroutines.cancellation.CancellationException import kotlin.test.* class KmmResultTest { + @Test + fun testCatching() { + assertFailsWith(CancellationException::class) { + catching { throw CancellationException("just a test", null) } + } + + assertFailsWith(CancellationException::class) { + runCatching { throw CancellationException("just a test", null) }.wrap() + } + + assertFailsWith(CancellationException::class) { + KmmResult.failure(CancellationException("just a test", null)) + } + assertFailsWith(CancellationException::class) { + Result.failure(CancellationException("just a test", null)).wrap() + } + + runCatching { throw CancellationException("just a test", null) } + Result.failure(CancellationException("just a test", null)) + catching { throw NullPointerException("just a test") } + runCatching { throw NullPointerException("just a test") }.wrap() + KmmResult.failure(NullPointerException("just a test")) + Result.failure(NullPointerException("just a test")).wrap() + + } + @Test fun testMap() { assertEquals("1234", KmmResult.success(1234).map { it.toString() }.getOrThrow())