Compare commits

...

16 Commits
v0.2 ... master

Author SHA1 Message Date
7d947fb9e3 Missed '@JvmStatic' on 'addSuppressed' 2025-03-26 04:33:20 +03:00
0b241ea7e2 Removed dependency on 'kotlin-dom-api-compat' in js target 2025-03-26 02:12:26 +03:00
25cb516d0f Some improvements in tests 2025-03-26 01:53:54 +03:00
099c3fad26 v0.5 2025-03-25 23:17:54 +03:00
28ab658dd2 Testing tryFinallyChain seems untrivial so it still require kotlin stdlib 2025-03-25 22:34:55 +03:00
529508bf74 Tests to check kotlin stdlib dependencies and some improvements in gradle 2025-03-25 22:20:45 +03:00
758bc31a85 Replaced overriding kotlin stdlib classes by expect/actual with non-JVM source set 2025-03-25 21:32:18 +03:00
c154fc7610 Attempt to remove dependency on kotlin's stdlib in jvm target 2025-03-25 09:24:14 +03:00
ee93e0b5b3 Java's target compatibility 1.8 2025-03-25 07:53:45 +03:00
88c8da21e4 'tryFinallyChain' now passes context as argument instead of receiver to simplify access to parent receiver 2025-03-22 15:40:00 +03:00
ef1d72ca10 Extracted to separate library, 'tryFinallyChain' 2025-03-22 14:29:45 +03:00
87386193ee [history] Fixed error when 'onCrossReturn' and 'onSuccess' called together 2025-03-20 02:41:03 +03:00
b9e02d68f8 [history] Minor warning fixes 2025-03-19 03:47:37 +03:00
b2a5ac9292 [history] Added functions that passes thrown exception to 'onError' block 2025-03-19 00:37:46 +03:00
6f9d3bdd32 [history] Added contracts and changed names to avoid ambiguous calls 2025-03-18 20:28:29 +03:00
586e825693 [history] Error in crossreturn check was replaced with lambda, shorten versions of function 2025-03-18 19:36:25 +03:00
11 changed files with 320 additions and 6 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
/.idea/
gradle/
.gradle/
build/
*.class
*.jar
/out/
/gradlew*
.kotlin/

101
build.gradle.kts Normal file
View File

@ -0,0 +1,101 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import ru.landgrafhomyak.kotlin.kmp_gradle_build_helper.*
import ru.landgrafhomyak.kotlin.kmp_gradle_build_helper.plugin.xomrk
buildscript {
repositories {
mavenCentral()
maven("https://maven.landgrafhomyak.ru/")
}
dependencies {
classpath("ru.landgrafhomyak.kotlin:kotlin-mpp-gradle-build:v0.3k2.1.10")
}
}
group = "ru.landgrafhomyak.utility"
version = "0.5"
repositories {
mavenCentral()
}
xomrk {
kotlin {
setCompatibilityWithKotlin(KotlinVersion.KOTLIN_2_0)
optInContracts()
noWarnExpectActual()
warningsAsErrors()
defineAllMultiplatformTargets()
jvmToolchain(8)
jvm {
withJava()
compilations.configureEach {
compileJavaTaskProvider?.configure {
targetCompatibility = "1.8"
}
compileTaskProvider.configure {
compilerOptions {
jvmTarget = JvmTarget.JVM_1_8
}
}
}
tasks.named { t -> t == "${this@jvm.name}Test" }.configureEach {
this as Test
useTestNG()
}
}
sourceSets {
// if use kotlin("stdlib") gitea ui brokes at paragraph with dependency versions
val kotlinStdlibDependency = "org.jetbrains.kotlin:kotlin-stdlib:${this@kotlin.coreLibrariesVersion}"
val commonMain by getting {
dependencies {
compileOnly(kotlinStdlibDependency)
}
}
val jvmMain by getting {
dependsOn(commonMain)
dependencies {
compileOnly(kotlinStdlibDependency)
}
}
val nonJvmMain by creating {
dependsOn(commonMain)
dependencies {
implementation(kotlinStdlibDependency)
}
}
jvmTest {
dependencies {
implementation("org.testng:testng:7.5.1")
}
}
configureEach {
when {
// commonMain !in dependsOn -> return@configureEach
!name.endsWith("Main") -> return@configureEach
this@configureEach === commonMain -> return@configureEach
this@configureEach === jvmMain -> return@configureEach
this@configureEach === nonJvmMain -> return@configureEach
}
dependsOn(nonJvmMain)
}
}
}
publishing {
repositories {
defineXomrkGiteaMavenRepo()
}
}
}

6
gradle.properties Normal file
View File

@ -0,0 +1,6 @@
kotlin.stdlib.default.dependency=false
kotlin.mpp.applyDefaultHierarchyTemplate=false
kotlin.native.enableKlibsCrossCompilation=true
# compileOnly dependencies from commonMain still throw warning
kotlin.suppressGradlePluginWarnings=IncorrectCompileOnlyDependencyWarning
kotlin.js.stdlib.dom.api.included=false

1
settings.gradle.kts Normal file
View File

@ -0,0 +1 @@
rootProject.name = "highlevel-try-finally"

View File

@ -0,0 +1,7 @@
package ru.landgrafhomyak.utility.highlevel_try_finally
@PublishedApi
internal expect object ExceptionsKt {
@PublishedApi
internal fun addSuppressed(e1: Throwable?, e2: Throwable?)
}

View File

@ -0,0 +1,26 @@
package ru.landgrafhomyak.utility.highlevel_try_finally
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
class TryFinallyChainScope @PublishedApi internal constructor() {
@PublishedApi
internal var _actualException: Throwable? = null
@PublishedApi
internal fun _throw() {
this._actualException?.let { e -> throw e }
}
inline fun action(fn: () -> Unit): TryFinallyChainScope {
contract {
callsInPlace(fn, InvocationKind.EXACTLY_ONCE)
}
safeAutoClose3e(
onCrossReturn = { throw Error("Cross return not allowed in tryFinallyChain{action{}}") },
onError = { err -> this._actualException?.addSuppressed(err) ?: run { this._actualException = err } },
action = fn
)
return this
}
}

View File

@ -1,10 +1,80 @@
@file:Suppress("unused")
@file:JvmName("SafeAutocloseKt")
package ru.landgrafhomyak.utility.highlevel_try_finally
fun <R> safeAutoClose(
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.jvm.JvmName
@Suppress("WRONG_INVOCATION_KIND")
inline fun <R> safeAutoClose1(
finally: () -> Unit,
action: () -> R
): R {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
callsInPlace(finally, InvocationKind.EXACTLY_ONCE)
}
return safeAutoClose3(onError = finally, onSuccess = finally, onCrossReturn = finally, action = action)
}
@Suppress("WRONG_INVOCATION_KIND")
inline fun <R> safeAutoClose2(
onError: () -> Unit = {},
onSuccess: () -> Unit = {},
action: () -> R
): R {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
callsInPlace(onError, InvocationKind.AT_MOST_ONCE)
callsInPlace(onSuccess, InvocationKind.AT_MOST_ONCE)
}
return safeAutoClose3(onError = onError, onSuccess = onSuccess, onCrossReturn = onSuccess, action = action)
}
@Suppress("WRONG_INVOCATION_KIND")
inline fun <R> safeAutoClose2e(
onError: (Throwable) -> Unit = { _ -> },
onSuccess: () -> Unit = {},
action: () -> R
): R {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
callsInPlace(onError, InvocationKind.AT_MOST_ONCE)
callsInPlace(onSuccess, InvocationKind.AT_MOST_ONCE)
}
return safeAutoClose3e(onError = onError, onSuccess = onSuccess, onCrossReturn = onSuccess, action = action)
}
inline fun <R> safeAutoClose3(
onError: () -> Unit = {},
onSuccess: () -> Unit = {},
onCrossReturn: () -> Unit = {},
action: () -> R
): R {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
callsInPlace(onError, InvocationKind.AT_MOST_ONCE)
callsInPlace(onSuccess, InvocationKind.AT_MOST_ONCE)
callsInPlace(onCrossReturn, InvocationKind.AT_MOST_ONCE)
}
return safeAutoClose3e(onError = { t -> onError() }, onSuccess = onSuccess, onCrossReturn = onCrossReturn, action = action)
}
inline fun <R> safeAutoClose3e(
onError: (Throwable) -> Unit = { _ -> },
onSuccess: () -> Unit = {},
onCrossReturn: () -> Unit = {},
action: () -> R
): R {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
callsInPlace(onError, InvocationKind.AT_MOST_ONCE)
callsInPlace(onSuccess, InvocationKind.AT_MOST_ONCE)
callsInPlace(onCrossReturn, InvocationKind.AT_MOST_ONCE)
}
val ret: R
var wasError = false
var crossReturned = true
@ -14,17 +84,18 @@ fun <R> safeAutoClose(
} catch (e1: Throwable) {
wasError = true
try {
onError()
onError(e1)
} catch (e2: Throwable) {
e1.addSuppressed(e2)
ExceptionsKt.addSuppressed(e1, e2)
}
throw e1
} finally {
if (!wasError) {
if (crossReturned)
throw Error("crossreturn")
onSuccess()
onCrossReturn()
else
onSuccess()
}
}
return ret
}
}

View File

@ -0,0 +1,22 @@
@file:Suppress("unused")
@file:JvmName("TryFinallyChainKt")
package ru.landgrafhomyak.utility.highlevel_try_finally
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.jvm.JvmName
inline fun tryFinallyChain(chains: (TryFinallyChainScope) -> Unit) {
contract {
callsInPlace(chains, InvocationKind.EXACTLY_ONCE)
}
val scope = TryFinallyChainScope()
safeAutoClose3e(
onCrossReturn = { throw Error("Cross return not allowed in tryFinallyChain{}") },
onError = { err -> throw Error("Unexpected exception in tryFinallyChain{}; calling anything outside action{} block isn't allowed", err) },
onSuccess = { scope._throw() },
action = { chains(scope) }
)
}

View File

@ -0,0 +1,12 @@
package ru.landgrafhomyak.utility.highlevel_try_finally
@PublishedApi
internal actual object ExceptionsKt {
@PublishedApi
@JvmStatic
internal actual fun addSuppressed(e1: Throwable?, e2: Throwable?) {
if (e1 == null) throw NullPointerException("e1")
if (e2 == null) throw NullPointerException("e2")
(java.lang.Throwable::addSuppressed)(e1 as java.lang.Throwable, e2)
}
}

View File

@ -0,0 +1,48 @@
package ru.landgrafhomyak.utility.highlevel_try_finally.tests
import org.testng.annotations.Test
import ru.landgrafhomyak.utility.highlevel_try_finally.safeAutoClose1
@Test
class KotlinStdlibDependencyTest {
@Test
fun testNoKotlinStdlib() {
try {
if (KotlinVersion.CURRENT.major != -1)
throw AssertionError("Kotlin stdlib still in runtime classpath")
} catch (_: LinkageError) {
}
}
private class CustomTestException : RuntimeException()
private object CustomUnit
private fun throw7throwFn() {
safeAutoClose1(finally = { throw CustomTestException() }, action = { throw CustomTestException() })
}
@Suppress("NOTHING_TO_INLINE")
private inline fun throw7throwIn() {
safeAutoClose1(finally = { throw CustomTestException() }, action = { throw CustomTestException() })
}
@Test(dependsOnMethods = ["testNoKotlinStdlib"])
fun testAutoCloseFn() {
try {
throw7throwFn()
} catch (_: CustomTestException) {
} catch (le: LinkageError) {
throw AssertionError("safeAutoClose still has dependency on kotlin stdlib", le)
}
}
@Test(dependsOnMethods = ["testNoKotlinStdlib"])
fun testAutoCloseIn() {
try {
throw7throwIn()
} catch (_: CustomTestException) {
} catch (le: LinkageError) {
throw AssertionError("safeAutoClose still has dependency on kotlin stdlib", le)
}
}
}

View File

@ -0,0 +1,11 @@
package ru.landgrafhomyak.utility.highlevel_try_finally
import kotlin.addSuppressed as kotlinAddSuppressed
@PublishedApi
internal actual object ExceptionsKt {
@PublishedApi
internal actual fun addSuppressed(e1: Throwable?, e2: Throwable?) {
e1!!.kotlinAddSuppressed(e2!!)
}
}