Skip to content

Commit

Permalink
Merge pull request #2 from leviysoft/feature/coroutine-compat
Browse files Browse the repository at this point in the history
Coroutines compatibility module
  • Loading branch information
danslapman authored Sep 24, 2024
2 parents ba9211d + a1efef4 commit b04b8d5
Show file tree
Hide file tree
Showing 12 changed files with 144 additions and 4 deletions.
32 changes: 29 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,39 @@ inThisBuild(
)
)

lazy val root = (project in file("."))
lazy val core = (project in file("core"))
.enablePlugins(KotlinPlugin)
.settings(
organization := "io.github.leviysoft",
name := "scala-kotlin-compat",
kotlinVersion := "1.9.25",
kotlincJvmTarget := "11",
kotlincOptions += "-Xjvm-default=all",
kotlinLib("stdlib")
)

lazy val coroutines = (project in file("coroutines"))
.enablePlugins(KotlinPlugin)
.settings(
organization := "io.github.leviysoft",
name := "scala-kotlin-compat",
name := "scala-kotlin-coroutines-compat",
kotlinVersion := "1.9.25",
kotlincJvmTarget := "11",
kotlincOptions += "-Xjvm-default=all",
kotlinLib("stdlib")
kotlinLib("stdlib"),
libraryDependencies ++= Seq(
"org.jetbrains.kotlinx" % "kotlinx-coroutines-core" % "1.8.1",
"com.github.sbt" % "junit-interface" % "0.13.3" % Test,
"org.jetbrains.kotlin" % "kotlin-test-junit" % kotlinVersion.value % Test
)
)

lazy val root = (project in file("."))
.dependsOn(core, coroutines)
.aggregate(core, coroutines)
.settings(
crossScalaVersions := Nil,
publish := {},
publishArtifact := false,
publish / skip := true
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
@file:OptIn(InternalCoroutinesApi::class, ExperimentalCoroutinesApi::class)

package com.github.leviysoft.sk.coroutines

import kotlinx.coroutines.*

import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import scala.concurrent.ExecutionContext
import scala.concurrent.Future
import scala.concurrent.Promise
import scala.util.Failure
import scala.util.Success
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

fun <T> CoroutineScope.scalaFuture(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
) : Future<T> {
require(!start.isLazy) { "$start start is not supported" }
val newContext = this.newCoroutineContext(context)
val promise = Promise.apply<T>()
val coroutine = PromiseCoroutine(newContext, promise)
coroutine.start(start, coroutine, block)

return promise.future()
}

fun <T> Deferred<T>.asScalaFuture(): Future<T> {
val promise: Promise<T> = Promise.apply()

invokeOnCompletion {
try {
promise.success(getCompleted())
} catch (t: Throwable) {
promise.failure(t)
}
}

return promise.future()
}

fun Job.asScalaFuture(): Future<scala.runtime.BoxedUnit> {
val promise: Promise<scala.runtime.BoxedUnit> = Promise.apply()
invokeOnCompletion { cause ->
if (cause === null) promise.success(scala.runtime.BoxedUnit.UNIT)
else promise.failure(cause)
}
return promise.future()
}

fun <T> Future<T>.asDeferred(executor: ExecutionContext): Deferred<T> {
val result = CompletableDeferred<T>()
this.onComplete({ res ->
when(res) {
is Success -> result.complete(res.value())
is Failure -> result.completeExceptionally(res.exception())
else -> throw IllegalStateException("Unreachable")
}
}, executor)
return result
}

suspend fun <T> Future<T>.await(executor: ExecutionContext): T {
return suspendCancellableCoroutine { cont: CancellableContinuation<T> ->
this.onComplete({ res ->
when(res) {
is Success -> cont.resume(res.value())
is Failure -> cont.resumeWithException(res.exception())
else -> throw IllegalStateException("Unreachable")
}
}, executor)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.github.leviysoft.sk.coroutines

import kotlinx.coroutines.AbstractCoroutine
import kotlinx.coroutines.InternalCoroutinesApi
import scala.concurrent.Promise
import kotlin.coroutines.CoroutineContext

@OptIn(InternalCoroutinesApi::class)
internal class PromiseCoroutine<T>(context: CoroutineContext, private val promise: Promise<T>): AbstractCoroutine<T>(context, initParentJob = true, active = true) {
override fun onCompleted(value: T) {
this.promise.success(value)
}

override fun onCancelled(cause: Throwable, handled: Boolean) {
this.promise.failure(cause)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.github.leviysoft.sk.coroutines

import kotlinx.coroutines.runBlocking
import scala.concurrent.ExecutionContext
import scala.concurrent.`Future$`
import kotlin.test.Test
import kotlin.test.assertEquals

class FutureConversionTests {
@Test
fun awaitFutureTest() {
val future = `Future$`.`MODULE$`.successful(42)

val result = runBlocking {
future.await(ExecutionContext.global())
}

assertEquals(42, result)
}
}
3 changes: 2 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ The following components of `scala.jdk` are implemented:
- [x] FutureConverters
- [x] OptionConverters
- [ ] StreamConverters


In addition, `scala-kotlin-coroutines-compat` provides utilities for calling `suspend fun`s as `Future`s

0 comments on commit b04b8d5

Please sign in to comment.