Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,15 @@ jobs:
curl.exe -L https://repo1.maven.org/maven2/com/lihaoyi/mill-dist/1.1.5/mill-dist-1.1.5-mill.bat -o mill.bat
./mill version
- name: Build
run: ./mill __.compile
run: |
./mill __.jvm.compile
./mill __.js.compile
./mill __.native.compile
- name: Test
run: ./mill __.test
run: |
./mill __.jvm.test
./mill __.js.test
./mill __.native.test
- name: Sample
run: >
./mill --no-daemon
Expand Down
4 changes: 2 additions & 2 deletions build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import com.github.lolgab.mill.mima._

val scala3 = "3.3.7"
val scalaJS = "1.20.2"
val scalaNative = "0.5.10"
val upickle = "3.3.0"
val scalaNative = "0.5.9"
val upickle = "4.4.3"
val utest = "0.9.5"

trait CommonTestModule extends TestModule.Utest {
Expand Down
1 change: 0 additions & 1 deletion spores3/src-jvm/spores/jvm/Spore.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package spores.jvm
import upickle.default.*

import spores.*
import spores.Packed.*


/** Internal API. Used by the [[spores.SporeObjectCompanionJVM]] factories.
Expand Down
7 changes: 3 additions & 4 deletions spores3/src/spores/Env.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package spores
import upickle.default.*

import spores.*
import spores.Packed.*


/** A factory for packing environment values of type `T` into `Spore[T]` by
Expand All @@ -15,14 +14,14 @@ object Env {
*
* @param env
* The value to pack.
* @param rw
* @param ev
* The implicit `Spore[ReadWriter[T]]` used for packing the `env`.
* @tparam T
* The type of the value to pack.
* @return
* A new `Spore[T]` with the packed `env`.
*/
def apply[T](env: T)(using rw: Spore[ReadWriter[T]]): Spore[T] = {
PackedEnv(write(env)(using rw.unwrap()), rw)
def apply[T](env: T)(using ev: Spore[ReadWriter[T]]): Spore[T] = {
AST.Val(ev, env)
}
}
90 changes: 60 additions & 30 deletions spores3/src/spores/ReadWriters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package spores
import upickle.default.*

import spores.*
import spores.Packed.*


/** A collection of ReadWriters. Contains both `ReadWriter[Spore[T]]` and
Expand All @@ -23,13 +22,65 @@ object ReadWriters {
// ReadWriter[Spore[T]]
//////////////////////////////////////////////////////////////////////////////

given [T]: ReadWriter[Spore[T]] = macroRW
given [T]: ReadWriter[PackedObject[T]] = macroRW
given [T]: ReadWriter[PackedClass[T]] = macroRW
given [T]: ReadWriter[PackedLambda[T]] = macroRW
given [E]: ReadWriter[PackedEnv[E]] = macroRW
given [E, T]: ReadWriter[PackedWithEnv[E, T]] = macroRW
given [E, T]: ReadWriter[PackedWithCtx[E, T]] = macroRW
given [T]: ReadWriter[Spore[T]] =
summon[ReadWriter[ujson.Value]].bimap[Spore[T]](
sp => {
sp match {
case AST.Body(kind, className, _) =>
ujson.Obj(
"tag" -> ujson.Str("Body"),
"kind" -> ujson.Num(kind),
"className" -> ujson.Str(className),
)
case AST.Val(ev, value) =>
ujson.Obj(
"tag" -> ujson.Str("Val"),
"ev" -> writeJs(ev),
"value" -> writeJs(value)(using ev.unwrap()),
)
case AST.WithEnv(fun, env) =>
ujson.Obj(
"tag" -> ujson.Str("WithEnv"),
"fun" -> writeJs(fun),
"env" -> writeJs(env),
)
case AST.WithCtx(fun, env) =>
ujson.Obj(
"tag" -> ujson.Str("WithCtx"),
"fun" -> writeJs(fun),
"env" -> writeJs(env),
)
}
},
js => {
js("tag").str match {
case "Body" =>
val kind = js("kind").num.toInt
val className = js("className").str
val body = kind match {
case 0 =>
Reflection.loadModuleFieldValue[SporeBuilder[T]](className).body
case 1 =>
Reflection.loadClassInstance[SporeClassBuilder[T]](className).body
case 2 =>
Reflection.loadClassInstance[SporeLambdaBuilder[T]](className).body
}
AST.Body(kind, className, body)
case "Val" =>
val ev = read[Spore[ReadWriter[T]]](js("ev"))
val value = read[T](js("value"))(using ev.unwrap())
AST.Val(ev, value)
case "WithEnv" =>
val fun = read[Spore[Any => T]](js("fun"))
val env = read[Spore[Any]](js("env"))
AST.WithEnv(fun, env)
case "WithCtx" =>
val fun = read[Spore[Any ?=> T]](js("fun"))
val env = read[Spore[Any]](js("env"))
AST.WithCtx(fun, env)
}
},
)

//////////////////////////////////////////////////////////////////////////////
// Spore[ReadWriter[T]] for primitive T
Expand Down Expand Up @@ -69,27 +120,9 @@ object ReadWriters {
// Spore[ReadWriter[Spore[?]]]
//////////////////////////////////////////////////////////////////////////////

private[spores] object SporeRW extends SporeBuilder[ReadWriter[Spore[?]]](macroRW)
private[spores] object SporeRW extends SporeBuilder[ReadWriter[Spore[?]]](summon[ReadWriter[Spore[?]]])
given packedSporeRW[T]: Spore[ReadWriter[Spore[T]]] = SporeRW.build().asInstanceOf[Spore[ReadWriter[Spore[T]]]]

private[spores] object PackedObjectRW extends SporeBuilder[ReadWriter[PackedObject[?]]](macroRW)
given packedObjectRW[T]: Spore[ReadWriter[PackedObject[T]]] = PackedObjectRW.build().asInstanceOf[Spore[ReadWriter[PackedObject[T]]]]

private[spores] object PackedClassRW extends SporeBuilder[ReadWriter[PackedClass[?]]](macroRW)
given packedClassRW[T]: Spore[ReadWriter[PackedClass[T]]] = PackedClassRW.build().asInstanceOf[Spore[ReadWriter[PackedClass[T]]]]

private[spores] object PackedLambdaRW extends SporeBuilder[ReadWriter[PackedLambda[?]]](macroRW)
given packedLambdaRW[T]: Spore[ReadWriter[PackedLambda[T]]] = PackedLambdaRW.build().asInstanceOf[Spore[ReadWriter[PackedLambda[T]]]]

private[spores] object PackedEnvRW extends SporeBuilder[ReadWriter[PackedEnv[?]]](macroRW)
given packedEnvRW[E]: Spore[ReadWriter[PackedEnv[E]]] = PackedEnvRW.build().asInstanceOf[Spore[ReadWriter[PackedEnv[E]]]]

private[spores] object PackedWithEnvRW extends SporeBuilder[ReadWriter[PackedWithEnv[?, ?]]](macroRW)
given packedWithEnvRW[E, T]: Spore[ReadWriter[PackedWithEnv[E, T]]] = PackedWithEnvRW.build().asInstanceOf[Spore[ReadWriter[PackedWithEnv[E, T]]]]

private[spores] object PackedWithCtxRW extends SporeBuilder[ReadWriter[PackedWithCtx[?, ?]]](macroRW)
given packedWithCtxRW[E, T]: Spore[ReadWriter[PackedWithCtx[E, T]]] = PackedWithCtxRW.build().asInstanceOf[Spore[ReadWriter[PackedWithCtx[E, T]]]]

//////////////////////////////////////////////////////////////////////////////
// Spore[ReadWriter[F[T]]] for Option[T], List[T], etc.
//////////////////////////////////////////////////////////////////////////////
Expand All @@ -106,9 +139,6 @@ object ReadWriters {
private[spores] class ListRW[T] extends SporeClassBuilder[ReadWriter[T] ?=> ReadWriter[List[T]]]({ summon })
given listRW[T](using tRW: Spore[ReadWriter[T]]): Spore[ReadWriter[List[T]]] = new ListRW[T].build().withCtx2(tRW)

private[spores] object Tuple0RW extends SporeBuilder[ReadWriter[EmptyTuple]](macroRW[EmptyTuple])
given tuple0RW: Spore[ReadWriter[EmptyTuple]] = Tuple0RW.build()

private[spores] class Tuple1RW[T1] extends SporeClassBuilder[ReadWriter[T1] ?=> ReadWriter[Tuple1[T1]]]({ summon })
given tuple1RW[T1](using t1RW: Spore[ReadWriter[T1]]): Spore[ReadWriter[Tuple1[T1]]] = new Tuple1RW[T1].build().withCtx2(t1RW)

Expand Down
47 changes: 19 additions & 28 deletions spores3/src/spores/Spore.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ import upickle.default.*
* The type of the packed closure.
*/
sealed trait Spore[+T] {
import spores.Packed.*

/** Applies the packed closure to a value of type `T1`. Only available if the
* wrapped closure of type `T` is a subtype of `T1 => R`.
Expand All @@ -59,7 +58,7 @@ sealed trait Spore[+T] {
*
* @param env
* The value applied to the packed closure.
* @param prw
* @param ev
* The implicit `Spore[ReadWriter[T1]]` used for packing the `env`.
* @tparam T1
* The type of the value applied to the packed closure.
Expand All @@ -68,8 +67,8 @@ sealed trait Spore[+T] {
* @return
* A new `Spore[R]` with the result of the application.
*/
def withEnv[T1, R](env: T1)(using prw: Spore[ReadWriter[T1]])(using @implicitNotFound(CanWithEnv.MSG) ev: CanWithEnv[T, T1, R]): Spore[R] = {
PackedWithEnv(this, PackedEnv(write(env)(using prw.unwrap()), prw))
def withEnv[T1, R](env: T1)(using ev: Spore[ReadWriter[T1]])(using @implicitNotFound(CanWithEnv.MSG) ev2: CanWithEnv[T, T1, R]): Spore[R] = {
AST.WithEnv(this, AST.Val(ev, env))
}

/** Optimization for applying this `Spore[T1 => R]` directly to a `Spore[T1]`.
Expand All @@ -79,8 +78,8 @@ sealed trait Spore[+T] {
* Only available if the wrapped closure of type `T` is a subtype of `T1 =>
* R`.
*/
def withEnv2[T1, R](env: Spore[T1])(using @implicitNotFound(CanWithEnv.MSG) ev: CanWithEnv[T, T1, R]): Spore[R] = {
PackedWithEnv(this, env)
def withEnv2[T1, R](env: Spore[T1])(using @implicitNotFound(CanWithEnv.MSG) ev2: CanWithEnv[T, T1, R]): Spore[R] = {
AST.WithEnv(this, env)
}

/** Applies the packed closure to a context value of type `T1`. Only available
Expand All @@ -91,7 +90,7 @@ sealed trait Spore[+T] {
*
* @param env
* The context value applied to the packed closure.
* @param prw
* @param ev
* The implicit `Spore[ReadWriter[T1]]` used for packing the `env`.
* @tparam T1
* The type of the context value applied to the packed closure.
Expand All @@ -100,8 +99,8 @@ sealed trait Spore[+T] {
* @return
* A new `Spore[R]` with the result of the application.
*/
def withCtx[T1, R](env: T1)(using prw: Spore[ReadWriter[T1]])(using @implicitNotFound(CanWithCtx.MSG) ev: CanWithCtx[T, T1, R]): Spore[R] = {
PackedWithCtx(this, PackedEnv(write(env)(using prw.unwrap()), prw))
def withCtx[T1, R](env: T1)(using ev: Spore[ReadWriter[T1]])(using @implicitNotFound(CanWithCtx.MSG) ev2: CanWithCtx[T, T1, R]): Spore[R] = {
AST.WithCtx(this, AST.Val(ev, env))
}

/** Optimization for applying this `Spore[T1 ?=> R]` directly to a
Expand All @@ -112,8 +111,8 @@ sealed trait Spore[+T] {
* Only available if the wrapped closure of type `T` is a subtype of `T1 ?=>
* R`.
*/
def withCtx2[T1, R](env: Spore[T1])(using @implicitNotFound(CanWithCtx.MSG) ev: CanWithCtx[T, T1, R]): Spore[R] = {
PackedWithCtx(this, env)
def withCtx2[T1, R](env: Spore[T1])(using @implicitNotFound(CanWithCtx.MSG) ev2: CanWithCtx[T, T1, R]): Spore[R] = {
AST.WithCtx(this, env)
}

def map[U](fun: Spore[T => U]): Spore[U] = {
Expand All @@ -131,12 +130,10 @@ sealed trait Spore[+T] {
*/
def unwrap(): T = {
this match
case PackedObject(className) => Reflection.loadModuleFieldValue[SporeBuilder[T]](className).fun
case PackedClass(className) => Reflection.loadClassInstance[SporeClassBuilder[T]](className).fun
case PackedLambda(className) => Reflection.loadClassInstance[SporeLambdaBuilder[T]](className).fun
case PackedEnv(env, rw) => read(env)(using rw.unwrap())
case PackedWithEnv(packed, packedEnv) => packed.unwrap()(packedEnv.unwrap())
case PackedWithCtx(packed, packedEnv) => packed.unwrap()(using packedEnv.unwrap())
case AST.Body(_, _, body) => body
case AST.Val(_, value) => value
case AST.WithEnv(fun, env) => fun.unwrap()(env.unwrap())
case AST.WithCtx(fun, env) => fun.unwrap()(using env.unwrap())
}

}
Expand All @@ -151,17 +148,11 @@ sealed trait Spore[+T] {
object Spore extends SporeObjectCompanionJVM


private object Packed {

// Static:
final case class PackedObject[+T](className: String) extends Spore[T]
final case class PackedClass[+T] (className: String) extends Spore[T]
final case class PackedLambda[+T](className: String) extends Spore[T]
// Dynamic:
final case class PackedEnv[E] (env: String, rw: Spore[ReadWriter[E]]) extends Spore[E]
final case class PackedWithEnv[E, +T](packed: Spore[E => T], packedEnv: Spore[E]) extends Spore[T]
final case class PackedWithCtx[E, +T](packed: Spore[E ?=> T], packedEnv: Spore[E]) extends Spore[T]

private object AST {
case class Body[+T](kind: Int, className: String, body: T) extends Spore[T]
case class Val[T](ev: Spore[ReadWriter[T]], value: T) extends Spore[T]
case class WithEnv[E, +R](fun: Spore[E => R], env: Spore[E]) extends Spore[R]
case class WithCtx[E, +R](fun: Spore[E ?=> R], env: Spore[E]) extends Spore[R]
}


Expand Down
6 changes: 3 additions & 3 deletions spores3/src/spores/SporeBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package spores
import upickle.default.*

import spores.Reflection
import spores.Packed.*
import spores.AST


/** A builder trait that packs a [[Spore]] with a closure of type `T`. Extend
Expand All @@ -29,7 +29,7 @@ import spores.Packed.*
* The wrapped closure.
*/
@Reflection.EnableReflectiveInstantiation
trait SporeBuilder[+T](private[spores] val fun: T) {
trait SporeBuilder[+T](private[spores] val body: T) {

/** Packs the wrapped closure into a [[Spore]] of type `T`.
*
Expand All @@ -47,6 +47,6 @@ private object SporeBuilder {

def buildMacro[T](expr: Expr[SporeBuilder[T]])(using Type[T], Quotes): Expr[Spore[T]] = {
Macros.isTopLevelObject(expr)
'{ PackedObject($expr.getClass().getName()) }
'{ AST.Body(0, $expr.getClass().getName(), $expr.body) }
}
}
6 changes: 3 additions & 3 deletions spores3/src/spores/SporeClassBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package spores
import upickle.default.*

import spores.Reflection
import spores.Packed.*
import spores.AST


/** A class-based builder trait that packs a [[Spore]] with a closure of type
Expand All @@ -30,7 +30,7 @@ import spores.Packed.*
* The wrapped closure.
*/
@Reflection.EnableReflectiveInstantiation
trait SporeClassBuilder[+T](private[spores] val fun: T) {
trait SporeClassBuilder[+T](private[spores] val body: T) {

/** Packs the wrapped closure into a [[Spore]] of type `T`.
*
Expand All @@ -48,6 +48,6 @@ private object SporeClassBuilder {

def buildMacro[T](expr: Expr[SporeClassBuilder[T]])(using Type[T], Quotes): Expr[Spore[T]] = {
Macros.isTopLevelClass(expr)
'{ PackedClass($expr.getClass().getName()) }
'{ AST.Body(1, $expr.getClass().getName(), $expr.body) }
}
}
6 changes: 3 additions & 3 deletions spores3/src/spores/SporeLambdaBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ package spores
import upickle.default.*

import spores.Reflection
import spores.Packed.*
import spores.AST


/** Internal API. Used by the spores.jvm.Spore lambda factories. */
@Reflection.EnableReflectiveInstantiation
private[spores] trait SporeLambdaBuilder[+T](val fun: T) {
private[spores] trait SporeLambdaBuilder[+T](val body: T) {

final inline def build(): Spore[T] = {
${ SporeLambdaBuilder.buildMacro('this) }
Expand All @@ -21,6 +21,6 @@ private object SporeLambdaBuilder {

def buildMacro[T](expr: Expr[SporeLambdaBuilder[T]])(using Type[T], Quotes): Expr[Spore[T]] = {
// No checks needed, all relevant checks are done in the spores.jvm.Spore lambda factories.
'{ PackedLambda($expr.getClass().getName()) }
'{ AST.Body(2, $expr.getClass().getName(), $expr.body) }
}
}
8 changes: 4 additions & 4 deletions spores3/test/src-jvm/spores/jvm/AutoCaptureTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,10 @@ object AutoCaptureTests extends TestSuite {
}

test("testReadFun0123") {
val json0 = """{"$type":"spores.Packed.PackedLambda","className":"spores.jvm.AutoCaptureTestsDefs$FunctionsToReadFromJSON$Lambda$1"}"""
val json1 = """{"$type":"spores.Packed.PackedWithEnv","packed":{"$type":"spores.Packed.PackedLambda","className":"spores.jvm.AutoCaptureTestsDefs$FunctionsToReadFromJSON$Lambda$2"},"packedEnv":{"$type":"spores.Packed.PackedEnv","env":"\"0123456789-1\"","rw":{"$type":"spores.Packed.PackedObject","className":"spores.ReadWriters$StringRW$"}}}"""
val json2 = """{"$type":"spores.Packed.PackedWithEnv","packed":{"$type":"spores.Packed.PackedWithEnv","packed":{"$type":"spores.Packed.PackedLambda","className":"spores.jvm.AutoCaptureTestsDefs$FunctionsToReadFromJSON$Lambda$3"},"packedEnv":{"$type":"spores.Packed.PackedEnv","env":"\"0123456789-1\"","rw":{"$type":"spores.Packed.PackedObject","className":"spores.ReadWriters$StringRW$"}}},"packedEnv":{"$type":"spores.Packed.PackedEnv","env":"\"0123456789-2\"","rw":{"$type":"spores.Packed.PackedObject","className":"spores.ReadWriters$StringRW$"}}}"""
val json3 = """{"$type":"spores.Packed.PackedWithEnv","packed":{"$type":"spores.Packed.PackedWithEnv","packed":{"$type":"spores.Packed.PackedWithEnv","packed":{"$type":"spores.Packed.PackedLambda","className":"spores.jvm.AutoCaptureTestsDefs$FunctionsToReadFromJSON$Lambda$4"},"packedEnv":{"$type":"spores.Packed.PackedEnv","env":"\"0123456789-1\"","rw":{"$type":"spores.Packed.PackedObject","className":"spores.ReadWriters$StringRW$"}}},"packedEnv":{"$type":"spores.Packed.PackedEnv","env":"\"0123456789-2\"","rw":{"$type":"spores.Packed.PackedObject","className":"spores.ReadWriters$StringRW$"}}},"packedEnv":{"$type":"spores.Packed.PackedEnv","env":"[\"a\",\"b\",\"c\"]","rw":{"$type":"spores.Packed.PackedWithCtx","packed":{"$type":"spores.Packed.PackedClass","className":"spores.ReadWriters$ListRW"},"packedEnv":{"$type":"spores.Packed.PackedObject","className":"spores.ReadWriters$StringRW$"}}}}"""
val json0 = """{"tag":"Body","kind":2,"className":"spores.jvm.AutoCaptureTestsDefs$FunctionsToReadFromJSON$Lambda$1"}"""
val json1 = """{"tag":"WithEnv","fun":{"tag":"Body","kind":2,"className":"spores.jvm.AutoCaptureTestsDefs$FunctionsToReadFromJSON$Lambda$2"},"env":{"tag":"Val","ev":{"tag":"Body","kind":0,"className":"spores.ReadWriters$StringRW$"},"value":"0123456789-1"}}"""
val json2 = """{"tag":"WithEnv","fun":{"tag":"WithEnv","fun":{"tag":"Body","kind":2,"className":"spores.jvm.AutoCaptureTestsDefs$FunctionsToReadFromJSON$Lambda$3"},"env":{"tag":"Val","ev":{"tag":"Body","kind":0,"className":"spores.ReadWriters$StringRW$"},"value":"0123456789-1"}},"env":{"tag":"Val","ev":{"tag":"Body","kind":0,"className":"spores.ReadWriters$StringRW$"},"value":"0123456789-2"}}"""
val json3 = """{"tag":"WithEnv","fun":{"tag":"WithEnv","fun":{"tag":"WithEnv","fun":{"tag":"Body","kind":2,"className":"spores.jvm.AutoCaptureTestsDefs$FunctionsToReadFromJSON$Lambda$4"},"env":{"tag":"Val","ev":{"tag":"Body","kind":0,"className":"spores.ReadWriters$StringRW$"},"value":"0123456789-1"}},"env":{"tag":"Val","ev":{"tag":"Body","kind":0,"className":"spores.ReadWriters$StringRW$"},"value":"0123456789-2"}},"env":{"tag":"Val","ev":{"tag":"WithCtx","fun":{"tag":"Body","kind":1,"className":"spores.ReadWriters$ListRW"},"env":{"tag":"Body","kind":0,"className":"spores.ReadWriters$StringRW$"}},"value":["a","b","c"]}}"""

val a1 = "0123456789-1"
val a2 = "0123456789-2"
Expand Down
Loading
Loading