diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 09d4f2c..c667f89 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -37,24 +37,32 @@ jobs: with: distribution: temurin java-version: ${{ matrix.java }} - cache: sbt - - name: Setup sbt - uses: sbt/setup-sbt@v1 - - name: Build + - name: Setup Mill (MacOS/Ubuntu) + if: runner.os != 'Windows' run: | - sbt -v compile - sbt -v test:compile - - name: Test + curl -L https://repo1.maven.org/maven2/com/lihaoyi/mill-dist/1.1.5/mill-dist-1.1.5-mill.sh -o mill + chmod +x mill + ./mill version + - name: Setup Mill (Windows) + if: runner.os == 'Windows' run: | - sbt -v test + 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 + - name: Test + run: ./mill __.test - name: Sample - run: | - sbt -v "sampleJVM / runMain spores.sample.BuilderExample" - sbt -v "sampleJVM / runMain spores.sample.SporeExample" - sbt -v "sampleJVM / runMain spores.sample.AutoCaptureExample" - sbt -v "sampleJVM / runMain spores.sample.AgentMain" - sbt -v "sampleJVM / runMain spores.sample.Futures" - sbt -v "sampleJVM / runMain spores.sample.FutureMap" - sbt -v "sampleJVM / runMain spores.sample.ParallelTreeReduction" - sbt -v "sampleJS / run" - sbt -v "sampleNative / run" + run: > + ./mill --no-daemon + sample.jvm.runMain spores.sample.BuilderExample + + sample.jvm.runMain spores.sample.SporeExample + + sample.jvm.runMain spores.sample.AutoCaptureExample + + sample.jvm.runMain spores.sample.AgentMain + + sample.jvm.runMain spores.sample.Futures + + sample.jvm.runMain spores.sample.FutureMap + + sample.jvm.runMain spores.sample.ParallelTreeReduction + + sample.jvm.runMain spores.sample.WorkflowMain + + sample.jvm.runMain spores.sample.ForComprehension + + sample.js.run + + sample.native.run diff --git a/.gitignore b/.gitignore index ffdcf2b..140e6cc 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *~ project/target/ target/ +out/ .bsp/ .vscode/ diff --git a/README.md b/README.md index 5f54b68..b78741b 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,13 @@ Paper: Philipp Haller. Enhancing closures in Scala 3 with Spores3. 13th ACM SIGP Add the following dependency to your `build.sbt`: ``` -libraryDependencies += "com.phaller" %% "spores3" % "0.1.0" +libraryDependencies += "com.phaller" %%% "spores3" % "0.1.0" +``` + +Add the following dependency to your `build.mill`: + +``` +def mvnDeps = Seq(mvn"com.phaller::spores3::0.1.0") ``` ## Overview diff --git a/build.mill b/build.mill new file mode 100644 index 0000000..086e0ad --- /dev/null +++ b/build.mill @@ -0,0 +1,107 @@ +//| mill-version: 1.1.5 +//| mill-jvm-opts: ["-Xss8m"] +//| mvnDeps: +//| - com.github.lolgab::mill-mima_mill1:0.2.0 +package build + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ +import mill.scalajslib._ +import mill.scalanativelib._ +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 utest = "0.9.5" + +trait CommonTestModule extends TestModule.Utest { + def mvnDeps = Seq(mvn"com.lihaoyi::utest::$utest") +} + +trait CommonMimaModule extends Mima { + def mimaPreviousVersions: T[Seq[String]] = Seq.empty[String] + + def mimaReportBinaryIssues() = { + if (this.isInstanceOf[ScalaNativeModule] || this.isInstanceOf[ScalaJSModule]) Task.Command {} + else super.mimaReportBinaryIssues() + } +} + +trait CommonPublishModule extends ScalaModule with PublishModule with CommonMimaModule { + def scalaVersion = scala3 + def publishVersion = "0.3.0-SNAPSHOT" + + def pomSettings = PomSettings( + description = "Spores3 provides abstractions for making closures in Scala safer and more flexible", + organization = "com.phaller", + url = "https://github.com/phaller/spores3", + licenses = Seq(License.`Apache-2.0`), + versionControl = VersionControl.github(owner = "phaller", repo = "spores3"), + developers = Seq( + Developer("phaller", "Philipp Haller", "https://github.com/phaller"), + Developer("jspenger", "Jonas Spenger", "https://github.com/jspenger") + ) + ) + + def versionScheme: T[Option[VersionScheme]] = Some(VersionScheme.SemVerSpec) +} + +trait CommonPlatformModule extends ScalaModule with PlatformScalaModule { + def platformSources = Task.Sources( + platformCrossSuffix match { + case "jvm" => Seq(os.sub / "src-jvm-native") + case "js" => Seq(os.sub / "src-js-native") + case "native" => Seq(os.sub / "src-js-native", os.sub / "src-jvm-native") + }* + ) + + override def sources = Task { super.sources() ++ platformSources() } +} + +object spores3 extends Module { + + trait Shared extends CommonPublishModule with CommonPlatformModule { + def artifactName = "spores3" + def mvnDeps = Seq(mvn"com.lihaoyi::upickle::$upickle") + } + + object jvm extends Shared { + object test extends ScalaTests with CommonTestModule + } + + object js extends Shared with ScalaJSModule { + def scalaJSVersion = scalaJS + object test extends ScalaJSTests with CommonTestModule + } + + object native extends Shared with ScalaNativeModule { + def scalaNativeVersion = scalaNative + object test extends ScalaNativeTests with CommonTestModule + } +} + +object sample extends Module { + + trait Shared extends ScalaModule with CommonPlatformModule { + def scalaVersion = scala3 + def mvnDeps = Seq(mvn"com.lihaoyi::upickle::$upickle") + } + + object jvm extends Shared { + def moduleDeps = Seq(spores3.jvm) + } + + object js extends Shared with ScalaJSModule { + def scalaJSVersion = scalaJS + def moduleDeps = Seq(spores3.js) + def scalaJSUseMainModuleInitializer = true + } + + object native extends Shared with ScalaNativeModule { + def scalaNativeVersion = scalaNative + def moduleDeps = Seq(spores3.native) + } +} diff --git a/build.sbt b/build.sbt deleted file mode 100644 index fe0e294..0000000 --- a/build.sbt +++ /dev/null @@ -1,76 +0,0 @@ -lazy val Scala33 = "3.3.4" -lazy val upickleVersion = "3.3.0" -lazy val junitInterfaceVersion = "0.11" - -ThisBuild / organization := "com.phaller" -ThisBuild / organizationName := "Philipp Haller" -ThisBuild / organizationHomepage := Some(url("https://www.phaller.com/")) - -ThisBuild / scmInfo := Some( - ScmInfo( - url("https://github.com/phaller/spores3"), - "scm:git@github.com:phaller/spores3.git" - ) -) -ThisBuild / developers := List( - Developer( - id = "phaller", - name = "Philipp Haller", - email = "hallerp@gmail.com", - url = url("https://github.com/phaller") - ), - Developer( - id = "jspenger", - name = "Jonas Spenger", - email = "jonas.spenger@gmail.com", - url = url("https://github.com/jspenger") - ) -) - -ThisBuild / description := "Spores3 provides abstractions for making closures in Scala safer and more flexible" -ThisBuild / licenses := List("Apache-2.0" -> new URL("https://www.apache.org/licenses/LICENSE-2.0.txt")) -ThisBuild / homepage := Some(url("https://github.com/phaller/spores3")) - -ThisBuild / pomIncludeRepository := { _ => false } -ThisBuild / publishTo := { - val nexus = "https://s01.oss.sonatype.org/" - if (isSnapshot.value) Some("snapshots" at nexus + "content/repositories/snapshots") - else Some("releases" at nexus + "service/local/staging/deploy/maven2") -} -ThisBuild / publishMavenStyle := true - -ThisBuild / version := "0.2.0" -ThisBuild / scalaVersion := Scala33 - -ThisBuild / credentials += Credentials( - "GnuPG Key ID", - "gpg", - "B4CC0C56EBBBC95D23D14C454ADDDD4698B3BC95", - "ignored" -) - - -lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) - .in(file("core")) - .settings( - name := "spores3", - libraryDependencies += "com.lihaoyi" %%% "upickle" % upickleVersion, - libraryDependencies += "com.novocode" % "junit-interface" % junitInterfaceVersion % "test", - testOptions += Tests.Argument(TestFrameworks.JUnit, "-v"), - Test / parallelExecution := false, - ) - .jsConfigure(_.enablePlugins(ScalaJSJUnitPlugin)) - .nativeConfigure(_.enablePlugins(ScalaNativeJUnitPlugin)) - - -lazy val sample = crossProject(JVMPlatform, JSPlatform, NativePlatform) - .in(file("sample")) - .settings( - name := "spores3-sample", - libraryDependencies += "com.lihaoyi" %%% "upickle" % upickleVersion, - publish / skip := true, - ) - .jsSettings( - scalaJSUseMainModuleInitializer := true, - ) - .dependsOn(core) diff --git a/core/jvm/src/test/scala/spores/jvm/AutoCaptureErrorTests.scala b/core/jvm/src/test/scala/spores/jvm/AutoCaptureErrorTests.scala deleted file mode 100644 index 1fbad1e..0000000 --- a/core/jvm/src/test/scala/spores/jvm/AutoCaptureErrorTests.scala +++ /dev/null @@ -1,450 +0,0 @@ -package spores.jvm - -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.junit.Test -import org.junit.Assert.* - -import upickle.default.* - -import spores.default.* -import spores.default.given -import spores.TestUtils.* - - -object AutoCaptureErrorTests { - - /** Foo values cannot be captured as there is no Spore[ReadWriter[Foo]]]. */ - case class Foo(x: Int, y: Int) - - /** Opaque type without a ReadWriter. */ - opaque type OpaqueInt = Int - object OpaqueInt { - def apply(value: Int): OpaqueInt = value - def unwrap(value: OpaqueInt): Int = value - } - - // For some reason this doesn't cause any errors when using the - // `typeCheckErrorMessages` method, but it does so here... - // class Outer { outer => - // val y = 12 - // class Inner { - // def foo = Spore.auto { (x: Int) => x + outer.y } - // } - // } -} - - -@RunWith(classOf[JUnit4]) -class AutoCaptureErrorTests { - import AutoCaptureErrorTests.* - - @Test - def testCaptureIdentError(): Unit = { - assertTrue: - typeCheckErrorMessages: - """ - val foo = Foo(12, 13) - Spore.auto { foo } - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `foo`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - val foo = Foo(12, 13) - Spore.auto { (x: Int) => x + foo.x + foo.y } - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `foo`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - val foo = Foo(12, 13) - Spore.auto { def bar(x: Int): Int = { x + foo.x } } - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `foo`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - } - - @Test - def testCaptureClassError(): Unit = { - assertTrue: - typeCheckErrorMessages: - """ - class A(val a: Int) - Spore.auto { new A(12) } - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `A`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - class A(val a: Int) - Spore.auto { (x: Int) => x + new A(12).a } - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `A`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - } - - @Test - def testCaptureMethodError(): Unit = { - assertTrue: - typeCheckErrorMessages: - """ - def captureMeIfYouCan(): Int = 12 - Spore.auto { (x: Int) => x + captureMeIfYouCan() } - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `captureMeIfYouCan`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - } - - val captureThisXIfYouCan = 99 - - @Test - def testCaptureThisError(): Unit = { - assertTrue: - typeCheckErrorMessages: - """ - Spore.auto { this } - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `AutoCaptureErrorTests`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - Spore.auto { this.captureThisXIfYouCan } - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `AutoCaptureErrorTests`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - Spore.auto { (x: Int) => x + captureThisXIfYouCan } - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `AutoCaptureErrorTests`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - } - - @Test - def testCaptureImplicitThisError(): Unit = { - assertTrue: - typeCheckErrorMessages: - """ - case class Bar(x: Int, y: Int) - given ReadWriter[Bar] = macroRW[Bar] - given Spore[ReadWriter[Bar]] = Spore.auto { summon[ReadWriter[Bar]] } - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `given_ReadWriter_Bar`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - } - - @Test - def testCaptureOpaqueTypeError(): Unit = { - assertTrue: - typeCheckErrorMessages: - """ - val opaqueInt = OpaqueInt(12) - Spore.auto { (x: Int) => x + OpaqueInt.unwrap(opaqueInt) } - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `opaqueInt`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - } - - @Test - def testCapturedThisNestedClassError(): Unit = { - assertTrue: - typeCheckErrorMessages: - """ - class Outer { - class Inner { - val y = 12 - def foo = Spore.auto { (x: Int) => x + y } - } - } - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `Inner`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - class Outer { - val y = 12 - class Inner { - def foo = Spore.auto { (x: Int) => x + y } - } - } - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `Outer`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - class Outer { - val y = 12 - class Inner { - def foo = Spore.auto { (x: Int) => x + Outer.this.y } - } - } - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `Outer`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - } - - @Test - def testCapturedIdentInClassError(): Unit = { - assertTrue: - typeCheckErrorMessages: - """ - val foo = Foo(12, 13) - Spore.auto { - class Bar { - def bar = foo.x + 12 - } - }.unwrap() - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `foo`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - } - - @Test - def testCapturedNewClassError(): Unit = { - assertTrue: - typeCheckErrorMessages: - """ - class Bar(x: Int, y: Int) - Spore.auto { (x: Int) => - new Bar(12, 14) - } - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `Bar`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - class Bar[T](x: T, y: T) - Spore.auto { (x: Int) => - new Bar[Int](12, 14) - } - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `Bar`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - } - - @Test - def testCapturedClassExtendsError(): Unit = { - assertTrue: - typeCheckErrorMessages: - """ - class Bar0 - Spore.auto { - class FooBar extends Bar0 - }.unwrap() - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `Bar0`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - class Bar1(x: Int, y: Int) - Spore.auto { - class FooBar extends Bar1(12, 13) - } - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `Bar1`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - class Bar2[T](x: T, y: T) - Spore.auto { - class FooBar extends Bar2[Int](12, 13) - }.unwrap() - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `Bar2`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - val x = 12 - trait Bar3 { def bar: Int = x } - Spore.auto { - class FooBar extends Foo(12, 13) with Bar3 - } - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `Bar3`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - trait Bar4[T] { def bar: Int = x } - Spore.auto { - class FooBar extends Foo(12, 13) with Bar4[Int] - }.unwrap() - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `Bar4`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - } - - @Test - def testCapturedTraitExtendsError(): Unit = { - assertTrue: - typeCheckErrorMessages: - """ - trait Bar - Spore.auto { - trait FooBar extends Bar - }.unwrap() - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `Bar`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - } - - @Test - def testObjectExtendsCapturedError(): Unit = { - assertTrue: - typeCheckErrorMessages: - """ - trait Bar - Spore.auto { - object FooBar extends Bar - }.unwrap() - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `Bar`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - } - - @Test - def testCapturedEnumError(): Unit = { - assertTrue: - typeCheckErrorMessages: - """ - enum Bar { case Baz } - Spore.auto { Bar.Baz } - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `Bar`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - } - - @Test - def testCapturedUnapplyError(): Unit = { - assertTrue: - typeCheckErrorMessages: - """ - sealed trait Bar - case class Baz(x: Int, y: Int) extends Bar - Spore.auto { (x: Bar) => x match { - case Baz(a, b) => a + b - } - } - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `Baz`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - } - - @Test - def testCapturedThisSuperError(): Unit = { - assertTrue: - typeCheckErrorMessages: - """ - class Bar extends Foo(12, 13) { - def bar = Spore.auto { (x: Int) => x.toString() + super.toString() } - } - """ - .exists: - _.matches: - raw""" - (?s)Missing implicit for captured variable `Bar`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* - """.trim() - } -} diff --git a/core/jvm/src/test/scala/spores/jvm/AutoCaptureTests.scala b/core/jvm/src/test/scala/spores/jvm/AutoCaptureTests.scala deleted file mode 100644 index 483b978..0000000 --- a/core/jvm/src/test/scala/spores/jvm/AutoCaptureTests.scala +++ /dev/null @@ -1,265 +0,0 @@ -package spores.jvm - -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.junit.Test -import org.junit.Assert.* - -import upickle.default.* - -import spores.default.* -import spores.default.given -import spores.TestUtils.* - - -object AutoCaptureTests { - - case class Foo(x: Int, y: Int) - - def writeReadUnwrap[T](s: Spore[T]): T = { - val w = write(s) - val r = read[Spore[T]](w) - r.unwrap() - } - - def readUnwrap[T](json: String): T = { - val r = read[Spore[T]](json) - r.unwrap() - } - - def countCapturedInSpore[T](s: Spore[T], captured: String): Int = { - // Note: the `captured` String should otherwise not occur in the JSON. - val length = captured.length - val json = write(s) - json.sliding(length).count(_.contains(captured)) - } - - object FunctionsToReadFromJSON { - def fun0() = Spore.auto { (x: String) => x } - def fun1(y1: String) = Spore.auto { (x: String) => x + y1 } - def fun2(y1: String, y2: String) = Spore.auto { (x: String) => x + y1 + y2 } - def fun3(y1: String, y2: String, y3: List[String]) = Spore.auto { (x: String) => x + y1 + y2 + y3.mkString(",") } - } - - sealed trait Tree[T] derives ReadWriter - case class Leaf[T](value: T) extends Tree[T] derives ReadWriter - case class Node[T](left: Tree[T], right: Tree[T]) extends Tree[T] derives ReadWriter - def reduce[T](tree: Tree[T], f: (T, T) => T): T = { - tree match { - case Leaf(value) => value - case Node(left, right) => f(reduce(left, f), reduce(right, f)) - } - } - object TreeRW extends SporeBuilder[ReadWriter[Tree[Int]]]({ macroRW }) - object LeafRW extends SporeBuilder[ReadWriter[Leaf[Int]]]({ macroRW }) - object NodeRW extends SporeBuilder[ReadWriter[Node[Int]]]({ macroRW }) - given treeRW: Spore[ReadWriter[Tree[Int]]] = TreeRW.build() - given leafRW: Spore[ReadWriter[Leaf[Int]]] = LeafRW.build() - given nodeRW: Spore[ReadWriter[Node[Int]]] = NodeRW.build() - - class TopLevel { - def x: Int = 4 - } - - def from[T](x: T): List[T] = List(x) -} - - -@RunWith(classOf[JUnit4]) -class AutoCaptureTests { - import AutoCaptureTests.* - - @Test - def testCaptureNothing(): Unit = { - val fun = Spore.auto {} - val unwrapped = writeReadUnwrap(fun) - assertEquals((), unwrapped) - } - - @Test - def testCaptures01234(): Unit = { - val a1 = "0123456789-1" - val a2 = "0123456789-2" - val a3 = "0123456789-3" - val a4 = "0123456789-4" - - val fun0 = Spore.auto { (x: String) => x } - val fun1 = Spore.auto { (x: String) => x + a1 } - val fun2 = Spore.auto { (x: String) => x + a1 + a2 } - val fun3 = Spore.auto { (x: String) => x + a1 + a2 + a3 } - val fun4 = Spore.auto { (x: String) => x + a1 + a2 + a3 + a4 } - - val unwrapped0 = writeReadUnwrap(fun0) - val unwrapped1 = writeReadUnwrap(fun1) - val unwrapped2 = writeReadUnwrap(fun2) - val unwrapped3 = writeReadUnwrap(fun3) - val unwrapped4 = writeReadUnwrap(fun4) - - assertEquals("hello", unwrapped0("hello")) - assertEquals("hello" + a1, unwrapped1("hello")) - assertEquals("hello" + a1 + a2, unwrapped2("hello")) - assertEquals("hello" + a1 + a2 + a3, unwrapped3("hello")) - assertEquals("hello" + a1 + a2 + a3 + a4, unwrapped4("hello")) - - assertEquals(1, countCapturedInSpore(fun1, a1)) - assertEquals(1, countCapturedInSpore(fun2, a1)) - assertEquals(1, countCapturedInSpore(fun2, a2)) - assertEquals(1, countCapturedInSpore(fun3, a1)) - assertEquals(1, countCapturedInSpore(fun3, a2)) - assertEquals(1, countCapturedInSpore(fun3, a3)) - assertEquals(1, countCapturedInSpore(fun4, a1)) - assertEquals(1, countCapturedInSpore(fun4, a2)) - assertEquals(1, countCapturedInSpore(fun4, a3)) - assertEquals(1, countCapturedInSpore(fun4, a4)) - } - - @Test - def testCapturedIdentExactlyOnce(): Unit = { - val c = "0123456789" - val fun = Spore.auto { (x: String) => - val y = { - val z = { - x + c - } - z + c - } - y + c + c - } - val unwrapped = writeReadUnwrap(fun) - assertEquals("0123456789" + c + c + c + c, unwrapped("0123456789")) - assertEquals(1, countCapturedInSpore(fun, c)) - } - - @Test - def testReadFun0123(): Unit = { - val json0 = """{"$type":"spores.Packed.PackedLambda","className":"spores.jvm.AutoCaptureTests$FunctionsToReadFromJSON$Lambda$16"}""" - val json1 = """{"$type":"spores.Packed.PackedWithEnv","packed":{"$type":"spores.Packed.PackedLambda","className":"spores.jvm.AutoCaptureTests$FunctionsToReadFromJSON$Lambda$17"},"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.AutoCaptureTests$FunctionsToReadFromJSON$Lambda$18"},"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.AutoCaptureTests$FunctionsToReadFromJSON$Lambda$19"},"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 a1 = "0123456789-1" - val a2 = "0123456789-2" - val a3 = List("a", "b", "c") - - val u0 = readUnwrap[String => String](json0) - val u1 = readUnwrap[String => String](json1) - val u2 = readUnwrap[String => String](json2) - val u3 = readUnwrap[String => String](json3) - - assertEquals("hello", u0("hello")) - assertEquals("hello" + a1, u1("hello")) - assertEquals("hello" + a1 + a2, u2("hello")) - assertEquals("hello" + a1 + a2 + a3.mkString(","), u3("hello")) - } - - @Test - def testCaptureTree(): Unit = { - val tree = Node(Leaf(1), Node(Leaf(2), Leaf(3))) - val fun = Spore.auto { reduce(tree, (x, y) => x + y) } - val unwrapped = writeReadUnwrap(fun) - assertEquals(6, unwrapped) - } - - @Test - def testValDefNoCapture(): Unit = { - val fun = Spore.auto { (x: Int) => - val y = 12 - x + y - } - val unwrapped = writeReadUnwrap(fun) - assertEquals(13, unwrapped(1)) - } - - @Test - def testTypeDefNoCapture(): Unit = { - val fun = Spore.auto { (y: Int) => - type T = TopLevel - new T { override def x = y } - } - val unwrapped = writeReadUnwrap(fun) - assertEquals(13, unwrapped(13).x) - } - - @Test - def testAsInstanceOfNoCapture(): Unit = { - class Bar(val value: Int) - val fun = Spore.auto { (x: List[Any]) => - x.asInstanceOf[List[Bar]].tail.head.value + x.head.asInstanceOf[Bar].value - } - val unwrapped = writeReadUnwrap(fun) - assertEquals(56, unwrapped(List(Bar(42), Bar(14), Bar(73)))) - } - - @Test - def testMethodTypeParamNoCapture(): Unit = { - class Bar(val value: Int) - val fun = Spore.auto { (x: Any) => - from[Bar](x.asInstanceOf[Bar]).head.value - } - val unwrapped = writeReadUnwrap(fun) - assertEquals(39, unwrapped.apply(Bar(39))) - } - - @Test - def testParameterTypeNoCapture(): Unit = { - class Bar(val value: Int) - val fun = Spore.auto { - (l: List[Bar]) - => (x: Bar) => - val foo: Bar = null - l.head.value + x.value - } - val unwrapped = writeReadUnwrap(fun) - assertEquals(76, unwrapped(List(Bar(36), Bar(31)))(Bar(40))) - } - - @Test - def testClassExtendsTopLevelNoCapture(): Unit = { - val fun = Spore.auto { - class FooBar extends Foo(12, 13) { - def foo: Foo = this - } - val fooBar = new FooBar() - fooBar.foo.x - } - val unwrapped = writeReadUnwrap(fun) - assertEquals(12, unwrapped) - } - - @Test - def testLargeProgram(): Unit = { - val C00 = 0; val C01 = 1; val C02 = 2; val C03 = 3; val C04 = 4; val C05 = 5; val C06 = 6; val C07 = 7; val C08 = 8; val C09 = 9; - val C10 = 0; val C11 = 1; val C12 = 2; val C13 = 3; val C14 = 4; val C15 = 5; val C16 = 6; val C17 = 7; val C18 = 8; val C19 = 9; - val C20 = 0; val C21 = 1; val C22 = 2; val C23 = 3; val C24 = 4; val C25 = 5; val C26 = 6; val C27 = 7; val C28 = 8; val C29 = 9; - val C30 = 0; val C31 = 1; val C32 = 2; val C33 = 3; val C34 = 4; val C35 = 5; val C36 = 6; val C37 = 7; val C38 = 8; val C39 = 9; - val C40 = 0; val C41 = 1; val C42 = 2; val C43 = 3; val C44 = 4; val C45 = 5; val C46 = 6; val C47 = 7; val C48 = 8; val C49 = 9; - - val fun = Spore.auto { - (x00: Int) => (x01: Int) => (x02: Int) => (x03: Int) => (x04: Int) => (x05: Int) => (x06: Int) => (x07: Int) => (x08: Int) => (x09: Int) => - (x10: Int) => (x11: Int) => (x12: Int) => (x13: Int) => (x14: Int) => (x15: Int) => (x16: Int) => (x17: Int) => (x18: Int) => (x19: Int) => - (x20: Int) => (x21: Int) => (x22: Int) => (x23: Int) => (x24: Int) => (x25: Int) => (x26: Int) => (x27: Int) => (x28: Int) => (x29: Int) => - (x30: Int) => (x31: Int) => (x32: Int) => (x33: Int) => (x34: Int) => (x35: Int) => (x36: Int) => (x37: Int) => (x38: Int) => (x39: Int) => - (x40: Int) => (x41: Int) => (x42: Int) => (x43: Int) => (x44: Int) => (x45: Int) => (x46: Int) => (x47: Int) => (x48: Int) => (x49: Int) => - x00 + x01 + x02 + x03 + x04 + x05 + x06 + x07 + x08 + x09 + - x10 + x11 + x12 + x13 + x14 + x15 + x16 + x17 + x18 + x19 + - x20 + x21 + x22 + x23 + x24 + x25 + x26 + x27 + x28 + x29 + - x30 + x31 + x32 + x33 + x34 + x35 + x36 + x37 + x38 + x39 + - x40 + x41 + x42 + x43 + x44 + x45 + x46 + x47 + x48 + x49 + - C00 + C01 + C02 + C03 + C04 + C05 + C06 + C07 + C08 + C09 + - C10 + C11 + C12 + C13 + C14 + C15 + C16 + C17 + C18 + C19 + - C20 + C21 + C22 + C23 + C24 + C25 + C26 + C27 + C28 + C29 + - C30 + C31 + C32 + C33 + C34 + C35 + C36 + C37 + C38 + C39 + - C40 + C41 + C42 + C43 + C44 + C45 + C46 + C47 + C48 + C49 - } - - val unwrapped = writeReadUnwrap(fun) - val actual = unwrapped - .apply(C00).apply(C01).apply(C02).apply(C03).apply(C04).apply(C05).apply(C06).apply(C07).apply(C08).apply(C09) - .apply(C10).apply(C11).apply(C12).apply(C13).apply(C14).apply(C15).apply(C16).apply(C17).apply(C18).apply(C19) - .apply(C20).apply(C21).apply(C22).apply(C23).apply(C24).apply(C25).apply(C26).apply(C27).apply(C28).apply(C29) - .apply(C30).apply(C31).apply(C32).apply(C33).apply(C34).apply(C35).apply(C36).apply(C37).apply(C38).apply(C39) - .apply(C40).apply(C41).apply(C42).apply(C43).apply(C44).apply(C45).apply(C46).apply(C47).apply(C48).apply(C49) - val expected = 450 // (0 + 1 + ... + 9) * 5 * 2 - assertEquals(expected, actual) - } -} diff --git a/core/jvm/src/test/scala/spores/jvm/SporeLambdaErrorTests.scala b/core/jvm/src/test/scala/spores/jvm/SporeLambdaErrorTests.scala deleted file mode 100644 index 67a2b9f..0000000 --- a/core/jvm/src/test/scala/spores/jvm/SporeLambdaErrorTests.scala +++ /dev/null @@ -1,105 +0,0 @@ -package spores.jvm - -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.junit.Test -import org.junit.Assert.* - -import spores.default.given -import spores.default.* -import spores.TestUtils.* - -// // The following code should produce a compile error: -// // Invalid capture of variable `x`. Use the first parameter of a spore's body to refer to the spore's environment.bloop -// // ... but reproducing it with the typeCheckErrorMessages macro is not possible as the object needs to be non-nested top-level. -// object Issue001: -// def foo(x: Int): Spore[Int => Boolean] = Spore.apply[Int => Boolean] { y => y > x } - -@RunWith(classOf[JUnit4]) -class SporeLambdaErrorTests: - - @Test - def testInvalidCaptureIdent(): Unit = - assertTrue: - typeCheckErrorMessages: - """ - val y = 12 - Spore.apply[Int => Int] { x => x + y } - """ - .contains: - """ - Invalid capture of variable `y`. Use the first parameter of a spore's body to refer to the spore's environment. - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - Spore.apply[Int => Int] { x => Spore.apply[Int => Int] { y => x + y }.unwrap().apply(x) } - """ - .contains: - """ - Invalid capture of variable `x`. Use the first parameter of a spore's body to refer to the spore's environment. - """.trim() - - @Test - def testInvalidCaptureMethodParameter(): Unit = - assertTrue: - typeCheckErrorMessages: - """ - def fun(x: Int): Spore[Int => Boolean] = Spore.apply[Int => Boolean] { y => y > x } - """ - .contains: - """ - Invalid capture of variable `x`. Use the first parameter of a spore's body to refer to the spore's environment. - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - object ShouldFail: - def fun(x: Int): Spore[Int => Boolean] = Spore.apply[Int => Boolean] { y => y > x } - """ - .contains: - """ - Invalid capture of variable `x`. Use the first parameter of a spore's body to refer to the spore's environment. - """.trim() - - val captureMeIfYouCan = 12 - - @Test - def testInvalidCaptureThis(): Unit = - assertTrue: - typeCheckErrorMessages: - """ - class TestClass { - Spore.apply { () => this.toString() }.unwrap() - } - (new TestClass()) - """ - .contains: - """ - Invalid capture of `this` from outer class. - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - class Outer: - val x = 12 - Spore.apply { () => 42 * x }.unwrap() - (new Outer()) - """ - .contains: - """ - Invalid capture of `this` from class Outer. - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - Spore.apply { () => 42 * captureMeIfYouCan }.unwrap() - """ - .contains: - """ - Invalid capture of `this` from class SporeLambdaErrorTests. - """.trim() diff --git a/core/jvm/src/test/scala/spores/jvm/SporeLambdaTests.scala b/core/jvm/src/test/scala/spores/jvm/SporeLambdaTests.scala deleted file mode 100644 index 2a76153..0000000 --- a/core/jvm/src/test/scala/spores/jvm/SporeLambdaTests.scala +++ /dev/null @@ -1,187 +0,0 @@ -package spores.jvm - -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.junit.Test -import org.junit.Assert.* - -import spores.default.given -import spores.default.* -import spores.TestUtils.* - -object SporeLambdaTests: - val lambda = Spore.apply[Int => Boolean] { x => x > 10 } - - val lambdaWithEnv = Spore.applyWithEnv(11) { x => x > 10 } - - object NestedLambda: - val lambda = Spore.apply[Int => Boolean] { x => x > 10 } - - def methodLambda(): Spore[Int => Boolean] = - Spore.apply[Int => Boolean] { x => x > 10 } - - def methodLambdaWithUnnusedArg(x: Int): Spore[Int => Boolean] = - Spore.apply[Int => Boolean] { y => y > 10 } - - inline def inlinedMethodLambda(): Spore[Int => Boolean] = - Spore.apply[Int => Boolean] { x => x > 10 } - - inline def inlinedMethodLambdaWithArg(x: Int): Spore[Int => Boolean] = - Spore.apply[Int => Boolean] { y => y > x } - - class ClassWithLambda(): - val lambda = Spore.apply[Int => Boolean] { x => x > 10 } - def methodLambda() = Spore.apply[Int => Boolean] { x => x > 10 } - -@RunWith(classOf[JUnit4]) -class SporeLambdaTests: - import SporeLambdaTests.* - - @Test - def testLambda(): Unit = - val predicate = lambda - assertTrue(predicate(11)) - assertFalse(predicate(9)) - assertTrue(predicate.unwrap()(11)) - assertFalse(predicate.unwrap()(9)) - - @Test - def testLambdaWithEnv(): Unit = - val predicate9 = Spore.applyWithEnv(9) { x => x > 10 } - val predicate11 = Spore.applyWithEnv(11) { x => x > 10 } - assertFalse(predicate9.unwrap()) - assertTrue(predicate11.unwrap()) - - @Test - def testLambdaWithCtx(): Unit = - val predicate9 = Spore.applyWithCtx(9) { summon[Int] > 10 } - val predicate11 = Spore.applyWithCtx(11) { summon[Int] > 10 } - assertFalse(predicate9.unwrap()) - assertTrue(predicate11.unwrap()) - - @Test - def testPackBuildHigherOrderLambda(): Unit = - val higherLevelFilter = Spore.apply[Spore[Int => Boolean] => Int => Option[Int]] { env => x => if env.unwrap().apply(x) then Some(x) else None } - val filter = higherLevelFilter.withEnv(lambda) - assertEquals(Some(11), filter(11)) - assertEquals(None, filter(9)) - assertEquals(Some(11), filter.unwrap()(11)) - assertEquals(None, filter.unwrap()(9)) - - @Test - def testPackedLambdaReadWriter(): Unit = - val json = """{"$type":"spores.Packed.PackedLambda","className":"spores.jvm.SporeLambdaTests$Lambda$12"}""" - - val packed = upickle.default.write(lambda) - assertEquals(json, packed) - - val loaded = upickle.default.read[Spore[Int => Boolean]](json) - assertTrue(loaded(11)) - assertFalse(loaded(9)) - assertTrue(loaded.unwrap()(11)) - assertFalse(loaded.unwrap()(9)) - - @Test - def testNestedLambdaReadWriter(): Unit = - val json = """{"$type":"spores.Packed.PackedLambda","className":"spores.jvm.SporeLambdaTests$NestedLambda$Lambda$14"}""" - - val packed = upickle.default.write(NestedLambda.lambda) - assertEquals(json, packed) - - val loaded = upickle.default.read[Spore[Int => Boolean]](json) - assertTrue(loaded(11)) - assertFalse(loaded(9)) - assertTrue(loaded.unwrap()(11)) - assertFalse(loaded.unwrap()(9)) - - @Test - def testPackedLambdaWithEnvReadWriter(): Unit = - val json9 = """{"$type":"spores.Packed.PackedWithEnv","packed":{"$type":"spores.Packed.PackedLambda","className":"spores.jvm.SporeLambdaTests$Lambda$12"},"packedEnv":{"$type":"spores.Packed.PackedEnv","env":"9","rw":{"$type":"spores.Packed.PackedObject","className":"spores.ReadWriters$IntRW$"}}}""" - val json11 = """{"$type":"spores.Packed.PackedWithEnv","packed":{"$type":"spores.Packed.PackedLambda","className":"spores.jvm.SporeLambdaTests$Lambda$12"},"packedEnv":{"$type":"spores.Packed.PackedEnv","env":"11","rw":{"$type":"spores.Packed.PackedObject","className":"spores.ReadWriters$IntRW$"}}}""" - - val packed9 = upickle.default.write(lambda.withEnv(9)) - val packed11 = upickle.default.write(lambda.withEnv(11)) - assertEquals(json9, packed9) - assertEquals(json11, packed11) - - val loaded9 = upickle.default.read[Spore[Boolean]](json9).unwrap() - val loaded11 = upickle.default.read[Spore[Boolean]](json11).unwrap() - assertFalse(loaded9) - assertTrue(loaded11) - - @Test - def testLambdaWithEnvConstructorReadWriter(): Unit = - val json = """{"$type":"spores.Packed.PackedWithEnv","packed":{"$type":"spores.Packed.PackedLambda","className":"spores.jvm.SporeLambdaTests$Lambda$13"},"packedEnv":{"$type":"spores.Packed.PackedEnv","env":"11","rw":{"$type":"spores.Packed.PackedObject","className":"spores.ReadWriters$IntRW$"}}}""" - - val packed = upickle.default.write(lambdaWithEnv) - assertEquals(json, packed) - - val loaded = upickle.default.read[Spore[Boolean]](json).unwrap() - assertTrue(loaded) - - @Test - def testLambdaWithOptionEnvironment(): Unit = - val packed = Spore.applyWithEnv(Some(11)) { x => x.getOrElse(0) } - val fun = packed.unwrap() - assertEquals(11, fun) - - @Test - def testLambdaWithListEnvironment(): Unit = - val packed = Spore.applyWithEnv(List(1, 2, 3)) { x => x.sum } - val fun = packed.unwrap() - assertEquals(6, fun) - - @Test - def testLambdaFromMethodCreator(): Unit = - val packed = methodLambda() - val fun = packed.unwrap() - assertTrue(fun(11)) - assertFalse(fun(9)) - - @Test - def testLambdaFromMethodCreatorWithUnnusedArg(): Unit = - val packed = methodLambdaWithUnnusedArg(11) - val fun = packed.unwrap() - assertTrue(fun(11)) - assertFalse(fun(9)) - - @Test - def testLambdaFromInlinedMethodCreator(): Unit = - val packed = inlinedMethodLambda() - val fun = packed.unwrap() - assertTrue(fun(11)) - assertFalse(fun(9)) - - @Test - def testLambdaFromInlinedMethodCreatorWithArg(): Unit = - val packed = inlinedMethodLambdaWithArg(10) - val fun = packed.unwrap() - assertTrue(fun(11)) - assertFalse(fun(9)) - - @Test - def testLambdaFromClassCreator(): Unit = - val packed = ClassWithLambda().lambda - val fun = packed.unwrap() - assertTrue(fun(11)) - assertFalse(fun(9)) - - @Test - def testLambdaFromClassMethodCreator(): Unit = - val packed = ClassWithLambda().methodLambda() - val fun = packed.unwrap() - assertTrue(fun(11)) - assertFalse(fun(9)) - - @Test - def testSporeApplyWithEnvAlias(): Unit = - val spore = Spore.apply[Int, Int => Boolean](12) { env => x => x > env } - val fun = spore.unwrap() - assertTrue(fun(13)) - assertFalse(fun(11)) - - @Test - def testSporeApplyWithEnvAlias2(): Unit = - val spore = Spore.apply("Hello") { (env: String) => (x: Int) => x.toString() + env } - val fun = spore.unwrap() - assertEquals("12Hello", fun(12)) diff --git a/core/jvm/src/test/scala/spores/test/SporeTests.scala b/core/jvm/src/test/scala/spores/test/SporeTests.scala deleted file mode 100644 index 54fe4ab..0000000 --- a/core/jvm/src/test/scala/spores/test/SporeTests.scala +++ /dev/null @@ -1,211 +0,0 @@ -package spores.test - -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 - -import spores.Spore -import spores.default.given - - -@RunWith(classOf[JUnit4]) -class SporeTests { - - @Test - def testWithoutEnv(): Unit = { - val b = Spore { (x: Int) => x + 2 } - val res = b.unwrap()(3) - assert(res == 5) - } - - @Test - def testWithoutEnv2(): Unit = { - def fun(s: Spore[Int => Int]): Unit = {} - - val s = Spore((x: Int) => x + 2) - - fun(s) - - val res = s.unwrap()(3) - assert(res == 5) - } - - @Test - def testWithoutEnvWithType(): Unit = { - val s: Spore[Int => Int] = Spore { - (x: Int) => x + 2 - } - val res = s.unwrap()(3) - assert(res == 5) - } - - @Test - def testWithoutEnvWithType1(): Unit = { - val s: Spore[Int => Int] = Spore { - x => x + 2 - } - val res = s.unwrap()(3) - assert(res == 5) - } - - /* the following does not compile: -[error] -- [E007] Type Mismatch Error: [...]/BlockTests.scala:37:61 -[error] 37 | val s: Spore[Int, Int] { type Env = Nothing } = Spore(y) { -[error] | ^ -[error] | Found: com.phaller.blocks.Spore[Int, Int]{Env = Int} -[error] | Required: com.phaller.blocks.Spore[Int, Int]{Env = Nothing} -[error] 38 | (x: Int) => x + 2 + env -[error] 39 | } - */ - /*@Test - def testWithoutEnvWithType1(): Unit = { - val y = 5 - val s: Spore[Int, Int] { type Env = Nothing } = Spore(y) { - (x: Int) => x + 2 + env - } - val res = s(3) - assert(res == 5) - }*/ - - @Test - def testWithEnv(): Unit = { - val y = 5 - val s = Spore.applyWithEnv(y) { - env => (x: Int) => x + env - } - val res = s.unwrap()(10) - assert(res == 15) - } - - /* -[error] -- Error: [...]/BlockTests.scala:83:35 -[error] 83 | env => (x: Int) => x + env + z -[error] | ^ -[error] |Invalid capture of variable `z`. Use first parameter of spore's body to refer to the spore's environment. - */ - /*@Test - def testWithEnvInvalidCapture(): Unit = { - val y = 5 - val z = 6 - val s = Spore(y) { - env => (x: Int) => x + env + z - } - val res = s(10) - assert(res == 21) - }*/ - - @Test - def testWithEnv2(): Unit = { - val str = "anonymous function" - val s: Spore[Int => Int] = Spore.applyWithEnv(str) { - env => (x: Int) => x + env.length - } - val res = s.unwrap()(10) - assert(res == 28) - } - - @Test - def testWithEnvTuple(): Unit = { - val str = "anonymous function" - val i = 5 - - val s: Spore[Int => Int] = Spore.applyWithEnv((str, i)) { - case (l, r) => (x: Int) => x + l.length - r - } - - val res = s.unwrap()(10) - assert(res == 23) - } - - @Test - def testWithEnvParamUntupling(): Unit = { - val str = "anonymous function" - val i = 5 - - val s = Spore.applyWithEnv((str, i)) { - (l, r) => (x: Int) => x + l.length - r - } - - val res = s.unwrap()(10) - assert(res == 23) - } - - @Test - def testWithEnvWithType(): Unit = { - val y = 5 - val s: Spore[Int => Int] = Spore.applyWithEnv(y) { - env => (x: Int) => x + env - } - val res = s.unwrap()(11) - assert(res == 16) - } - - @Test - def testThunk(): Unit = { - val x = 5 - val t = Spore.applyWithEnv(x) { env => () => - env + 7 - } - val res = t.unwrap()() - assert(res == 12) - } - - @Test - def testNestedWithoutEnv(): Unit = { - val s = Spore { - (x: Int) => - val s2 = Spore { (y: Int) => y - 1 } - s2.unwrap()(x) + 2 - } - val res = s.unwrap()(3) - assert(res == 4) - } - - @Test - def testNestedWithEnv1(): Unit = { - val z = 5 - - val s = Spore.applyWithEnv(z) { - env => (x: Int) => - val s2 = Spore.applyWithEnv(env) { env => (y: Int) => env + y - 1 } - s2.unwrap()(x) + 2 - } - val res = s.unwrap()(3) - assert(res == 9) - } - - @Test - def testNestedWithEnv2(): Unit = { - val z = 5 - val w = 6 - - val s = Spore.applyWithEnv((w, z)) { - case (l, r) => (x: Int) => - val s2 = Spore.applyWithEnv(r) { env => (y: Int) => env + y - 1 } - s2.unwrap()(x) + 2 - l - } - - val res = s.unwrap()(3) - assert(res == 3) - } - - @Test - def testLocalClasses(): Unit = { - val x = 5 - - val s = Spore.applyWithEnv(x) { env => (y: Int) => - class Local2 { def m() = y } - class Local(p: Int)(using loc: Local2) { - val fld = env + p - } - - given l2: Local2 = new Local2 - val l = new Local(y + 1) - l.fld - } - - val res = s.unwrap()(3) - assert(res == 9) - } - -} diff --git a/core/jvm/src/test/scala/spores/test/TrieMapTest.scala b/core/jvm/src/test/scala/spores/test/TrieMapTest.scala deleted file mode 100644 index b136546..0000000 --- a/core/jvm/src/test/scala/spores/test/TrieMapTest.scala +++ /dev/null @@ -1,43 +0,0 @@ -package spores.test - -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 - -import scala.collection.concurrent.TrieMap - -import spores.default.* -import spores.default.given - - -case class Customer(name: String, customerNo: Int) -case class CustomerInfo(customerNo: Int, age: Int, since: Int) - -@RunWith(classOf[JUnit4]) -class TrieMapTest { - - type CustomerMap = TrieMap[Int, CustomerInfo] - given Duplicable[CustomerMap] with - def duplicate(map: CustomerMap): CustomerMap = map.snapshot() - - val customerData = TrieMap.empty[Int, CustomerInfo] - - @Test - def test(): Unit = { - val s = Duplicate.applyWithEnv[CustomerMap, List[Customer] => Float](customerData) { data => cs => - val infos = cs.flatMap { c => - data.get(c.customerNo) match { - case Some(info) => List(info) - case None => List() - } - } - val sumAges = infos.foldLeft(0)(_ + _.age).toFloat - if (infos.size == 0) 0 - else sumAges / infos.size - } - - val res = s(List()) - assert(res == 0) - } - -} diff --git a/core/shared/src/test/scala/spores/SporeClassBuilderErrorTests.scala b/core/shared/src/test/scala/spores/SporeClassBuilderErrorTests.scala deleted file mode 100644 index 844af8b..0000000 --- a/core/shared/src/test/scala/spores/SporeClassBuilderErrorTests.scala +++ /dev/null @@ -1,154 +0,0 @@ -package spores - -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.junit.Test -import org.junit.Assert.* - -import spores.default.given -import spores.default.* -import spores.TestUtils.* - -object SporeClassBuilderErrorTests: - object NotClzClz extends SporeClassBuilder[Int => Int](x => x) - - def someMethod: SporeClassBuilder[Int => Int] = { - class Local extends SporeClassBuilder[Int => Int](x => x) - new Local() - } - - class NestedBuilderInClass: - class Inner extends SporeClassBuilder[Int](10) - - class ClassWithoutPublicConstructor private () extends SporeClassBuilder[Int => Int](x => x) - object ClassWithoutPublicConstructor: - def apply(): ClassWithoutPublicConstructor = new ClassWithoutPublicConstructor() - - class ClassWithParameters(i: Int) extends SporeClassBuilder[() => Int](() => i) - - class F[T] - class ClassWithContext1[T: F] extends SporeClassBuilder[F[T]](summon) - class ClassWithContext2[T](using F[T]) extends SporeClassBuilder[F[T]](summon) - class ClassWithContext3[T](implicit f: F[T]) extends SporeClassBuilder[F[T]](summon) - -@RunWith(classOf[JUnit4]) -class SporeClassBuilderErrorTests: - import SporeClassBuilderErrorTests.* - - @Test - def testObjectSporeClassBuilderError(): Unit = - assertTrue: - typeCheckErrorMessages: - """ - NotClzClz.build() - """ - .contains: - """ - The provided SporeClassBuilder `spores.SporeClassBuilderErrorTests$.NotClzClz$` is not a class. - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - val notClzClz = NotClzClz - notClzClz.build() - """ - .contains: - """ - The provided SporeClassBuilder `spores.SporeClassBuilderErrorTests$.NotClzClz$` is not a class. - """.trim() - - @Test - def testSporeClassBuilderNestedInClassError(): Unit = - assertTrue: - typeCheckErrorMessages: - """ - val builder = new NestedBuilderInClass() - val pred = new builder.Inner() - pred.build() - """ - .contains: - """ - The provided SporeClassBuilder `spores.SporeClassBuilderErrorTests$.NestedBuilderInClass.Inner` is nested in a class. - """.trim() - - @Test - def testSporeClassBuilderNestedInMethodError(): Unit = - assertTrue: - typeCheckErrorMessages: - """ - someMethod.build() - """ - .contains: - """ - The provided SporeClassBuilder `spores.SporeClassBuilder` is not a concrete class. - """.trim() - - @Test - def testSporeClassBuilderWithPrivateConstructorError(): Unit = - assertTrue: - typeCheckErrorMessages: - """ - ClassWithoutPublicConstructor().build() - """ - .contains: - """ - The provided SporeClassBuilder `spores.SporeClassBuilderErrorTests$.ClassWithoutPublicConstructor` `` does not have a public constructor. - """.trim() - - @Test - def testSporeClassBuilderWithParameterError(): Unit = - assertTrue: - typeCheckErrorMessages: - """ - ClassWithParameters(10).build() - """ - .contains: - """ - The constructor of the provided SporeClassBuilder `spores.SporeClassBuilderErrorTests$.ClassWithParameters` `` does not have an empty parameter list. - """.trim() - - @Test - def testSporeClassBuilderWithContextParameterError(): Unit = - // Catches a common mistake in which implicit parameters are used in the - // constructor. For example, this would seem like a reasonable thing to do, - // but will not work: - // - // class PackedRW[T: ReadWriter] extends SporeClassBuilder[ReadWriter[T]](summon[ReadWriter[T]]) - // given Spore[ReadWriter[T]] = PackedRW[T].build() - // - // // This will crash at runtime, as the init method is assumed to not have any params. - // summon[Spore[ReadWriter[Int]]].unwrap() - - assertTrue: - typeCheckErrorMessages: - """ - given F[Int] = new F[Int]() - ClassWithContext1[Int].build() - """ - .contains: - """ - The constructor of the provided SporeClassBuilder `spores.SporeClassBuilderErrorTests$.ClassWithContext1` `` contains a context parameter list. - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - given F[Int] = new F[Int]() - ClassWithContext2[Int].build() - """ - .contains: - """ - The constructor of the provided SporeClassBuilder `spores.SporeClassBuilderErrorTests$.ClassWithContext2` `` contains a context parameter list. - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - given F[Int] = new F[Int]() - ClassWithContext3[Int].build() - """ - .contains: - """ - The constructor of the provided SporeClassBuilder `spores.SporeClassBuilderErrorTests$.ClassWithContext3` `` contains a context parameter list. - """.trim() diff --git a/core/shared/src/test/scala/spores/SporeClassBuilderTests.scala b/core/shared/src/test/scala/spores/SporeClassBuilderTests.scala deleted file mode 100644 index fc78548..0000000 --- a/core/shared/src/test/scala/spores/SporeClassBuilderTests.scala +++ /dev/null @@ -1,96 +0,0 @@ -package spores - -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.junit.Test -import org.junit.Assert.* - -import spores.default.given -import spores.default.* -import spores.TestUtils.* - -object SporeClassBuilderTests: - class Thunk[T] extends SporeClassBuilder[T => () => T](t => () => t) - - class Predicate extends SporeClassBuilder[Int => Boolean](x => x > 10) - - class FilterWithTypeParam[T] extends SporeClassBuilder[Spore[T => Boolean] => T => Option[T]]({ env => x => if env.unwrap().apply(x) then Some(x) else None }) - - class Flatten[T] extends SporeClassBuilder[List[List[T]] => List[T]](x => x.flatten) - - object NestedBuilder: - class Predicate extends SporeClassBuilder[Int => Boolean](x => x > 10) - -@RunWith(classOf[JUnit4]) -class SporeClassBuilderTests: - import SporeClassBuilderTests.* - - @Test - def testSporeClassBuilderPack(): Unit = - val predicate = new Predicate().build() - assertTrue(predicate(11)) - assertFalse(predicate(9)) - assertTrue(predicate.unwrap()(11)) - assertFalse(predicate.unwrap()(9)) - - @Test - def testSporeClassBuilderWithEnv(): Unit = - val thunk = new Thunk[Int].build().withEnv(10) - assertEquals(10, thunk()) - assertEquals(10, thunk.unwrap()()) - - @Test - def testSporeClassBuilderWithTypeParam(): Unit = - val flatten = new Flatten[Int].build() - val nestedList = List(List(1), List(2), List(3)) - assertEquals(nestedList.flatten, flatten(nestedList)) - assertEquals(nestedList.flatten, flatten.unwrap()(nestedList)) - - @Test - def testHigherLevelSporeClassBuilder(): Unit = - val filter = new FilterWithTypeParam[Int].build() - val predicate = new Predicate().build() - assertEquals(Some(11), filter(predicate)(11)) - assertEquals(None, filter(predicate)(9)) - assertEquals(Some(11), filter.unwrap()(predicate)(11)) - assertEquals(None, filter.unwrap()(predicate)(9)) - - @Test - def testSporeClassBuilderReadWriter(): Unit = - val json = """{"$type":"spores.Packed.PackedClass","className":"spores.SporeClassBuilderTests$Predicate"}""" - - val packed = upickle.default.write(new Predicate().build()) - assertEquals(json, packed) - - val loaded = upickle.default.read[Spore[Int => Boolean]](json) - assertTrue(loaded(11)) - assertFalse(loaded(9)) - assertTrue(loaded.unwrap()(11)) - assertFalse(loaded.unwrap()(9)) - - @Test - def testSporeClassBuilderWithTypeParamReadWriter(): Unit = - val json = """{"$type":"spores.Packed.PackedClass","className":"spores.SporeClassBuilderTests$Flatten"}""" - - val packed = upickle.default.write(new Flatten[Int].build()) - assertEquals(json, packed) - - val loaded = upickle.default.read[Spore[List[List[Int]] => List[Int]]](json) - val nestedList = List(List(1), List(2), List(3)) - assertEquals(nestedList.flatten, loaded(nestedList)) - assertEquals(nestedList.flatten, loaded.unwrap()(nestedList)) - - @Test - def testSporeClassBuilderWithEnvReadWriter(): Unit = - val json = """{"$type":"spores.Packed.PackedWithEnv","packed":{"$type":"spores.Packed.PackedClass","className":"spores.SporeClassBuilderTests$FilterWithTypeParam"},"packedEnv":{"$type":"spores.Packed.PackedEnv","env":"{\"$type\":\"spores.Packed.PackedClass\",\"className\":\"spores.SporeClassBuilderTests$Predicate\"}","rw":{"$type":"spores.Packed.PackedObject","className":"spores.ReadWriters$SporeRW$"}}}""" - - val predicate = new Predicate().build() - val filter = new FilterWithTypeParam[Int].build().withEnv(predicate) - val packed = upickle.default.write(filter) - assertEquals(json, packed) - - val loaded = upickle.default.read[Spore[Int => Option[Int]]](json) - assertEquals(Some(11), loaded(11)) - assertEquals(None, loaded(9)) - assertEquals(Some(11), loaded.unwrap()(11)) - assertEquals(None, loaded.unwrap()(9)) diff --git a/core/shared/src/test/scala/spores/SporeObjectBuilderErrorTests.scala b/core/shared/src/test/scala/spores/SporeObjectBuilderErrorTests.scala deleted file mode 100644 index b12e870..0000000 --- a/core/shared/src/test/scala/spores/SporeObjectBuilderErrorTests.scala +++ /dev/null @@ -1,84 +0,0 @@ -package spores - -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.junit.Test -import org.junit.Assert.* - -import spores.default.given -import spores.default.* -import spores.TestUtils.* - -object SporeBuilderErrorTests: - class NotObjObj extends SporeBuilder[Int => Int](x => x) - - class SomeClass: - object NotTopLevel extends SporeBuilder[Int => Int](x => x) - - def someMethod: SporeBuilder[Int => Int] = { - object NotTopLevel extends SporeBuilder[Int => Int](x => x) - NotTopLevel - } - -@RunWith(classOf[JUnit4]) -class SporeBuilderErrorTests: - import SporeBuilderErrorTests.* - - @Test - def testClassSporeBuilderError(): Unit = - assertTrue: - typeCheckErrorMessages: - """ - new NotObjObj().build() - """ - .contains: - """ - The provided SporeBuilder `spores.SporeBuilderErrorTests$.NotObjObj` is not an object. - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - val notObjObj = new NotObjObj() - notObjObj.build() - """ - .contains: - """ - The provided SporeBuilder `spores.SporeBuilderErrorTests$.NotObjObj` is not an object. - """.trim() - - @Test - def testNotTopLevelError(): Unit = - assertTrue: - typeCheckErrorMessages: - """ - val notTopLevel = new SomeClass().NotTopLevel - notTopLevel.build() - """ - .contains: - """ - The provided SporeBuilder `spores.SporeBuilderErrorTests$.SomeClass.NotTopLevel$` is not a top-level object; its owner `SomeClass` is not a top-level object nor a package. - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - val notObject = someMethod - notObject.build() - """ - .contains: - """ - The provided SporeBuilder `spores.SporeBuilder` is not an object. - """.trim() - - assertTrue: - typeCheckErrorMessages: - """ - object Builder extends SporeBuilder[Int => String](x => x.toString.reverse) - Builder.build() - """ - .exists: - _.matches: - raw""" - The provided SporeBuilder `.*Builder\$$` is not a top-level object; its owner `.*` is not a top-level object nor a package. - """.trim() diff --git a/core/shared/src/test/scala/spores/SporeObjectBuilderTests.scala b/core/shared/src/test/scala/spores/SporeObjectBuilderTests.scala deleted file mode 100644 index 6cb325b..0000000 --- a/core/shared/src/test/scala/spores/SporeObjectBuilderTests.scala +++ /dev/null @@ -1,194 +0,0 @@ -package spores - -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.junit.Test -import org.junit.Assert.* - -import spores.default.given -import spores.default.* -import spores.TestUtils.* - -object SporeBuilderTests: - object Thunk extends SporeBuilder[() => Int](() => 10) - - object Predicate extends SporeBuilder[Int => Boolean](x => x > 10) - - object HigherLevelFilter extends SporeBuilder[Spore[Int => Boolean] => Int => Option[Int]]({ env => x => if env.unwrap().apply(x) then Some(x) else None }) - - object PredicateCtx extends SporeBuilder[Int ?=> Boolean](summon[Int] > 10) - - object OptionMapper extends SporeBuilder[Option[Int] => Int](x => x.getOrElse(0)) - - object ListReducer extends SporeBuilder[List[Int] => Int](x => x.sum) - - object NestedBuilder: - object Predicate extends SporeBuilder[Int => Boolean](x => x > 10) - - object Funct0 extends SporeBuilder[() => Int](() => 1) - - object Funct1 extends SporeBuilder[(Int) => Int](x1 => x1 + 1) - - object Funct2 extends SporeBuilder[(Int, Int) => Int]((x1, x2) => x1 + x2 + 1) - - object Funct3 extends SporeBuilder[(Int, Int, Int) => Int]((x1, x2, x3) => x1 + x2 + x3 + 1) - - object Funct4 extends SporeBuilder[(Int, Int, Int, Int) => Int]((x1, x2, x3, x4) => x1 + x2 + x3 + x4 + 1) - - object Funct5 extends SporeBuilder[(Int, Int, Int, Int, Int) => Int]((x1, x2, x3, x4, x5) => x1 + x2 + x3 + x4 + x5 + 1) - - object Funct6 extends SporeBuilder[(Int, Int, Int, Int, Int, Int) => Int]((x1, x2, x3, x4, x5, x6) => x1 + x2 + x3 + x4 + x5 + x6 + 1) - - object Funct7 extends SporeBuilder[(Int, Int, Int, Int, Int, Int, Int) => Int]((x1, x2, x3, x4, x5, x6, x7) => x1 + x2 + x3 + x4 + x5 + x6 + x7 + 1) - -@RunWith(classOf[JUnit4]) -class SporeBuilderTests: - import SporeBuilderTests.* - - @Test - def testSporeBuilderPack(): Unit = - val predicate = Predicate.build() - assertTrue(predicate(11)) - assertFalse(predicate(9)) - val unwrapped = predicate.unwrap() - assertTrue(unwrapped(11)) - assertFalse(unwrapped(9)) - - @Test - def testNestedSporeBuilderPack(): Unit = - val predicate = NestedBuilder.Predicate.build() - assertTrue(predicate(11)) - assertFalse(predicate(9)) - val unwrapped = predicate.unwrap() - assertTrue(unwrapped(11)) - assertFalse(unwrapped(9)) - - @Test - def testSporeBuilderThunk(): Unit = - val thunk = Thunk.build() - assertEquals(10, thunk.apply()) - val unwrapped = thunk.unwrap() - assertEquals(10, unwrapped.apply()) - - @Test - def testWithEnv(): Unit = - val predicate9 = Predicate.build().withEnv(9) - val predicate11 = Predicate.build().withEnv(11) - assertFalse(predicate9.unwrap()) - assertTrue(predicate11.unwrap()) - - @Test - def testWithEnv2(): Unit = - val env9 = Env.apply(9) - val predicate9 = Predicate.build().withEnv2(env9) - val env11 = Env.apply(11) - val predicate11 = Predicate.build().withEnv2(env11) - assertFalse(predicate9.unwrap()) - assertTrue(predicate11.unwrap()) - - @Test - def testWithCtx(): Unit = - val predicate9 = PredicateCtx.build().withCtx(9) - val packed11 = PredicateCtx.build().withCtx(11) - assertFalse(predicate9.unwrap()) - assertTrue(packed11.unwrap()) - - @Test - def testWithCtx2(): Unit = - val env9 = Env.apply(9) - val predicate9 = PredicateCtx.build().withCtx2(env9) - val env11 = Env.apply(11) - val packed11 = PredicateCtx.build().withCtx2(env11) - assertFalse(predicate9.unwrap()) - assertTrue(packed11.unwrap()) - - @Test - def testPackBuildHigherOrderSporeBuilder(): Unit = - val predicate = Predicate.build() - val filter = HigherLevelFilter.build().withEnv(predicate) - assertEquals(Some(11), filter(11)) - assertEquals(None, filter(9)) - val unwrapped = filter.unwrap() - assertEquals(Some(11), unwrapped(11)) - assertEquals(None, unwrapped(9)) - - @Test - def testSporeReadWriter(): Unit = - val json = """{"$type":"spores.Packed.PackedObject","className":"spores.SporeBuilderTests$Predicate$"}""" - - val packed = upickle.default.write(Predicate.build()) - assertEquals(json, packed) - - val loaded = upickle.default.read[Spore[Int => Boolean]](json) - assertTrue(loaded(11)) - assertFalse(loaded(9)) - assertTrue(loaded.unwrap()(11)) - assertFalse(loaded.unwrap()(9)) - - @Test - def testNestedSporeReadWriter(): Unit = - val json = """{"$type":"spores.Packed.PackedObject","className":"spores.SporeBuilderTests$NestedBuilder$Predicate$"}""" - - val packed = upickle.default.write(NestedBuilder.Predicate.build()) - assertEquals(json, packed) - - val loaded = upickle.default.read[Spore[Int => Boolean]](json) - assertTrue(loaded(11)) - assertFalse(loaded(9)) - assertTrue(loaded.unwrap()(11)) - assertFalse(loaded.unwrap()(9)) - - @Test - def testSporeReadWriterWithEnv(): Unit = - val json = """{"$type":"spores.Packed.PackedWithEnv","packed":{"$type":"spores.Packed.PackedObject","className":"spores.SporeBuilderTests$HigherLevelFilter$"},"packedEnv":{"$type":"spores.Packed.PackedEnv","env":"{\"$type\":\"spores.Packed.PackedObject\",\"className\":\"spores.SporeBuilderTests$Predicate$\"}","rw":{"$type":"spores.Packed.PackedObject","className":"spores.ReadWriters$SporeRW$"}}}""" - - val predicate = Predicate.build() - val filter = HigherLevelFilter.build().withEnv(predicate) - val packed = upickle.default.write(filter) - assertEquals(json, packed) - - val loaded = upickle.default.read[Spore[Int => Option[Int]]](json) - assertEquals(Some(11), loaded(11)) - assertEquals(None, loaded(9)) - assertEquals(Some(11), loaded.unwrap()(11)) - assertEquals(None, loaded.unwrap()(9)) - - @Test - def testOptionEnvironment(): Unit = - val packed = OptionMapper.build().withEnv(Some(11)) - val fun = packed.unwrap() - assertEquals(11, fun) - - val packed2 = OptionMapper.build().withEnv(Some(11)) - val fun2 = packed2.unwrap() - assertEquals(11, fun2) - - @Test - def testListEnvironment(): Unit = - val packed = ListReducer.build().withEnv(List(1, 2, 3)) - val fun = packed.unwrap() - assertEquals(6, fun) - - val packed2 = ListReducer.build().withEnv(List(1, 2, 3)) - val fun2 = packed2.unwrap() - assertEquals(6, fun2) - - @Test - def testSporeApplyMethods(): Unit = - val funct0 = Funct0.build() - val funct1 = Funct1.build() - val funct2 = Funct2.build() - val funct3 = Funct3.build() - val funct4 = Funct4.build() - val funct5 = Funct5.build() - val funct6 = Funct6.build() - val funct7 = Funct7.build() - - assertEquals(1, funct0.apply()) - assertEquals(2, funct1.apply(1)) - assertEquals(4, funct2.apply(1, 2)) - assertEquals(7, funct3.apply(1, 2, 3)) - assertEquals(11, funct4.apply(1, 2, 3, 4)) - assertEquals(16, funct5.apply(1, 2, 3, 4, 5)) - assertEquals(22, funct6.apply(1, 2, 3, 4, 5, 6)) - assertEquals(29, funct7.apply(1, 2, 3, 4, 5, 6, 7)) diff --git a/core/shared/src/test/scala/spores/test/DuplicableTests.scala b/core/shared/src/test/scala/spores/test/DuplicableTests.scala deleted file mode 100644 index cbc05cb..0000000 --- a/core/shared/src/test/scala/spores/test/DuplicableTests.scala +++ /dev/null @@ -1,229 +0,0 @@ -package spores -package test - -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 - -import spores.default.* -import spores.default.given - - -class C { - var f: Int = 0 -} - -object C { - given Duplicable[C] with { - def duplicate(x: C): C = { - val y = new C - y.f = x.f - y - } - } -} - -@RunWith(classOf[JUnit4]) -class DuplicableTests { - - @Test - def testDuplicateInt(): Unit = { - val x = 5 - val dup = summon[Duplicable[Int]] - assert(5 == dup.duplicate(x)) - } - - @Test - def testDuplicateThunk(): Unit = { - val x = 5 - val b = Duplicate.applyWithEnv(x) { env => () => - env + 1 - } - - val b2 = duplicate(b) - - val res = b2() - assert(res == 6) - } - - @Test - def testDuplicateThunkWithMutableClass(): Unit = { - val x = new C - x.f = 4 - - val b = Duplicate.applyWithEnv(x) { env => () => - env.f + 1 - } - - val b2 = duplicate(b) - - val res = b2() - assert(res == 5) - - val dup = b.asInstanceOf[DuplicateWithEnv[C, Int]] - val dup2 = b2.asInstanceOf[DuplicateWithEnv[C, Int]] - assert(dup.env.unwrap() ne dup2.env.unwrap()) - assert(dup.env.unwrap().f == dup2.env.unwrap().f) - } - - @Test - def testDuplicatedThunkAccessesNewEnv(): Unit = { - val x = new C - - val b = Duplicate.applyWithEnv(x) { env => () => - env - } - - val b2 = duplicate(b) - - val envVal = b2() - - assert(envVal != x) - } - - @Test - def testDuplicatedThunk1(): Unit = { - val x = new C - x.f = 7 - - val b = Duplicate.applyWithEnv(x) { env => () => - env - } - - val b2 = duplicate(b) - - val envVal = b2() - - assert(envVal.f == 7) - assert(envVal ne x) - } - - @Test - def testDuplicatedThunk2(): Unit = { - val x = new C - x.f = 7 - - val b: Duplicate[() => C] = Duplicate.applyWithEnv(x) { env => () => - env - } - - val b2 = duplicate(b) - - val envVal = b2() - - assert(envVal.f == 7) - assert(envVal ne x) - } - - @Test - def testDuplicatedSporeNoCapture(): Unit = { - // spore does not capture anything - val s = Duplicate { - (x: Int) => x + 2 - } - val s2 = duplicate(s) - val res = s2(3) - assert(res == 5) - } - - @Test - def testDuplicateSporeWithEnv(): Unit = { - val x = new C - x.f = 4 - - val b = Duplicate.applyWithEnv(x) { - env => (y: Int) => env.f + y - } - - val b2 = duplicate(b) - val res = b2(3) - assert(res == 7) - } - - @Test - def testDuplicateSporeWithEnvGeneric(): Unit = { - def duplicateThenApply[T, R, B <: Duplicate[T => R] : Duplicable](spore: B, arg: T): R = { - val dup = summon[Duplicable[B]] - val duplicated = dup.duplicate(spore) - duplicated(arg) - } - - val x = new C - x.f = 4 - - val b = Duplicate.applyWithEnv(x) { - env => (y: Int) => env.f + y - } - - val res = duplicateThenApply(b, 3) - assert(res == 7) - } - - @Test - def testPassingSpore(): Unit = { - def m2(s: Duplicate[Int => Int], arg: Int): Int = { - s(arg) - } - - def m1(s: Duplicate[Int => Int]): Int = { - m2(s, 10) + 20 - } - - val x = new C - x.f = 4 - - val s = Duplicate.applyWithEnv(x) { - env => (y: Int) => env.f + y - } - - val res = m1(s) - assert(res == 34) - } - - @Test - def testPassingSporeAndDuplicate(): Unit = { - def m2[B <: Duplicate[Int => Int] : Duplicable](spore: B, arg: Int): Int = { - val dup = summon[Duplicable[B]] - val duplicated = dup.duplicate(spore) - duplicated(arg) - } - - def m1[B <: Duplicate[Int => Int] : Duplicable](spore: B): Int = { - m2(spore, 10) + 20 - } - - val x = new C - x.f = 4 - - val b = Duplicate.applyWithEnv(x) { - env => (y: Int) => env.f + y - } - - val res = m1(b) - assert(res == 34) - } - - @Test - def testDuplicateThenApply(): Unit = { - def duplicateThenApply[S <: Duplicate[Unit => C] : Duplicable](s: S): C = { - val dup = summon[Duplicable[S]] - val duplicated = dup.duplicate(s) - duplicated(()) - } - - val x = new C - // x is a mutable instance: - x.f = 7 - - // create thunk: - val b = Duplicate.applyWithEnv(x) { env => (_: Unit) => - env - } - - val y = duplicateThenApply(b) - assert(y.f == 7) - // references are not equal: - assert(y ne x) - } - -} diff --git a/core/shared/src/test/scala/spores/upickle/test/PickleTests.scala b/core/shared/src/test/scala/spores/upickle/test/PickleTests.scala deleted file mode 100644 index addcc4b..0000000 --- a/core/shared/src/test/scala/spores/upickle/test/PickleTests.scala +++ /dev/null @@ -1,64 +0,0 @@ -package spores.pickle.test - -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 - -import upickle.default.* - -import spores.{Spore, Reflection, SporeBuilder} -import spores.default.given - - -@RunWith(classOf[JUnit4]) -class PickleTests { - - @Test - def testReflection(): Unit = { - val b = Reflection.loadModuleFieldValue[SporeBuilder[Int => Int => Int]]("spores.pickle.test.MySpore$") - val fun = b.fun - val res = fun(12)(3) - assert(res == 16) - } - - @Test - def testSporeReadWriter(): Unit = { - // create a spore - val spore: Spore[Int => Int] = MySpore.build().withEnv(12) - - // pickle spore - val pickled = write(spore) - assert(pickled == """{"$type":"spores.Packed.PackedWithEnv","packed":{"$type":"spores.Packed.PackedObject","className":"spores.pickle.test.MySpore$"},"packedEnv":{"$type":"spores.Packed.PackedEnv","env":"12","rw":{"$type":"spores.Packed.PackedObject","className":"spores.ReadWriters$IntRW$"}}}""") - - // unpickle spore - val unpickled = read[Spore[Int => Int]](pickled) - assert(unpickled.unwrap()(3) == 16) - assert(unpickled == spore) - } - - @Test - def testSporeWithoutEnvReadWriter(): Unit = { - val spore: Spore[Int => Int] = SporeWithoutEnv.build() - - val pickled = write(spore) - assert(pickled == """{"$type":"spores.Packed.PackedObject","className":"spores.pickle.test.SporeWithoutEnv$"}""") - - val unpickled = read[Spore[Int => Int]](pickled) - assert(unpickled.unwrap()(3) == 4) - assert(unpickled == spore) - } - - @Test - def testSporeAppendStringReadWriter(): Unit = { - val spore: Spore[List[String] => List[String]] = AppendString.build().withEnv("three") - - val pickled = write(spore) - assert(pickled == """{"$type":"spores.Packed.PackedWithEnv","packed":{"$type":"spores.Packed.PackedObject","className":"spores.pickle.test.AppendString$"},"packedEnv":{"$type":"spores.Packed.PackedEnv","env":"\"three\"","rw":{"$type":"spores.Packed.PackedObject","className":"spores.ReadWriters$StringRW$"}}}""") - - val unpickled = read[Spore[List[String] => List[String]]](pickled) - val l3 = List("four") - assert(unpickled.unwrap()(l3) == List("four", "three")) - assert(unpickled == spore) - } - -} diff --git a/project/build.properties b/project/build.properties deleted file mode 100644 index bbb0b60..0000000 --- a/project/build.properties +++ /dev/null @@ -1 +0,0 @@ -sbt.version=1.11.2 diff --git a/project/plugins.sbt b/project/plugins.sbt deleted file mode 100644 index de1dbcf..0000000 --- a/project/plugins.sbt +++ /dev/null @@ -1,9 +0,0 @@ -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.0") - -addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.2.0") - -addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.6") - -addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.2.0") - -addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.3.1") diff --git a/sample/js/src/main/scala/sporks/sample/platform/package.scala b/sample/src-js/spores/sample/platform/package.scala similarity index 100% rename from sample/js/src/main/scala/sporks/sample/platform/package.scala rename to sample/src-js/spores/sample/platform/package.scala diff --git a/sample/jvm-native/src/main/scala/spores/sample/platform/package.scala b/sample/src-jvm-native/spores/sample/platform/package.scala similarity index 100% rename from sample/jvm-native/src/main/scala/spores/sample/platform/package.scala rename to sample/src-jvm-native/spores/sample/platform/package.scala diff --git a/sample/jvm/src/main/scala/spores/sample/Agent.scala b/sample/src-jvm/spores/sample/Agent.scala similarity index 100% rename from sample/jvm/src/main/scala/spores/sample/Agent.scala rename to sample/src-jvm/spores/sample/Agent.scala diff --git a/sample/jvm/src/main/scala/spores/sample/AutoCaptureExample.scala b/sample/src-jvm/spores/sample/AutoCaptureExample.scala similarity index 100% rename from sample/jvm/src/main/scala/spores/sample/AutoCaptureExample.scala rename to sample/src-jvm/spores/sample/AutoCaptureExample.scala diff --git a/sample/jvm/src/main/scala/spores/sample/ForComprehension.scala b/sample/src-jvm/spores/sample/ForComprehension.scala similarity index 100% rename from sample/jvm/src/main/scala/spores/sample/ForComprehension.scala rename to sample/src-jvm/spores/sample/ForComprehension.scala diff --git a/sample/jvm/src/main/scala/spores/sample/SporeExample.scala b/sample/src-jvm/spores/sample/SporeExample.scala similarity index 100% rename from sample/jvm/src/main/scala/spores/sample/SporeExample.scala rename to sample/src-jvm/spores/sample/SporeExample.scala diff --git a/sample/jvm/src/main/scala/spores/sample/Workflow.scala b/sample/src-jvm/spores/sample/Workflow.scala similarity index 100% rename from sample/jvm/src/main/scala/spores/sample/Workflow.scala rename to sample/src-jvm/spores/sample/Workflow.scala diff --git a/sample/jvm/src/main/scala/spores/sample/futures/FutureMap.scala b/sample/src-jvm/spores/sample/futures/FutureMap.scala similarity index 100% rename from sample/jvm/src/main/scala/spores/sample/futures/FutureMap.scala rename to sample/src-jvm/spores/sample/futures/FutureMap.scala diff --git a/sample/jvm/src/main/scala/spores/sample/futures/Futures.scala b/sample/src-jvm/spores/sample/futures/Futures.scala similarity index 100% rename from sample/jvm/src/main/scala/spores/sample/futures/Futures.scala rename to sample/src-jvm/spores/sample/futures/Futures.scala diff --git a/sample/jvm/src/main/scala/spores/sample/futures/ParallelTreeReduction.scala b/sample/src-jvm/spores/sample/futures/ParallelTreeReduction.scala similarity index 100% rename from sample/jvm/src/main/scala/spores/sample/futures/ParallelTreeReduction.scala rename to sample/src-jvm/spores/sample/futures/ParallelTreeReduction.scala diff --git a/sample/shared/src/main/scala/sporks/sample/BuilderExample.scala b/sample/src/spores/sample/BuilderExample.scala similarity index 100% rename from sample/shared/src/main/scala/sporks/sample/BuilderExample.scala rename to sample/src/spores/sample/BuilderExample.scala diff --git a/core/js-native/src/main/scala/spores/SporeObjectCompanionJVM.scala b/spores3/src-js-native/spores/SporeObjectCompanionJVM.scala similarity index 100% rename from core/js-native/src/main/scala/spores/SporeObjectCompanionJVM.scala rename to spores3/src-js-native/spores/SporeObjectCompanionJVM.scala diff --git a/core/js/src/main/scala/spores/Reflection.scala b/spores3/src-js/spores/Reflection.scala similarity index 100% rename from core/js/src/main/scala/spores/Reflection.scala rename to spores3/src-js/spores/Reflection.scala diff --git a/core/jvm/src/main/scala/spores/Reflection.scala b/spores3/src-jvm/spores/Reflection.scala similarity index 100% rename from core/jvm/src/main/scala/spores/Reflection.scala rename to spores3/src-jvm/spores/Reflection.scala diff --git a/core/jvm/src/main/scala/spores/SporeObjectCompanionJVM.scala b/spores3/src-jvm/spores/SporeObjectCompanionJVM.scala similarity index 100% rename from core/jvm/src/main/scala/spores/SporeObjectCompanionJVM.scala rename to spores3/src-jvm/spores/SporeObjectCompanionJVM.scala diff --git a/core/jvm/src/main/scala/spores/jvm/AutoCapture.scala b/spores3/src-jvm/spores/jvm/AutoCapture.scala similarity index 100% rename from core/jvm/src/main/scala/spores/jvm/AutoCapture.scala rename to spores3/src-jvm/spores/jvm/AutoCapture.scala diff --git a/core/jvm/src/main/scala/spores/jvm/Spore.scala b/spores3/src-jvm/spores/jvm/Spore.scala similarity index 100% rename from core/jvm/src/main/scala/spores/jvm/Spore.scala rename to spores3/src-jvm/spores/jvm/Spore.scala diff --git a/core/native/src/main/scala/spores/Reflection.scala b/spores3/src-native/spores/Reflection.scala similarity index 100% rename from core/native/src/main/scala/spores/Reflection.scala rename to spores3/src-native/spores/Reflection.scala diff --git a/core/shared/src/main/scala/spores/Conversions.scala b/spores3/src/spores/Conversions.scala similarity index 100% rename from core/shared/src/main/scala/spores/Conversions.scala rename to spores3/src/spores/Conversions.scala diff --git a/core/shared/src/main/scala/spores/Duplicable.scala b/spores3/src/spores/Duplicable.scala similarity index 100% rename from core/shared/src/main/scala/spores/Duplicable.scala rename to spores3/src/spores/Duplicable.scala diff --git a/core/shared/src/main/scala/spores/Duplicate.scala b/spores3/src/spores/Duplicate.scala similarity index 100% rename from core/shared/src/main/scala/spores/Duplicate.scala rename to spores3/src/spores/Duplicate.scala diff --git a/core/shared/src/main/scala/spores/Env.scala b/spores3/src/spores/Env.scala similarity index 100% rename from core/shared/src/main/scala/spores/Env.scala rename to spores3/src/spores/Env.scala diff --git a/core/shared/src/main/scala/spores/Macros.scala b/spores3/src/spores/Macros.scala similarity index 100% rename from core/shared/src/main/scala/spores/Macros.scala rename to spores3/src/spores/Macros.scala diff --git a/core/shared/src/main/scala/spores/ReadWriters.scala b/spores3/src/spores/ReadWriters.scala similarity index 100% rename from core/shared/src/main/scala/spores/ReadWriters.scala rename to spores3/src/spores/ReadWriters.scala diff --git a/core/shared/src/main/scala/spores/Spore.scala b/spores3/src/spores/Spore.scala similarity index 100% rename from core/shared/src/main/scala/spores/Spore.scala rename to spores3/src/spores/Spore.scala diff --git a/core/shared/src/main/scala/spores/SporeBuilder.scala b/spores3/src/spores/SporeBuilder.scala similarity index 100% rename from core/shared/src/main/scala/spores/SporeBuilder.scala rename to spores3/src/spores/SporeBuilder.scala diff --git a/core/shared/src/main/scala/spores/SporeClassBuilder.scala b/spores3/src/spores/SporeClassBuilder.scala similarity index 100% rename from core/shared/src/main/scala/spores/SporeClassBuilder.scala rename to spores3/src/spores/SporeClassBuilder.scala diff --git a/core/shared/src/main/scala/spores/SporeLambdaBuilder.scala b/spores3/src/spores/SporeLambdaBuilder.scala similarity index 100% rename from core/shared/src/main/scala/spores/SporeLambdaBuilder.scala rename to spores3/src/spores/SporeLambdaBuilder.scala diff --git a/core/shared/src/main/scala/spores/default.scala b/spores3/src/spores/default.scala similarity index 100% rename from core/shared/src/main/scala/spores/default.scala rename to spores3/src/spores/default.scala diff --git a/core/shared/src/main/scala/spores/package.scala b/spores3/src/spores/package.scala similarity index 100% rename from core/shared/src/main/scala/spores/package.scala rename to spores3/src/spores/package.scala diff --git a/spores3/test/src-jvm/spores/jvm/AutoCaptureErrorTests.scala b/spores3/test/src-jvm/spores/jvm/AutoCaptureErrorTests.scala new file mode 100644 index 0000000..41229a0 --- /dev/null +++ b/spores3/test/src-jvm/spores/jvm/AutoCaptureErrorTests.scala @@ -0,0 +1,439 @@ +package spores.jvm + +import utest._ + +import upickle.default.* + +import spores.default.* +import spores.default.given +import spores.TestUtils.* + + +object AutoCaptureErrorTestsDefs { + + /** Foo values cannot be captured as there is no Spore[ReadWriter[Foo]]]. */ + case class Foo(x: Int, y: Int) + + /** Opaque type without a ReadWriter. */ + opaque type OpaqueInt = Int + object OpaqueInt { + def apply(value: Int): OpaqueInt = value + def unwrap(value: OpaqueInt): Int = value + } + + // For some reason this doesn't cause any errors when using the + // `typeCheckErrorMessages` method, but it does so here... + // class Outer { outer => + // val y = 12 + // class Inner { + // def foo = Spore.auto { (x: Int) => x + outer.y } + // } + // } +} + + +object AutoCaptureErrorTests extends TestSuite { + import AutoCaptureErrorTestsDefs.* + + val tests = Tests { + test("testCaptureIdentError") { + assert: + typeCheckErrorMessages: + """ + val foo = Foo(12, 13) + Spore.auto { foo } + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `foo`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + + assert: + typeCheckErrorMessages: + """ + val foo = Foo(12, 13) + Spore.auto { (x: Int) => x + foo.x + foo.y } + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `foo`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + + assert: + typeCheckErrorMessages: + """ + val foo = Foo(12, 13) + Spore.auto { def bar(x: Int): Int = { x + foo.x } } + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `foo`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + } + + test("testCaptureClassError") { + assert: + typeCheckErrorMessages: + """ + class A(val a: Int) + Spore.auto { new A(12) } + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `A`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + + assert: + typeCheckErrorMessages: + """ + class A(val a: Int) + Spore.auto { (x: Int) => x + new A(12).a } + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `A`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + } + + test("testCaptureMethodError") { + assert: + typeCheckErrorMessages: + """ + def captureMeIfYouCan(): Int = 12 + Spore.auto { (x: Int) => x + captureMeIfYouCan() } + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `captureMeIfYouCan`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + } + + test("testCaptureThisError") { + assert: + typeCheckErrorMessages: + """ + class Outer { + Spore.auto { this } + } + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `Outer`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + + assert: + typeCheckErrorMessages: + """ + class Outer { + val captureThisXIfYouCan = 99 + Spore.auto { this.captureThisXIfYouCan } + } + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `Outer`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + + assert: + typeCheckErrorMessages: + """ + class Outer { + val captureThisXIfYouCan = 99 + Spore.auto { (x: Int) => x + captureThisXIfYouCan } + } + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `Outer`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + } + + test("testCaptureImplicitThisError") { + assert: + typeCheckErrorMessages: + """ + case class Bar(x: Int, y: Int) + given ReadWriter[Bar] = macroRW[Bar] + given Spore[ReadWriter[Bar]] = Spore.auto { summon[ReadWriter[Bar]] } + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `given_ReadWriter_Bar`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + } + + test("testCaptureOpaqueTypeError") { + assert: + typeCheckErrorMessages: + """ + val opaqueInt = OpaqueInt(12) + Spore.auto { (x: Int) => x + OpaqueInt.unwrap(opaqueInt) } + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `opaqueInt`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + } + + test("testCapturedThisNestedClassError") { + assert: + typeCheckErrorMessages: + """ + class Outer { + class Inner { + val y = 12 + def foo = Spore.auto { (x: Int) => x + y } + } + } + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `Inner`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + + assert: + typeCheckErrorMessages: + """ + class Outer { + val y = 12 + class Inner { + def foo = Spore.auto { (x: Int) => x + y } + } + } + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `Outer`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + + assert: + typeCheckErrorMessages: + """ + class Outer { + val y = 12 + class Inner { + def foo = Spore.auto { (x: Int) => x + Outer.this.y } + } + } + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `Outer`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + } + + test("testCapturedIdentInClassError") { + assert: + typeCheckErrorMessages: + """ + val foo = Foo(12, 13) + Spore.auto { + class Bar { + def bar = foo.x + 12 + } + }.unwrap() + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `foo`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + } + + test("testCapturedNewClassError") { + assert: + typeCheckErrorMessages: + """ + class Bar(x: Int, y: Int) + Spore.auto { (x: Int) => + new Bar(12, 14) + } + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `Bar`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + + assert: + typeCheckErrorMessages: + """ + class Bar[T](x: T, y: T) + Spore.auto { (x: Int) => + new Bar[Int](12, 14) + } + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `Bar`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + } + + test("testCapturedClassExtendsError") { + assert: + typeCheckErrorMessages: + """ + class Bar0 + Spore.auto { + class FooBar extends Bar0 + }.unwrap() + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `Bar0`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + + assert: + typeCheckErrorMessages: + """ + class Bar1(x: Int, y: Int) + Spore.auto { + class FooBar extends Bar1(12, 13) + } + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `Bar1`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + + assert: + typeCheckErrorMessages: + """ + class Bar2[T](x: T, y: T) + Spore.auto { + class FooBar extends Bar2[Int](12, 13) + }.unwrap() + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `Bar2`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + + assert: + typeCheckErrorMessages: + """ + val x = 12 + trait Bar3 { def bar: Int = x } + Spore.auto { + class FooBar extends Foo(12, 13) with Bar3 + } + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `Bar3`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + + assert: + typeCheckErrorMessages: + """ + trait Bar4[T] { def bar: Int = x } + Spore.auto { + class FooBar extends Foo(12, 13) with Bar4[Int] + }.unwrap() + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `Bar4`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + } + + test("testCapturedTraitExtendsError") { + assert: + typeCheckErrorMessages: + """ + trait Bar + Spore.auto { + trait FooBar extends Bar + }.unwrap() + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `Bar`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + } + + test("testObjectExtendsCapturedError") { + assert: + typeCheckErrorMessages: + """ + trait Bar + Spore.auto { + object FooBar extends Bar + }.unwrap() + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `Bar`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + } + + test("testCapturedEnumError") { + assert: + typeCheckErrorMessages: + """ + enum Bar { case Baz } + Spore.auto { Bar.Baz } + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `Bar`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + } + + test("testCapturedUnapplyError") { + assert: + typeCheckErrorMessages: + """ + sealed trait Bar + case class Baz(x: Int, y: Int) extends Bar + Spore.auto { (x: Bar) => x match { + case Baz(a, b) => a + b + } + } + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `Baz`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + } + + test("testCapturedThisSuperError") { + assert: + typeCheckErrorMessages: + """ + class Bar extends Foo(12, 13) { + def bar = Spore.auto { (x: Int) => x.toString() + super.toString() } + } + """ + .exists: + _.matches: + raw""" + (?s)Missing implicit for captured variable `Bar`\.\R\Rno implicit values were found that match type spores.Spore\[\s*upickle.default.ReadWriter\[.*\]\]\s* + """.trim() + } + } +} diff --git a/spores3/test/src-jvm/spores/jvm/AutoCaptureTests.scala b/spores3/test/src-jvm/spores/jvm/AutoCaptureTests.scala new file mode 100644 index 0000000..0b7d157 --- /dev/null +++ b/spores3/test/src-jvm/spores/jvm/AutoCaptureTests.scala @@ -0,0 +1,251 @@ +package spores.jvm + +import utest._ + +import upickle.default.* + +import spores.default.* +import spores.default.given +import spores.TestUtils.* + + +object AutoCaptureTestsDefs { + + case class Foo(x: Int, y: Int) + + def writeReadUnwrap[T](s: Spore[T]): T = { + val w = write(s) + val r = read[Spore[T]](w) + r.unwrap() + } + + def readUnwrap[T](json: String): T = { + val r = read[Spore[T]](json) + r.unwrap() + } + + def countCapturedInSpore[T](s: Spore[T], captured: String): Int = { + // Note: the `captured` String should otherwise not occur in the JSON. + val length = captured.length + val json = write(s) + json.sliding(length).count(_.contains(captured)) + } + + object FunctionsToReadFromJSON { + def fun0() = Spore.auto { (x: String) => x } + def fun1(y1: String) = Spore.auto { (x: String) => x + y1 } + def fun2(y1: String, y2: String) = Spore.auto { (x: String) => x + y1 + y2 } + def fun3(y1: String, y2: String, y3: List[String]) = Spore.auto { (x: String) => x + y1 + y2 + y3.mkString(",") } + } + + sealed trait Tree[T] derives ReadWriter + case class Leaf[T](value: T) extends Tree[T] derives ReadWriter + case class Node[T](left: Tree[T], right: Tree[T]) extends Tree[T] derives ReadWriter + def reduce[T](tree: Tree[T], f: (T, T) => T): T = { + tree match { + case Leaf(value) => value + case Node(left, right) => f(reduce(left, f), reduce(right, f)) + } + } + object TreeRW extends SporeBuilder[ReadWriter[Tree[Int]]]({ macroRW }) + object LeafRW extends SporeBuilder[ReadWriter[Leaf[Int]]]({ macroRW }) + object NodeRW extends SporeBuilder[ReadWriter[Node[Int]]]({ macroRW }) + given treeRW: Spore[ReadWriter[Tree[Int]]] = TreeRW.build() + given leafRW: Spore[ReadWriter[Leaf[Int]]] = LeafRW.build() + given nodeRW: Spore[ReadWriter[Node[Int]]] = NodeRW.build() + + class TopLevel { + def x: Int = 4 + } + + def from[T](x: T): List[T] = List(x) +} + + +object AutoCaptureTests extends TestSuite { + import AutoCaptureTestsDefs.* + + val tests = Tests { + test("testCaptureNothing") { + val fun = Spore.auto {} + val unwrapped = writeReadUnwrap(fun) + assert(() == unwrapped) + } + + test("testCaptures01234") { + val a1 = "0123456789-1" + val a2 = "0123456789-2" + val a3 = "0123456789-3" + val a4 = "0123456789-4" + + val fun0 = Spore.auto { (x: String) => x } + val fun1 = Spore.auto { (x: String) => x + a1 } + val fun2 = Spore.auto { (x: String) => x + a1 + a2 } + val fun3 = Spore.auto { (x: String) => x + a1 + a2 + a3 } + val fun4 = Spore.auto { (x: String) => x + a1 + a2 + a3 + a4 } + + val unwrapped0 = writeReadUnwrap(fun0) + val unwrapped1 = writeReadUnwrap(fun1) + val unwrapped2 = writeReadUnwrap(fun2) + val unwrapped3 = writeReadUnwrap(fun3) + val unwrapped4 = writeReadUnwrap(fun4) + + assert("hello" == unwrapped0("hello")) + assert("hello" + a1 == unwrapped1("hello")) + assert("hello" + a1 + a2 == unwrapped2("hello")) + assert("hello" + a1 + a2 + a3 == unwrapped3("hello")) + assert("hello" + a1 + a2 + a3 + a4 == unwrapped4("hello")) + + assert(1 == countCapturedInSpore(fun1, a1)) + assert(1 == countCapturedInSpore(fun2, a1)) + assert(1 == countCapturedInSpore(fun2, a2)) + assert(1 == countCapturedInSpore(fun3, a1)) + assert(1 == countCapturedInSpore(fun3, a2)) + assert(1 == countCapturedInSpore(fun3, a3)) + assert(1 == countCapturedInSpore(fun4, a1)) + assert(1 == countCapturedInSpore(fun4, a2)) + assert(1 == countCapturedInSpore(fun4, a3)) + assert(1 == countCapturedInSpore(fun4, a4)) + } + + test("testCapturedIdentExactlyOnce") { + val c = "0123456789" + val fun = Spore.auto { (x: String) => + val y = { + val z = { + x + c + } + z + c + } + y + c + c + } + val unwrapped = writeReadUnwrap(fun) + assert("0123456789" + c + c + c + c == unwrapped("0123456789")) + assert(1 == countCapturedInSpore(fun, c)) + } + + 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 a1 = "0123456789-1" + val a2 = "0123456789-2" + val a3 = List("a", "b", "c") + + val u0 = readUnwrap[String => String](json0) + val u1 = readUnwrap[String => String](json1) + val u2 = readUnwrap[String => String](json2) + val u3 = readUnwrap[String => String](json3) + + assert("hello" == u0("hello")) + assert("hello" + a1 == u1("hello")) + assert("hello" + a1 + a2 == u2("hello")) + assert("hello" + a1 + a2 + a3.mkString(",") == u3("hello")) + } + + test("testCaptureTree") { + val tree = Node(Leaf(1), Node(Leaf(2), Leaf(3))) + val fun = Spore.auto { reduce(tree, (x, y) => x + y) } + val unwrapped = writeReadUnwrap(fun) + assert(6 == unwrapped) + } + + test("testValDefNoCapture") { + val fun = Spore.auto { (x: Int) => + val y = 12 + x + y + } + val unwrapped = writeReadUnwrap(fun) + assert(13 == unwrapped(1)) + } + + test("testTypeDefNoCapture") { + val fun = Spore.auto { (y: Int) => + type T = TopLevel + new T { override def x = y } + } + val unwrapped = writeReadUnwrap(fun) + assert(13 == unwrapped(13).x) + } + + test("testAsInstanceOfNoCapture") { + class Bar(val value: Int) + val fun = Spore.auto { (x: List[Any]) => + x.asInstanceOf[List[Bar]].tail.head.value + x.head.asInstanceOf[Bar].value + } + val unwrapped = writeReadUnwrap(fun) + assert(56 == unwrapped(List(Bar(42), Bar(14), Bar(73)))) + } + + test("testMethodTypeParamNoCapture") { + class Bar(val value: Int) + val fun = Spore.auto { (x: Any) => + from[Bar](x.asInstanceOf[Bar]).head.value + } + val unwrapped = writeReadUnwrap(fun) + assert(39 == unwrapped.apply(Bar(39))) + } + + test("testParameterTypeNoCapture") { + class Bar(val value: Int) + val fun = Spore.auto { + (l: List[Bar]) + => (x: Bar) => + val foo: Bar = null + l.head.value + x.value + } + val unwrapped = writeReadUnwrap(fun) + assert(76 == unwrapped(List(Bar(36), Bar(31)))(Bar(40))) + } + + test("testClassExtendsTopLevelNoCapture") { + val fun = Spore.auto { + class FooBar extends Foo(12, 13) { + def foo: Foo = this + } + val fooBar = new FooBar() + fooBar.foo.x + } + val unwrapped = writeReadUnwrap(fun) + assert(12 == unwrapped) + } + + test("testLargeProgram") { + val C00 = 0; val C01 = 1; val C02 = 2; val C03 = 3; val C04 = 4; val C05 = 5; val C06 = 6; val C07 = 7; val C08 = 8; val C09 = 9; + val C10 = 0; val C11 = 1; val C12 = 2; val C13 = 3; val C14 = 4; val C15 = 5; val C16 = 6; val C17 = 7; val C18 = 8; val C19 = 9; + val C20 = 0; val C21 = 1; val C22 = 2; val C23 = 3; val C24 = 4; val C25 = 5; val C26 = 6; val C27 = 7; val C28 = 8; val C29 = 9; + val C30 = 0; val C31 = 1; val C32 = 2; val C33 = 3; val C34 = 4; val C35 = 5; val C36 = 6; val C37 = 7; val C38 = 8; val C39 = 9; + val C40 = 0; val C41 = 1; val C42 = 2; val C43 = 3; val C44 = 4; val C45 = 5; val C46 = 6; val C47 = 7; val C48 = 8; val C49 = 9; + + val fun = Spore.auto { + (x00: Int) => (x01: Int) => (x02: Int) => (x03: Int) => (x04: Int) => (x05: Int) => (x06: Int) => (x07: Int) => (x08: Int) => (x09: Int) => + (x10: Int) => (x11: Int) => (x12: Int) => (x13: Int) => (x14: Int) => (x15: Int) => (x16: Int) => (x17: Int) => (x18: Int) => (x19: Int) => + (x20: Int) => (x21: Int) => (x22: Int) => (x23: Int) => (x24: Int) => (x25: Int) => (x26: Int) => (x27: Int) => (x28: Int) => (x29: Int) => + (x30: Int) => (x31: Int) => (x32: Int) => (x33: Int) => (x34: Int) => (x35: Int) => (x36: Int) => (x37: Int) => (x38: Int) => (x39: Int) => + (x40: Int) => (x41: Int) => (x42: Int) => (x43: Int) => (x44: Int) => (x45: Int) => (x46: Int) => (x47: Int) => (x48: Int) => (x49: Int) => + x00 + x01 + x02 + x03 + x04 + x05 + x06 + x07 + x08 + x09 + + x10 + x11 + x12 + x13 + x14 + x15 + x16 + x17 + x18 + x19 + + x20 + x21 + x22 + x23 + x24 + x25 + x26 + x27 + x28 + x29 + + x30 + x31 + x32 + x33 + x34 + x35 + x36 + x37 + x38 + x39 + + x40 + x41 + x42 + x43 + x44 + x45 + x46 + x47 + x48 + x49 + + C00 + C01 + C02 + C03 + C04 + C05 + C06 + C07 + C08 + C09 + + C10 + C11 + C12 + C13 + C14 + C15 + C16 + C17 + C18 + C19 + + C20 + C21 + C22 + C23 + C24 + C25 + C26 + C27 + C28 + C29 + + C30 + C31 + C32 + C33 + C34 + C35 + C36 + C37 + C38 + C39 + + C40 + C41 + C42 + C43 + C44 + C45 + C46 + C47 + C48 + C49 + } + + val unwrapped = writeReadUnwrap(fun) + val actual = unwrapped + .apply(C00).apply(C01).apply(C02).apply(C03).apply(C04).apply(C05).apply(C06).apply(C07).apply(C08).apply(C09) + .apply(C10).apply(C11).apply(C12).apply(C13).apply(C14).apply(C15).apply(C16).apply(C17).apply(C18).apply(C19) + .apply(C20).apply(C21).apply(C22).apply(C23).apply(C24).apply(C25).apply(C26).apply(C27).apply(C28).apply(C29) + .apply(C30).apply(C31).apply(C32).apply(C33).apply(C34).apply(C35).apply(C36).apply(C37).apply(C38).apply(C39) + .apply(C40).apply(C41).apply(C42).apply(C43).apply(C44).apply(C45).apply(C46).apply(C47).apply(C48).apply(C49) + val expected = 450 // (0 + 1 + ... + 9) * 5 * 2 + assert(expected == actual) + } + } +} diff --git a/spores3/test/src-jvm/spores/jvm/SporeLambdaErrorTests.scala b/spores3/test/src-jvm/spores/jvm/SporeLambdaErrorTests.scala new file mode 100644 index 0000000..523fd01 --- /dev/null +++ b/spores3/test/src-jvm/spores/jvm/SporeLambdaErrorTests.scala @@ -0,0 +1,92 @@ +package spores.jvm + +import utest._ + +import spores.default.given +import spores.default.* +import spores.TestUtils.* + +// // The following code should produce a compile error: +// // Invalid capture of variable `x`. Use the first parameter of a spore's body to refer to the spore's environment.bloop +// // ... but reproducing it with the typeCheckErrorMessages macro is not possible as the object needs to be non-nested top-level. +// object Issue001: +// def foo(x: Int): Spore[Int => Boolean] = Spore.apply[Int => Boolean] { y => y > x } + +object SporeLambdaErrorTests extends TestSuite { + + val tests = Tests { + test("testInvalidCaptureIdent") { + assert: + typeCheckErrorMessages: + """ + val y = 12 + Spore.apply[Int => Int] { x => x + y } + """ + .contains: + """ + Invalid capture of variable `y`. Use the first parameter of a spore's body to refer to the spore's environment. + """.trim() + + assert: + typeCheckErrorMessages: + """ + Spore.apply[Int => Int] { x => Spore.apply[Int => Int] { y => x + y }.unwrap().apply(x) } + """ + .contains: + """ + Invalid capture of variable `x`. Use the first parameter of a spore's body to refer to the spore's environment. + """.trim() + } + + test("testInvalidCaptureMethodParameter") { + assert: + typeCheckErrorMessages: + """ + def fun(x: Int): Spore[Int => Boolean] = Spore.apply[Int => Boolean] { y => y > x } + """ + .contains: + """ + Invalid capture of variable `x`. Use the first parameter of a spore's body to refer to the spore's environment. + """.trim() + + assert: + typeCheckErrorMessages: + """ + object ShouldFail: + def fun(x: Int): Spore[Int => Boolean] = Spore.apply[Int => Boolean] { y => y > x } + """ + .contains: + """ + Invalid capture of variable `x`. Use the first parameter of a spore's body to refer to the spore's environment. + """.trim() + } + + test("testInvalidCaptureThis") { + assert: + typeCheckErrorMessages: + """ + class TestClass { + Spore.apply { () => this.toString() }.unwrap() + } + (new TestClass()) + """ + .contains: + """ + Invalid capture of `this` from outer class. + """.trim() + + assert: + typeCheckErrorMessages: + """ + class Outer: + val x = 12 + Spore.apply { () => 42 * x }.unwrap() + (new Outer()) + """ + .contains: + """ + Invalid capture of `this` from class Outer. + """.trim() + } + } +} diff --git a/spores3/test/src-jvm/spores/jvm/SporeLambdaTests.scala b/spores3/test/src-jvm/spores/jvm/SporeLambdaTests.scala new file mode 100644 index 0000000..9a4777f --- /dev/null +++ b/spores3/test/src-jvm/spores/jvm/SporeLambdaTests.scala @@ -0,0 +1,187 @@ +package spores.jvm + +import utest._ + +import spores.default.given +import spores.default.* +import spores.TestUtils.* + +object SporeLambdaTestsDefs { + val lambda = Spore.apply[Int => Boolean] { x => x > 10 } + + val lambdaWithEnv = Spore.applyWithEnv(11) { x => x > 10 } + + object NestedLambda: + val lambda = Spore.apply[Int => Boolean] { x => x > 10 } + + def methodLambda(): Spore[Int => Boolean] = + Spore.apply[Int => Boolean] { x => x > 10 } + + def methodLambdaWithUnnusedArg(x: Int): Spore[Int => Boolean] = + Spore.apply[Int => Boolean] { y => y > 10 } + + inline def inlinedMethodLambda(): Spore[Int => Boolean] = + Spore.apply[Int => Boolean] { x => x > 10 } + + inline def inlinedMethodLambdaWithArg(x: Int): Spore[Int => Boolean] = + Spore.apply[Int => Boolean] { y => y > x } + + class ClassWithLambda(): + val lambda = Spore.apply[Int => Boolean] { x => x > 10 } + def methodLambda() = Spore.apply[Int => Boolean] { x => x > 10 } +} + +object SporeLambdaTests extends TestSuite { + import SporeLambdaTestsDefs.* + + val tests = Tests { + test("testLambda") { + val predicate = lambda + assert(predicate(11)) + assert(!predicate(9)) + assert(predicate.unwrap()(11)) + assert(!predicate.unwrap()(9)) + } + + test("testLambdaWithEnv") { + val predicate9 = Spore.applyWithEnv(9) { x => x > 10 } + val predicate11 = Spore.applyWithEnv(11) { x => x > 10 } + assert(!predicate9.unwrap()) + assert(predicate11.unwrap()) + } + + test("testLambdaWithCtx") { + val predicate9 = Spore.applyWithCtx(9) { summon[Int] > 10 } + val predicate11 = Spore.applyWithCtx(11) { summon[Int] > 10 } + assert(!predicate9.unwrap()) + assert(predicate11.unwrap()) + } + + test("testPackBuildHigherOrderLambda") { + val higherLevelFilter = Spore.apply[Spore[Int => Boolean] => Int => Option[Int]] { env => x => if env.unwrap().apply(x) then Some(x) else None } + val filter = higherLevelFilter.withEnv(lambda) + assert(Some(11) == filter(11)) + assert(None == filter(9)) + assert(Some(11) == filter.unwrap()(11)) + assert(None == filter.unwrap()(9)) + } + + test("testPackedLambdaReadWriter") { + val json = """{"$type":"spores.Packed.PackedLambda","className":"spores.jvm.SporeLambdaTestsDefs$Lambda$1"}""" + + val packed = upickle.default.write(lambda) + assert(json == packed) + + val loaded = upickle.default.read[Spore[Int => Boolean]](json) + assert(loaded(11)) + assert(!loaded(9)) + assert(loaded.unwrap()(11)) + assert(!loaded.unwrap()(9)) + } + + test("testNestedLambdaReadWriter") { + val json = """{"$type":"spores.Packed.PackedLambda","className":"spores.jvm.SporeLambdaTestsDefs$NestedLambda$Lambda$3"}""" + + val packed = upickle.default.write(NestedLambda.lambda) + assert(json == packed) + + val loaded = upickle.default.read[Spore[Int => Boolean]](json) + assert(loaded(11)) + assert(!loaded(9)) + assert(loaded.unwrap()(11)) + assert(!loaded.unwrap()(9)) + } + + test("testPackedLambdaWithEnvReadWriter") { + val json9 = """{"$type":"spores.Packed.PackedWithEnv","packed":{"$type":"spores.Packed.PackedLambda","className":"spores.jvm.SporeLambdaTestsDefs$Lambda$1"},"packedEnv":{"$type":"spores.Packed.PackedEnv","env":"9","rw":{"$type":"spores.Packed.PackedObject","className":"spores.ReadWriters$IntRW$"}}}""" + val json11 = """{"$type":"spores.Packed.PackedWithEnv","packed":{"$type":"spores.Packed.PackedLambda","className":"spores.jvm.SporeLambdaTestsDefs$Lambda$1"},"packedEnv":{"$type":"spores.Packed.PackedEnv","env":"11","rw":{"$type":"spores.Packed.PackedObject","className":"spores.ReadWriters$IntRW$"}}}""" + + val packed9 = upickle.default.write(lambda.withEnv(9)) + val packed11 = upickle.default.write(lambda.withEnv(11)) + assert(json9 == packed9) + assert(json11 == packed11) + + val loaded9 = upickle.default.read[Spore[Boolean]](json9).unwrap() + val loaded11 = upickle.default.read[Spore[Boolean]](json11).unwrap() + assert(!loaded9) + assert(loaded11) + } + + test("testLambdaWithEnvConstructorReadWriter") { + val json = """{"$type":"spores.Packed.PackedWithEnv","packed":{"$type":"spores.Packed.PackedLambda","className":"spores.jvm.SporeLambdaTestsDefs$Lambda$2"},"packedEnv":{"$type":"spores.Packed.PackedEnv","env":"11","rw":{"$type":"spores.Packed.PackedObject","className":"spores.ReadWriters$IntRW$"}}}""" + + val packed = upickle.default.write(lambdaWithEnv) + assert(json == packed) + + val loaded = upickle.default.read[Spore[Boolean]](json).unwrap() + assert(loaded) + } + + test("testLambdaWithOptionEnvironment") { + val packed = Spore.applyWithEnv(Some(11)) { x => x.getOrElse(0) } + val fun = packed.unwrap() + assert(11 == fun) + } + + test("testLambdaWithListEnvironment") { + val packed = Spore.applyWithEnv(List(1, 2, 3)) { x => x.sum } + val fun = packed.unwrap() + assert(6 == fun) + } + + test("testLambdaFromMethodCreator") { + val packed = methodLambda() + val fun = packed.unwrap() + assert(fun(11)) + assert(!fun(9)) + } + + test("testLambdaFromMethodCreatorWithUnnusedArg") { + val packed = methodLambdaWithUnnusedArg(11) + val fun = packed.unwrap() + assert(fun(11)) + assert(!fun(9)) + } + + test("testLambdaFromInlinedMethodCreator") { + val packed = inlinedMethodLambda() + val fun = packed.unwrap() + assert(fun(11)) + assert(!fun(9)) + } + + test("testLambdaFromInlinedMethodCreatorWithArg") { + val packed = inlinedMethodLambdaWithArg(10) + val fun = packed.unwrap() + assert(fun(11)) + assert(!fun(9)) + } + + test("testLambdaFromClassCreator") { + val packed = ClassWithLambda().lambda + val fun = packed.unwrap() + assert(fun(11)) + assert(!fun(9)) + } + + test("testLambdaFromClassMethodCreator") { + val packed = ClassWithLambda().methodLambda() + val fun = packed.unwrap() + assert(fun(11)) + assert(!fun(9)) + } + + test("testSporeApplyWithEnvAlias") { + val spore = Spore.apply[Int, Int => Boolean](12) { env => x => x > env } + val fun = spore.unwrap() + assert(fun(13)) + assert(!fun(11)) + } + + test("testSporeApplyWithEnvAlias2") { + val spore = Spore.apply("Hello") { (env: String) => (x: Int) => x.toString() + env } + val fun = spore.unwrap() + assert("12Hello" == fun(12)) + } + } +} diff --git a/spores3/test/src-jvm/spores/test/SporeTests.scala b/spores3/test/src-jvm/spores/test/SporeTests.scala new file mode 100644 index 0000000..b1d878c --- /dev/null +++ b/spores3/test/src-jvm/spores/test/SporeTests.scala @@ -0,0 +1,195 @@ +package spores.test + +import utest._ + +import spores.Spore +import spores.default.given + + +object SporeTests extends TestSuite { + + val tests = Tests { + + test("testWithoutEnv") { + val b = Spore { (x: Int) => x + 2 } + val res = b.unwrap()(3) + assert(res == 5) + } + + test("testWithoutEnv2") { + def fun(s: Spore[Int => Int]): Unit = {} + + val s = Spore((x: Int) => x + 2) + + fun(s) + + val res = s.unwrap()(3) + assert(res == 5) + } + + test("testWithoutEnvWithType") { + val s: Spore[Int => Int] = Spore { + (x: Int) => x + 2 + } + val res = s.unwrap()(3) + assert(res == 5) + } + + test("testWithoutEnvWithType1") { + val s: Spore[Int => Int] = Spore { + x => x + 2 + } + val res = s.unwrap()(3) + assert(res == 5) + } + + /* the following does not compile: + [error] -- [E007] Type Mismatch Error: [...]/BlockTests.scala:37:61 + [error] 37 | val s: Spore[Int, Int] { type Env = Nothing } = Spore(y) { + [error] | ^ + [error] | Found: com.phaller.blocks.Spore[Int, Int]{Env = Int} + [error] | Required: com.phaller.blocks.Spore[Int, Int]{Env = Nothing} + [error] 38 | (x: Int) => x + 2 + env + [error] 39 | } + */ + /*test("testWithoutEnvWithType1") { + val y = 5 + val s: Spore[Int, Int] { type Env = Nothing } = Spore(y) { + (x: Int) => x + 2 + env + } + val res = s(3) + assert(res == 5) + }*/ + + test("testWithEnv") { + val y = 5 + val s = Spore.applyWithEnv(y) { + env => (x: Int) => x + env + } + val res = s.unwrap()(10) + assert(res == 15) + } + + /* + [error] -- Error: [...]/BlockTests.scala:83:35 + [error] 83 | env => (x: Int) => x + env + z + [error] | ^ + [error] |Invalid capture of variable `z`. Use first parameter of spore's body to refer to the spore's environment. + */ + /*test("testWithEnvInvalidCapture") { + val y = 5 + val z = 6 + val s = Spore(y) { + env => (x: Int) => x + env + z + } + val res = s(10) + assert(res == 21) + }*/ + + test("testWithEnv2") { + val str = "anonymous function" + val s: Spore[Int => Int] = Spore.applyWithEnv(str) { + env => (x: Int) => x + env.length + } + val res = s.unwrap()(10) + assert(res == 28) + } + + test("testWithEnvTuple") { + val str = "anonymous function" + val i = 5 + + val s: Spore[Int => Int] = Spore.applyWithEnv((str, i)) { + case (l, r) => (x: Int) => x + l.length - r + } + + val res = s.unwrap()(10) + assert(res == 23) + } + + test("testWithEnvParamUntupling") { + val str = "anonymous function" + val i = 5 + + val s = Spore.applyWithEnv((str, i)) { + (l, r) => (x: Int) => x + l.length - r + } + + val res = s.unwrap()(10) + assert(res == 23) + } + + test("testWithEnvWithType") { + val y = 5 + val s: Spore[Int => Int] = Spore.applyWithEnv(y) { + env => (x: Int) => x + env + } + val res = s.unwrap()(11) + assert(res == 16) + } + + test("testThunk") { + val x = 5 + val t = Spore.applyWithEnv(x) { env => () => + env + 7 + } + val res = t.unwrap()() + assert(res == 12) + } + + test("testNestedWithoutEnv") { + val s = Spore { + (x: Int) => + val s2 = Spore { (y: Int) => y - 1 } + s2.unwrap()(x) + 2 + } + val res = s.unwrap()(3) + assert(res == 4) + } + + test("testNestedWithEnv1") { + val z = 5 + + val s = Spore.applyWithEnv(z) { + env => (x: Int) => + val s2 = Spore.applyWithEnv(env) { env => (y: Int) => env + y - 1 } + s2.unwrap()(x) + 2 + } + val res = s.unwrap()(3) + assert(res == 9) + } + + test("testNestedWithEnv2") { + val z = 5 + val w = 6 + + val s = Spore.applyWithEnv((w, z)) { + case (l, r) => (x: Int) => + val s2 = Spore.applyWithEnv(r) { env => (y: Int) => env + y - 1 } + s2.unwrap()(x) + 2 - l + } + + val res = s.unwrap()(3) + assert(res == 3) + } + + test("testLocalClasses") { + val x = 5 + + val s = Spore.applyWithEnv(x) { env => (y: Int) => + class Local2 { def m() = y } + class Local(p: Int)(using loc: Local2) { + val fld = env + p + } + + given l2: Local2 = new Local2 + val l = new Local(y + 1) + l.fld + } + + val res = s.unwrap()(3) + assert(res == 9) + } + } + +} diff --git a/spores3/test/src-jvm/spores/test/TrieMapTest.scala b/spores3/test/src-jvm/spores/test/TrieMapTest.scala new file mode 100644 index 0000000..8dbaab8 --- /dev/null +++ b/spores3/test/src-jvm/spores/test/TrieMapTest.scala @@ -0,0 +1,42 @@ +package spores.test + +import utest._ + +import scala.collection.concurrent.TrieMap + +import spores.default.* +import spores.default.given + + +case class Customer(name: String, customerNo: Int) +case class CustomerInfo(customerNo: Int, age: Int, since: Int) + +object TrieMapTest extends TestSuite { + + type CustomerMap = TrieMap[Int, CustomerInfo] + given Duplicable[CustomerMap] with + def duplicate(map: CustomerMap): CustomerMap = map.snapshot() + + val customerData = TrieMap.empty[Int, CustomerInfo] + + val tests = Tests { + + test("test") { + val s = Duplicate.applyWithEnv[CustomerMap, List[Customer] => Float](customerData) { data => cs => + val infos = cs.flatMap { c => + data.get(c.customerNo) match { + case Some(info) => List(info) + case None => List() + } + } + val sumAges = infos.foldLeft(0)(_ + _.age).toFloat + if (infos.size == 0) 0 + else sumAges / infos.size + } + + val res = s(List()) + assert(res == 0) + } + } + +} diff --git a/spores3/test/src/spores/SporeClassBuilderErrorTests.scala b/spores3/test/src/spores/SporeClassBuilderErrorTests.scala new file mode 100644 index 0000000..0770e0c --- /dev/null +++ b/spores3/test/src/spores/SporeClassBuilderErrorTests.scala @@ -0,0 +1,154 @@ +package spores + +import utest._ + +import spores.default.given +import spores.default.* +import spores.TestUtils.* + +object SporeClassBuilderErrorTestsDefs { + object NotClzClz extends SporeClassBuilder[Int => Int](x => x) + + def someMethod: SporeClassBuilder[Int => Int] = { + class Local extends SporeClassBuilder[Int => Int](x => x) + new Local() + } + + class NestedBuilderInClass: + class Inner extends SporeClassBuilder[Int](10) + + class ClassWithoutPublicConstructor private () extends SporeClassBuilder[Int => Int](x => x) + object ClassWithoutPublicConstructor: + def apply(): ClassWithoutPublicConstructor = new ClassWithoutPublicConstructor() + + class ClassWithParameters(i: Int) extends SporeClassBuilder[() => Int](() => i) + + class F[T] + class ClassWithContext1[T: F] extends SporeClassBuilder[F[T]](summon) + class ClassWithContext2[T](using F[T]) extends SporeClassBuilder[F[T]](summon) + class ClassWithContext3[T](implicit f: F[T]) extends SporeClassBuilder[F[T]](summon) +} + +object SporeClassBuilderErrorTests extends TestSuite { + import SporeClassBuilderErrorTestsDefs.* + + val tests = Tests { + test("testObjectSporeClassBuilderError") { + assert: + typeCheckErrorMessages: + """ + NotClzClz.build() + """ + .contains: + """ + The provided SporeClassBuilder `spores.SporeClassBuilderErrorTestsDefs$.NotClzClz$` is not a class. + """.trim() + + assert: + typeCheckErrorMessages: + """ + val notClzClz = NotClzClz + notClzClz.build() + """ + .contains: + """ + The provided SporeClassBuilder `spores.SporeClassBuilderErrorTestsDefs$.NotClzClz$` is not a class. + """.trim() + } + + test("testSporeClassBuilderNestedInClassError") { + assert: + typeCheckErrorMessages: + """ + val builder = new NestedBuilderInClass() + val pred = new builder.Inner() + pred.build() + """ + .contains: + """ + The provided SporeClassBuilder `spores.SporeClassBuilderErrorTestsDefs$.NestedBuilderInClass.Inner` is nested in a class. + """.trim() + } + + test("testSporeClassBuilderNestedInMethodError") { + assert: + typeCheckErrorMessages: + """ + someMethod.build() + """ + .contains: + """ + The provided SporeClassBuilder `spores.SporeClassBuilder` is not a concrete class. + """.trim() + } + + test("testSporeClassBuilderWithPrivateConstructorError") { + assert: + typeCheckErrorMessages: + """ + ClassWithoutPublicConstructor().build() + """ + .contains: + """ + The provided SporeClassBuilder `spores.SporeClassBuilderErrorTestsDefs$.ClassWithoutPublicConstructor` `` does not have a public constructor. + """.trim() + } + + test("testSporeClassBuilderWithParameterError") { + assert: + typeCheckErrorMessages: + """ + ClassWithParameters(10).build() + """ + .contains: + """ + The constructor of the provided SporeClassBuilder `spores.SporeClassBuilderErrorTestsDefs$.ClassWithParameters` `` does not have an empty parameter list. + """.trim() + } + + test("testSporeClassBuilderWithContextParameterError") { + // Catches a common mistake in which implicit parameters are used in the + // constructor. For example, this would seem like a reasonable thing to do, + // but will not work: + // + // class PackedRW[T: ReadWriter] extends SporeClassBuilder[ReadWriter[T]](summon[ReadWriter[T]]) + // given Spore[ReadWriter[T]] = PackedRW[T].build() + // + // // This will crash at runtime, as the init method is assumed to not have any params. + // summon[Spore[ReadWriter[Int]]].unwrap() + + assert: + typeCheckErrorMessages: + """ + given F[Int] = new F[Int]() + ClassWithContext1[Int].build() + """ + .contains: + """ + The constructor of the provided SporeClassBuilder `spores.SporeClassBuilderErrorTestsDefs$.ClassWithContext1` `` contains a context parameter list. + """.trim() + + assert: + typeCheckErrorMessages: + """ + given F[Int] = new F[Int]() + ClassWithContext2[Int].build() + """ + .contains: + """ + The constructor of the provided SporeClassBuilder `spores.SporeClassBuilderErrorTestsDefs$.ClassWithContext2` `` contains a context parameter list. + """.trim() + + assert: + typeCheckErrorMessages: + """ + given F[Int] = new F[Int]() + ClassWithContext3[Int].build() + """ + .contains: + """ + The constructor of the provided SporeClassBuilder `spores.SporeClassBuilderErrorTestsDefs$.ClassWithContext3` `` contains a context parameter list. + """.trim() + } + } +} diff --git a/spores3/test/src/spores/SporeClassBuilderTests.scala b/spores3/test/src/spores/SporeClassBuilderTests.scala new file mode 100644 index 0000000..f92e3f5 --- /dev/null +++ b/spores3/test/src/spores/SporeClassBuilderTests.scala @@ -0,0 +1,96 @@ +package spores + +import utest._ + +import spores.default.given +import spores.default.* +import spores.TestUtils.* + +object SporeClassBuilderTestsDefs { + class Thunk[T] extends SporeClassBuilder[T => () => T](t => () => t) + + class Predicate extends SporeClassBuilder[Int => Boolean](x => x > 10) + + class FilterWithTypeParam[T] extends SporeClassBuilder[Spore[T => Boolean] => T => Option[T]]({ env => x => if env.unwrap().apply(x) then Some(x) else None }) + + class Flatten[T] extends SporeClassBuilder[List[List[T]] => List[T]](x => x.flatten) + + object NestedBuilder: + class Predicate extends SporeClassBuilder[Int => Boolean](x => x > 10) +} + +object SporeClassBuilderTests extends TestSuite { + import SporeClassBuilderTestsDefs.* + + val tests = Tests { + test("testSporeClassBuilderPack") { + val predicate = new Predicate().build() + assert(predicate(11)) + assert(!predicate(9)) + assert(predicate.unwrap()(11)) + assert(!predicate.unwrap()(9)) + } + + test("testSporeClassBuilderWithEnv") { + val thunk = new Thunk[Int].build().withEnv(10) + assert(10 == thunk()) + assert(10 == thunk.unwrap()()) + } + + test("testSporeClassBuilderWithTypeParam") { + val flatten = new Flatten[Int].build() + val nestedList = List(List(1), List(2), List(3)) + assert(nestedList.flatten == flatten(nestedList)) + assert(nestedList.flatten == flatten.unwrap()(nestedList)) + } + + test("testHigherLevelSporeClassBuilder") { + val filter = new FilterWithTypeParam[Int].build() + val predicate = new Predicate().build() + assert(Some(11) == filter(predicate)(11)) + assert(None == filter(predicate)(9)) + assert(Some(11) == filter.unwrap()(predicate)(11)) + assert(None == filter.unwrap()(predicate)(9)) + } + + test("testSporeClassBuilderReadWriter") { + val json = """{"$type":"spores.Packed.PackedClass","className":"spores.SporeClassBuilderTestsDefs$Predicate"}""" + + val packed = upickle.default.write(new Predicate().build()) + assert(json == packed) + + val loaded = upickle.default.read[Spore[Int => Boolean]](json) + assert(loaded(11)) + assert(!loaded(9)) + assert(loaded.unwrap()(11)) + assert(!loaded.unwrap()(9)) + } + + test("testSporeClassBuilderWithTypeParamReadWriter") { + val json = """{"$type":"spores.Packed.PackedClass","className":"spores.SporeClassBuilderTestsDefs$Flatten"}""" + + val packed = upickle.default.write(new Flatten[Int].build()) + assert(json == packed) + + val loaded = upickle.default.read[Spore[List[List[Int]] => List[Int]]](json) + val nestedList = List(List(1), List(2), List(3)) + assert(loaded(nestedList) == nestedList.flatten) + assert(loaded.unwrap()(nestedList) == nestedList.flatten) + } + + test("testSporeClassBuilderWithEnvReadWriter") { + val json = """{"$type":"spores.Packed.PackedWithEnv","packed":{"$type":"spores.Packed.PackedClass","className":"spores.SporeClassBuilderTestsDefs$FilterWithTypeParam"},"packedEnv":{"$type":"spores.Packed.PackedEnv","env":"{\"$type\":\"spores.Packed.PackedClass\",\"className\":\"spores.SporeClassBuilderTestsDefs$Predicate\"}","rw":{"$type":"spores.Packed.PackedObject","className":"spores.ReadWriters$SporeRW$"}}}""" + + val predicate = new Predicate().build() + val filter = new FilterWithTypeParam[Int].build().withEnv(predicate) + val packed = upickle.default.write(filter) + assert(json == packed) + + val loaded = upickle.default.read[Spore[Int => Option[Int]]](json) + assert(Some(11) == loaded(11)) + assert(None == loaded(9)) + assert(Some(11) == loaded.unwrap()(11)) + assert(None == loaded.unwrap()(9)) + } + } +} diff --git a/spores3/test/src/spores/SporeObjectBuilderErrorTests.scala b/spores3/test/src/spores/SporeObjectBuilderErrorTests.scala new file mode 100644 index 0000000..7ebc6d1 --- /dev/null +++ b/spores3/test/src/spores/SporeObjectBuilderErrorTests.scala @@ -0,0 +1,84 @@ +package spores + +import utest._ + +import spores.default.given +import spores.default.* +import spores.TestUtils.* + +object SporeBuilderErrorTestsDefs { + class NotObjObj extends SporeBuilder[Int => Int](x => x) + + class SomeClass: + object NotTopLevel extends SporeBuilder[Int => Int](x => x) + + def someMethod: SporeBuilder[Int => Int] = { + object NotTopLevel extends SporeBuilder[Int => Int](x => x) + NotTopLevel + } +} + +object SporeBuilderErrorTests extends TestSuite { + import SporeBuilderErrorTestsDefs.* + + val tests = Tests { + test("testClassSporeBuilderError") { + assert: + typeCheckErrorMessages: + """ + new NotObjObj().build() + """ + .contains: + """ + The provided SporeBuilder `spores.SporeBuilderErrorTestsDefs$.NotObjObj` is not an object. + """.trim() + + assert: + typeCheckErrorMessages: + """ + val notObjObj = new NotObjObj() + notObjObj.build() + """ + .contains: + """ + The provided SporeBuilder `spores.SporeBuilderErrorTestsDefs$.NotObjObj` is not an object. + """.trim() + } + + test("testNotTopLevelError") { + assert: + typeCheckErrorMessages: + """ + val notTopLevel = new SomeClass().NotTopLevel + notTopLevel.build() + """ + .contains: + """ + The provided SporeBuilder `spores.SporeBuilderErrorTestsDefs$.SomeClass.NotTopLevel$` is not a top-level object; its owner `SomeClass` is not a top-level object nor a package. + """.trim() + + assert: + typeCheckErrorMessages: + """ + val notObject = someMethod + notObject.build() + """ + .contains: + """ + The provided SporeBuilder `spores.SporeBuilder` is not an object. + """.trim() + + assert: + typeCheckErrorMessages: + """ + object Builder extends SporeBuilder[Int => String](x => x.toString.reverse) + Builder.build() + """ + .exists: + _.matches: + raw""" + The provided SporeBuilder `.*Builder\$$` is not a top-level object; its owner `.*` is not a top-level object nor a package. + """.trim() + } + } +} diff --git a/spores3/test/src/spores/SporeObjectBuilderTests.scala b/spores3/test/src/spores/SporeObjectBuilderTests.scala new file mode 100644 index 0000000..cd9757e --- /dev/null +++ b/spores3/test/src/spores/SporeObjectBuilderTests.scala @@ -0,0 +1,194 @@ +package spores + +import utest._ + +import spores.default.given +import spores.default.* +import spores.TestUtils.* + +object SporeBuilderTestsDefs { + object Thunk extends SporeBuilder[() => Int](() => 10) + + object Predicate extends SporeBuilder[Int => Boolean](x => x > 10) + + object HigherLevelFilter extends SporeBuilder[Spore[Int => Boolean] => Int => Option[Int]]({ env => x => if env.unwrap().apply(x) then Some(x) else None }) + + object PredicateCtx extends SporeBuilder[Int ?=> Boolean](summon[Int] > 10) + + object OptionMapper extends SporeBuilder[Option[Int] => Int](x => x.getOrElse(0)) + + object ListReducer extends SporeBuilder[List[Int] => Int](x => x.sum) + + object NestedBuilder: + object Predicate extends SporeBuilder[Int => Boolean](x => x > 10) + + object Funct0 extends SporeBuilder[() => Int](() => 1) + + object Funct1 extends SporeBuilder[(Int) => Int](x1 => x1 + 1) + + object Funct2 extends SporeBuilder[(Int, Int) => Int]((x1, x2) => x1 + x2 + 1) + + object Funct3 extends SporeBuilder[(Int, Int, Int) => Int]((x1, x2, x3) => x1 + x2 + x3 + 1) + + object Funct4 extends SporeBuilder[(Int, Int, Int, Int) => Int]((x1, x2, x3, x4) => x1 + x2 + x3 + x4 + 1) + + object Funct5 extends SporeBuilder[(Int, Int, Int, Int, Int) => Int]((x1, x2, x3, x4, x5) => x1 + x2 + x3 + x4 + x5 + 1) + + object Funct6 extends SporeBuilder[(Int, Int, Int, Int, Int, Int) => Int]((x1, x2, x3, x4, x5, x6) => x1 + x2 + x3 + x4 + x5 + x6 + 1) + + object Funct7 extends SporeBuilder[(Int, Int, Int, Int, Int, Int, Int) => Int]((x1, x2, x3, x4, x5, x6, x7) => x1 + x2 + x3 + x4 + x5 + x6 + x7 + 1) +} + +object SporeBuilderTests extends TestSuite { + import SporeBuilderTestsDefs.* + + val tests = Tests { + test("testSporeBuilderPack") { + val predicate = Predicate.build() + assert(predicate(11)) + assert(!predicate(9)) + val unwrapped = predicate.unwrap() + assert(unwrapped(11)) + assert(!unwrapped(9)) + } + + test("testNestedSporeBuilderPack") { + val predicate = NestedBuilder.Predicate.build() + assert(predicate(11)) + assert(!predicate(9)) + val unwrapped = predicate.unwrap() + assert(unwrapped(11)) + assert(!unwrapped(9)) + } + + test("testSporeBuilderThunk") { + val thunk = Thunk.build() + assert(10 == thunk.apply()) + val unwrapped = thunk.unwrap() + assert(10 == unwrapped.apply()) + } + + test("testWithEnv") { + val predicate9 = Predicate.build().withEnv(9) + val predicate11 = Predicate.build().withEnv(11) + assert(!predicate9.unwrap()) + assert(predicate11.unwrap()) + } + + test("testWithEnv2") { + val env9 = Env.apply(9) + val predicate9 = Predicate.build().withEnv2(env9) + val env11 = Env.apply(11) + val predicate11 = Predicate.build().withEnv2(env11) + assert(!predicate9.unwrap()) + assert(predicate11.unwrap()) + } + + test("testWithCtx") { + val predicate9 = PredicateCtx.build().withCtx(9) + val packed11 = PredicateCtx.build().withCtx(11) + assert(!predicate9.unwrap()) + assert(packed11.unwrap()) + } + + test("testWithCtx2") { + val env9 = Env.apply(9) + val predicate9 = PredicateCtx.build().withCtx2(env9) + val env11 = Env.apply(11) + val packed11 = PredicateCtx.build().withCtx2(env11) + assert(!predicate9.unwrap()) + assert(packed11.unwrap()) + } + + test("testPackBuildHigherOrderSporeBuilder") { + val predicate = Predicate.build() + val filter = HigherLevelFilter.build().withEnv(predicate) + assert(Some(11) == filter(11)) + assert(None == filter(9)) + val unwrapped = filter.unwrap() + assert(Some(11) == unwrapped(11)) + assert(None == unwrapped(9)) + } + + test("testSporeReadWriter") { + val json = """{"$type":"spores.Packed.PackedObject","className":"spores.SporeBuilderTestsDefs$Predicate$"}""" + + val packed = upickle.default.write(Predicate.build()) + assert(packed == json) + + val loaded = upickle.default.read[Spore[Int => Boolean]](json) + assert(loaded(11)) + assert(!loaded(9)) + assert(loaded.unwrap()(11)) + assert(!loaded.unwrap()(9)) + } + + test("testNestedSporeReadWriter") { + val json = """{"$type":"spores.Packed.PackedObject","className":"spores.SporeBuilderTestsDefs$NestedBuilder$Predicate$"}""" + + val packed = upickle.default.write(NestedBuilder.Predicate.build()) + assert(packed == json) + + val loaded = upickle.default.read[Spore[Int => Boolean]](json) + assert(loaded(11)) + assert(!loaded(9)) + assert(loaded.unwrap()(11)) + assert(!loaded.unwrap()(9)) + } + + test("testSporeReadWriterWithEnv") { + val json = """{"$type":"spores.Packed.PackedWithEnv","packed":{"$type":"spores.Packed.PackedObject","className":"spores.SporeBuilderTestsDefs$HigherLevelFilter$"},"packedEnv":{"$type":"spores.Packed.PackedEnv","env":"{\"$type\":\"spores.Packed.PackedObject\",\"className\":\"spores.SporeBuilderTestsDefs$Predicate$\"}","rw":{"$type":"spores.Packed.PackedObject","className":"spores.ReadWriters$SporeRW$"}}}""" + + val predicate = Predicate.build() + val filter = HigherLevelFilter.build().withEnv(predicate) + val packed = upickle.default.write(filter) + assert(packed == json) + + val loaded = upickle.default.read[Spore[Int => Option[Int]]](json) + assert(Some(11) == loaded(11)) + assert(None == loaded(9)) + assert(Some(11) == loaded.unwrap()(11)) + assert(None == loaded.unwrap()(9)) + } + + test("testOptionEnvironment") { + val packed = OptionMapper.build().withEnv(Some(11)) + val fun = packed.unwrap() + assert(11 == fun) + + val packed2 = OptionMapper.build().withEnv(Some(11)) + val fun2 = packed2.unwrap() + assert(11 == fun2) + } + + test("testListEnvironment") { + val packed = ListReducer.build().withEnv(List(1, 2, 3)) + val fun = packed.unwrap() + assert(6 == fun) + + val packed2 = ListReducer.build().withEnv(List(1, 2, 3)) + val fun2 = packed2.unwrap() + assert(6 == fun2) + } + + test("testSporeApplyMethods") { + val funct0 = Funct0.build() + val funct1 = Funct1.build() + val funct2 = Funct2.build() + val funct3 = Funct3.build() + val funct4 = Funct4.build() + val funct5 = Funct5.build() + val funct6 = Funct6.build() + val funct7 = Funct7.build() + + assert(1 == funct0.apply()) + assert(2 == funct1.apply(1)) + assert(4 == funct2.apply(1, 2)) + assert(7 == funct3.apply(1, 2, 3)) + assert(11 == funct4.apply(1, 2, 3, 4)) + assert(16 == funct5.apply(1, 2, 3, 4, 5)) + assert(22 == funct6.apply(1, 2, 3, 4, 5, 6)) + assert(29 == funct7.apply(1, 2, 3, 4, 5, 6, 7)) + } + } +} diff --git a/core/shared/src/test/scala/spores/TestUtils.scala b/spores3/test/src/spores/TestUtils.scala similarity index 100% rename from core/shared/src/test/scala/spores/TestUtils.scala rename to spores3/test/src/spores/TestUtils.scala diff --git a/spores3/test/src/spores/test/DuplicableTests.scala b/spores3/test/src/spores/test/DuplicableTests.scala new file mode 100644 index 0000000..2b9ac6c --- /dev/null +++ b/spores3/test/src/spores/test/DuplicableTests.scala @@ -0,0 +1,216 @@ +package spores +package test + +import utest._ + +import spores.default.* +import spores.default.given + + +class C { + var f: Int = 0 +} + +object C { + given Duplicable[C] with { + def duplicate(x: C): C = { + val y = new C + y.f = x.f + y + } + } +} + +object DuplicableTests extends TestSuite { + + val tests = Tests { + test("testDuplicateInt") { + val x = 5 + val dup = summon[Duplicable[Int]] + assert(5 == dup.duplicate(x)) + } + + test("testDuplicateThunk") { + val x = 5 + val b = Duplicate.applyWithEnv(x) { env => () => + env + 1 + } + + val b2 = duplicate(b) + + val res = b2() + assert(6 == res) + } + + test("testDuplicateThunkWithMutableClass") { + val x = new C + x.f = 4 + + val b = Duplicate.applyWithEnv(x) { env => () => + env.f + 1 + } + + val b2 = duplicate(b) + + val res = b2() + assert(5 == res) + + val dup = b.asInstanceOf[DuplicateWithEnv[C, Int]] + val dup2 = b2.asInstanceOf[DuplicateWithEnv[C, Int]] + assert(dup.env.unwrap() ne dup2.env.unwrap()) + assert(dup.env.unwrap().f == dup2.env.unwrap().f) + } + + test("testDuplicatedThunkAccessesNewEnv") { + val x = new C + + val b = Duplicate.applyWithEnv(x) { env => () => + env + } + + val b2 = duplicate(b) + + val envVal = b2() + + assert(envVal != x) + } + + test("testDuplicatedThunk1") { + val x = new C + x.f = 7 + + val b = Duplicate.applyWithEnv(x) { env => () => + env + } + + val b2 = duplicate(b) + + val envVal = b2() + + assert(7 == envVal.f) + assert(envVal ne x) + } + + test("testDuplicatedThunk2") { + val x = new C + x.f = 7 + + val b: Duplicate[() => C] = Duplicate.applyWithEnv(x) { env => () => + env + } + + val b2 = duplicate(b) + + val envVal = b2() + + assert(7 == envVal.f) + assert(envVal ne x) + } + + test("testDuplicatedSporeNoCapture") { + // spore does not capture anything + val s = Duplicate { + (x: Int) => x + 2 + } + val s2 = duplicate(s) + val res = s2(3) + assert(5 == res) + } + + test("testDuplicateSporeWithEnv") { + val x = new C + x.f = 4 + + val b = Duplicate.applyWithEnv(x) { + env => (y: Int) => env.f + y + } + + val b2 = duplicate(b) + val res = b2(3) + assert(7 == res) + } + + test("testDuplicateSporeWithEnvGeneric") { + def duplicateThenApply[T, R, B <: Duplicate[T => R] : Duplicable](spore: B, arg: T): R = { + val dup = summon[Duplicable[B]] + val duplicated = dup.duplicate(spore) + duplicated(arg) + } + + val x = new C + x.f = 4 + + val b = Duplicate.applyWithEnv(x) { + env => (y: Int) => env.f + y + } + + val res = duplicateThenApply(b, 3) + assert(7 == res) + } + + test("testPassingSpore") { + def m2(s: Duplicate[Int => Int], arg: Int): Int = { + s(arg) + } + + def m1(s: Duplicate[Int => Int]): Int = { + m2(s, 10) + 20 + } + + val x = new C + x.f = 4 + + val s = Duplicate.applyWithEnv(x) { + env => (y: Int) => env.f + y + } + + val res = m1(s) + assert(34 == res) + } + + test("testPassingSporeAndDuplicate") { + def m2[B <: Duplicate[Int => Int] : Duplicable](spore: B, arg: Int): Int = { + val dup = summon[Duplicable[B]] + val duplicated = dup.duplicate(spore) + duplicated(arg) + } + + def m1[B <: Duplicate[Int => Int] : Duplicable](spore: B): Int = { + m2(spore, 10) + 20 + } + + val x = new C + x.f = 4 + + val b = Duplicate.applyWithEnv(x) { + env => (y: Int) => env.f + y + } + + val res = m1(b) + assert(34 == res) + } + + test("testDuplicateThenApply") { + def duplicateThenApply[S <: Duplicate[Unit => C] : Duplicable](s: S): C = { + val dup = summon[Duplicable[S]] + val duplicated = dup.duplicate(s) + duplicated(()) + } + + val x = new C + // x is a mutable instance: + x.f = 7 + + // create thunk: + val b = Duplicate.applyWithEnv(x) { env => (_: Unit) => + env + } + + val y = duplicateThenApply(b) + assert(7 == y.f) + // references are not equal: + assert(y ne x) + } + } + +} diff --git a/core/shared/src/test/scala/spores/upickle/test/AppendString.scala b/spores3/test/src/spores/upickle/test/AppendString.scala similarity index 100% rename from core/shared/src/test/scala/spores/upickle/test/AppendString.scala rename to spores3/test/src/spores/upickle/test/AppendString.scala diff --git a/core/shared/src/test/scala/spores/upickle/test/MySpore.scala b/spores3/test/src/spores/upickle/test/MySpore.scala similarity index 100% rename from core/shared/src/test/scala/spores/upickle/test/MySpore.scala rename to spores3/test/src/spores/upickle/test/MySpore.scala diff --git a/spores3/test/src/spores/upickle/test/PickleTests.scala b/spores3/test/src/spores/upickle/test/PickleTests.scala new file mode 100644 index 0000000..aea2af7 --- /dev/null +++ b/spores3/test/src/spores/upickle/test/PickleTests.scala @@ -0,0 +1,59 @@ +package spores.pickle.test + +import utest._ + +import upickle.default.* + +import spores.{Spore, Reflection, SporeBuilder} +import spores.default.given + + +object PickleTests extends TestSuite { + + val tests = Tests { + test("testReflection") { + val b = Reflection.loadModuleFieldValue[SporeBuilder[Int => Int => Int]]("spores.pickle.test.MySpore$") + val fun = b.fun + val res = fun(12)(3) + assert(16 == res) + } + + test("testSporeReadWriter") { + // create a spore + val spore: Spore[Int => Int] = MySpore.build().withEnv(12) + + // pickle spore + val pickled = write(spore) + assert(pickled == """{"$type":"spores.Packed.PackedWithEnv","packed":{"$type":"spores.Packed.PackedObject","className":"spores.pickle.test.MySpore$"},"packedEnv":{"$type":"spores.Packed.PackedEnv","env":"12","rw":{"$type":"spores.Packed.PackedObject","className":"spores.ReadWriters$IntRW$"}}}""") + + // unpickle spore + val unpickled = read[Spore[Int => Int]](pickled) + assert(16 == unpickled.unwrap()(3)) + assert(unpickled == spore) + } + + test("testSporeWithoutEnvReadWriter") { + val spore: Spore[Int => Int] = SporeWithoutEnv.build() + + val pickled = write(spore) + assert(pickled == """{"$type":"spores.Packed.PackedObject","className":"spores.pickle.test.SporeWithoutEnv$"}""") + + val unpickled = read[Spore[Int => Int]](pickled) + assert(4 == unpickled.unwrap()(3)) + assert(unpickled == spore) + } + + test("testSporeAppendStringReadWriter") { + val spore: Spore[List[String] => List[String]] = AppendString.build().withEnv("three") + + val pickled = write(spore) + assert(pickled == """{"$type":"spores.Packed.PackedWithEnv","packed":{"$type":"spores.Packed.PackedObject","className":"spores.pickle.test.AppendString$"},"packedEnv":{"$type":"spores.Packed.PackedEnv","env":"\"three\"","rw":{"$type":"spores.Packed.PackedObject","className":"spores.ReadWriters$StringRW$"}}}""") + + val unpickled = read[Spore[List[String] => List[String]]](pickled) + val l3 = List("four") + assert(unpickled.unwrap()(l3) == List("four", "three")) + assert(unpickled == spore) + } + } + +} diff --git a/core/shared/src/test/scala/spores/upickle/test/SporeWithoutEnv.scala b/spores3/test/src/spores/upickle/test/SporeWithoutEnv.scala similarity index 100% rename from core/shared/src/test/scala/spores/upickle/test/SporeWithoutEnv.scala rename to spores3/test/src/spores/upickle/test/SporeWithoutEnv.scala