Skip to content

Add support for Scala.js #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ jobs:
build:
name: Build and Test
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
scala: [3.0.0]
java: [[email protected]]
platform: [jvm, js]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout current branch (full)
Expand Down Expand Up @@ -53,8 +55,13 @@ jobs:
run: sbt ++${{ matrix.scala }} githubWorkflowCheck

- name: Validate JVM
if: matrix.platform == 'jvm'
run: sbt ++${{ matrix.scala }} validateJVM

- name: Validate JS
if: matrix.platform == 'js'
run: sbt ++${{ matrix.scala }} validateJS

publish:
name: Publish Artifacts
needs: [build]
Expand Down
80 changes: 59 additions & 21 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,23 @@ ThisBuild / githubWorkflowJavaVersions := Seq("[email protected]")

ThisBuild / githubWorkflowArtifactUpload := false

ThisBuild / githubWorkflowBuildMatrixFailFast := Some(false)

val JvmCond = s"matrix.platform == 'jvm'"
val JsCond = s"matrix.platform == 'js'"

ThisBuild / githubWorkflowBuild := Seq(
WorkflowStep.Sbt(List("validateJVM"), name = Some("Validate JVM"))
WorkflowStep.Sbt(List("validateJVM"), name = Some("Validate JVM"), cond = Some(JvmCond)),
WorkflowStep.Sbt(List("validateJS"), name = Some("Validate JS"), cond = Some(JsCond))
)

ThisBuild / githubWorkflowTargetTags ++= Seq("v*")
ThisBuild / githubWorkflowPublishTargetBranches :=
Seq(RefPredicate.Equals(Ref.Branch("main")), RefPredicate.StartsWith(Ref.Tag("v")))

ThisBuild / githubWorkflowBuildMatrixAdditions +=
"platform" -> List("jvm", "js")

ThisBuild / githubWorkflowPublishPreamble +=
WorkflowStep.Use(UseRef.Public("olafurpg", "setup-gpg", "v3"))

Expand All @@ -34,15 +43,17 @@ ThisBuild / githubWorkflowPublish := Seq(
)
)

addCommandAlias("validateJVM", ";clean;compile;test")

lazy val modules: List[ProjectReference] = List(
deriving,
test,
typeable
)
addCommandAlias("validate", ";clean;validateJVM;validateJS")
addCommandAlias("validateJVM", ";buildJVM;testJVM")
addCommandAlias("validateJS", ";buildJS;testJS")
addCommandAlias("buildJVM", ";derivingJVM/compile;testJVM/compile;typeableJVM/compile")
addCommandAlias("buildJS", ";derivingJS/compile;testJS/compile;typeableJS/compile")
addCommandAlias("testJVM", ";derivingJVM/test;testJVM/test;typeableJVM/test")
addCommandAlias("testJS", ";derivingJS/test;testJS/test;typeableJS/test")

lazy val commonSettings = Seq(
crossScalaVersions := (ThisBuild / crossScalaVersions).value,

scalacOptions ++= Seq(
"-Xfatal-warnings",
"-Yexplicit-nulls"
Expand All @@ -52,38 +63,64 @@ lazy val commonSettings = Seq(
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test",
)

lazy val root = project.in(file("."))
.aggregate(modules:_*)
.settings(commonSettings:_*)
lazy val root = project
.in(file("."))
.settings(commonSettings)
.settings(crossScalaVersions := Seq())
.settings(noPublishSettings)
.aggregate(
derivingJVM,
derivingJS,
testJVM,
testJS,
typeableJVM,
typeableJS
)

lazy val deriving = project
lazy val deriving = crossProject(JSPlatform, JVMPlatform)
.crossType(CrossType.Pure)
.in(file("modules/deriving"))
.dependsOn(test % "test")
.settings(
moduleName := "shapeless3-deriving"
moduleName := "shapeless3-deriving",
libraryDependencies += "org.typelevel" %%% "cats-core" % "2.6.1" % "test"
)
.settings(commonSettings: _*)
.settings(commonSettings)
.settings(publishSettings)
.jsConfigure(_.enablePlugins(ScalaJSJUnitPlugin))

lazy val derivingJVM = deriving.jvm
lazy val derivingJS = deriving.js

lazy val test = project
lazy val test = crossProject(JSPlatform, JVMPlatform)
.crossType(CrossType.Pure)
.in(file("modules/test"))
.settings(
moduleName := "shapeless3-test"
)
.settings(commonSettings: _*)
.settings(commonSettings)
.settings(publishSettings)
.jsConfigure(_.enablePlugins(ScalaJSJUnitPlugin))

lazy val typeable = project
lazy val testJVM = test.jvm
lazy val testJS = test.js

lazy val typeable = crossProject(JSPlatform, JVMPlatform)
.crossType(CrossType.Pure)
.in(file("modules/typeable"))
.dependsOn(test % "test")
.settings(
moduleName := "shapeless3-typeable"
)
.settings(commonSettings: _*)
.settings(commonSettings)
.settings(publishSettings)
.jsConfigure(_.enablePlugins(ScalaJSJUnitPlugin))

lazy val typeableJVM = typeable.jvm
lazy val typeableJS = typeable.js

lazy val local = project
lazy val local = crossProject(JSPlatform, JVMPlatform)
.crossType(CrossType.Pure)
.in(file("local"))
.dependsOn(deriving, test, typeable)
.settings(
Expand All @@ -93,10 +130,11 @@ lazy val local = project
Compile / console / scalacOptions -= "-Xprint:postInlining",
console / initialCommands := """import shapeless3.deriving.* ; import scala.deriving.*"""
)
.settings(commonSettings: _*)
.settings(commonSettings)
.settings(noPublishSettings)
.jsConfigure(_.enablePlugins(ScalaJSJUnitPlugin))

lazy val publishSettings = Seq(
lazy val publishSettings: Seq[Setting[_]] = Seq(
Test / publishArtifact := false,
pomIncludeRepository := (_ => false),
homepage := Some(url("https://github.com/milessabin/shapeless")),
Expand Down
44 changes: 21 additions & 23 deletions modules/deriving/src/test/scala/shapeless3/deriving/deriving.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ package shapeless3.deriving

import org.junit.Test

import scala.annotation.tailrec
import scala.compiletime.constValueTuple

import cats.Eval

import adts._
import OptE.{SmE, NnE}

Expand Down Expand Up @@ -162,55 +165,50 @@ class DerivationTests {
assert(v8.map(NnE)(identity) == NnE)
}

//Sanity check our Eval implementation for making Foldable#foldRight lazy
@Test
def eval: Unit = {
assert(Eval.now("foo").force == "foo")
assert(Eval.later("foo").force == "foo")
assert(Eval.later("foo").map(_ ++ "!").force == "foo!")
assert(Eval.later("foo").flatMap(s => Eval.now(s ++ "!")).force == "foo!")

def loop(n: Int): Eval[Int] = if (n == 1) Eval.now(1) else loop(n - 1).flatMap(n => Eval.now(n + 1))

//Stacksafe construction and evaluation
assert(loop(20000).force == 20000)
def mkCList(n: Int) = {
@tailrec
def loop(n: Int, acc: CList[Int]): CList[Int] =
if(n == 0) acc
else loop(n-1, CCons(1, acc))
loop(n, CNil)
}

@Test
def foldable: Unit = {
val v0 = Foldable[Box]
assert(v0.foldLeft(Box(1))(0)((acc: Int, x: Int) => acc + x) == 1)
assert(v0.foldRight(Box(1))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).force == 1)
assert(v0.foldRight(Box(1))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 1)

val v1 = Foldable[Sm]
assert(v1.foldLeft(Sm(1))(0)((acc: Int, x: Int) => acc + x) == 1)
assert(v1.foldRight(Sm(1))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).force == 1)
assert(v1.foldRight(Sm(1))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 1)
val v2 = Foldable[Const[Nn.type]]
assert(v2.foldLeft(Nn)(0)((acc: Int, x: Int) => acc + x) == 0)
assert(v2.foldRight(Nn)(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).force == 0)
assert(v2.foldRight(Nn)(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 0)
val v3 = Foldable[Opt]
assert(v3.foldLeft(Sm(1))(0)((acc: Int, x: Int) => acc + x) == 1)
assert(v3.foldRight(Sm(1))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).force == 1)
assert(v3.foldRight(Sm(1))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 1)
assert(v3.foldLeft(Nn)(0)((acc: Int, x: Int) => acc + x) == 0)
assert(v3.foldRight(Nn)(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).force == 0)
assert(v3.foldRight(Nn)(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 0)

val v4 = Foldable[Const[CNil.type]]
assert(v4.foldLeft(CNil)(0)((acc: Int, x: Int) => acc + x) == 0)
assert(v4.foldRight(CNil)(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).force == 0)
assert(v4.foldRight(CNil)(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 0)
val v5 = Foldable[CCons]
assert(v5.foldLeft(CCons(1, CCons(2, CNil)))(0)((acc: Int, x: Int) => acc + x) == 3)
assert(v5.foldRight(CCons(1, CCons(2, CNil)))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).force == 3)
assert(v5.foldRight(CCons(1, CCons(2, CNil)))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 3)
val v6 = Foldable[CList]
assert(v6.foldLeft(CNil)(0)((acc: Int, x: Int) => acc + x) == 0)
assert(v6.foldRight(CNil)(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).force == 0)
assert(v6.foldRight(CNil)(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 0)
assert(v6.foldLeft(CCons(1, CCons(2, CNil)))(0)((acc: Int, x: Int) => acc + x) == 3)
assert(v6.foldRight(CCons(1, CCons(2, CNil)))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).force == 3)
assert(v6.foldRight(CCons(1, CCons(2, CNil)))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 3)
assert(v6.foldRight(mkCList(20000))(Eval.later(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 20000)

val v7 = Foldable[OptE]
assert(v7.foldLeft(SmE(1))(0)((acc: Int, x: Int) => acc + x) == 1)
assert(v7.foldRight(SmE(1))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).force == 1)
assert(v7.foldRight(SmE(1))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 1)
assert(v7.foldLeft(NnE)(0)((acc: Int, x: Int) => acc + x) == 0)
assert(v7.foldRight(NnE)(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).force == 0)
assert(v7.foldRight(NnE)(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 0)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package shapeless3.deriving
import scala.annotation.tailrec
import scala.compiletime._

import cats.Eval

// Type classes

trait Monoid[A] {
Expand Down Expand Up @@ -232,53 +234,6 @@ object Traverse {
inline def derived[F[_]](using gen: K1.Generic[F]): Traverse[F] = traverseGen
}

//Encodes lazy evaluation for the sake of Foldable#foldRight
sealed trait Eval[A] {

def map[B](f: A => B): Eval[B] = flatMap(f.andThen(Eval.now(_)))

def flatMap[B](f: A => Eval[B]): Eval[B] = Bind(this, f)

//Simplistic, no error handling, etc, etc
def force: A = {
var stack: List[Any => Eval[Any]] = Nil

@tailrec
def go(e: Eval[Any]): Any =
e match {
case Now(a) => stack match {
case Nil => a
case k :: ks => {
stack = ks
go(k(a))
}
}
case Later(thunk) => stack match {
case Nil => thunk()
case k :: ks => {
stack = ks
go(k(thunk()))
}
}
case Bind(e, f) => {
stack = f.asInstanceOf :: stack
go(e.asInstanceOf)
}
}

go(this.asInstanceOf[Eval[Any]]).asInstanceOf[A]
}
}
case class Now[A](a: A) extends Eval[A]
case class Later[A](thunk: () => A) extends Eval[A]
case class Bind[X, A](ev: Eval[X], f: X => Eval[A]) extends Eval[A]

object Eval {
def now[A](value: A): Eval[A] = Now(value)

def later[A](thunk: => A): Eval[A] = Later(() => thunk)
}

trait Foldable[F[_]] {
def foldLeft[A, B](fa: F[A])(b: B)(f: (B, A) => B): B

Expand Down Expand Up @@ -307,7 +262,8 @@ object Foldable {

def foldRight[A, B](fa: F[A])(lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
inst.foldRight[A, Eval[B]](fa)(lb)(
[t[_]] => (fd: Foldable[t], t0: t[A], acc: Eval[B]) => Continue(fd.foldRight(t0)(acc)(f))
[t[_]] => (fd: Foldable[t], t0: t[A], acc: Eval[B]) =>
Continue(Eval.defer(fd.foldRight(t0)(acc)(f)))
)

given foldableCoproduct[F[_]](using inst: => K1.CoproductInstances[Foldable, F]): Foldable[F] with
Expand All @@ -318,7 +274,7 @@ object Foldable {

def foldRight[A, B](fa: F[A])(lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
inst.fold[A, Eval[B]](fa)(
[t[_]] => (fd: Foldable[t], t0: t[A]) => fd.foldRight(t0)(lb)(f)
[t[_]] => (fd: Foldable[t], t0: t[A]) => Eval.defer(fd.foldRight(t0)(lb)(f))
)

inline def derived[F[_]](using gen: K1.Generic[F]): Foldable[F] =
Expand Down
32 changes: 28 additions & 4 deletions modules/typeable/src/main/scala/shapeless3/typeable/typeable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ trait Typeable[T] extends Serializable {

object syntax {
object typeable {
implicit class Ops[T](t: T) {
extension [T](t: T) {
/**
* Cast the receiver to a value of type `U` if possible. This operation
* will be as precise wrt erasure as possible given the in-scope
Expand Down Expand Up @@ -64,7 +64,7 @@ object syntax {
object Typeable extends Typeable0 {
import java.{ lang => jl }
import scala.reflect.ClassTag
import syntax.typeable.given
import syntax.typeable.*

inline def apply[T](using tt: Typeable[T]): Typeable[T] = tt

Expand All @@ -91,8 +91,32 @@ object Typeable extends Typeable0 {
/** Typeable instance for `Unit`. */
given unitTypeable: Typeable[Unit] = ValueTypeable[Unit, scala.runtime.BoxedUnit](classOf[scala.runtime.BoxedUnit], "Unit")

/** Typeable instance for `java.lang.Byte`. */
given jlByteTypeable: Typeable[jl.Byte] = ValueTypeable[jl.Byte, jl.Byte](classOf[jl.Byte], "java.lang.Byte")
/** Typeable instance for `java.lang.Short`. */
given jlShortTypeable: Typeable[jl.Short] = ValueTypeable[jl.Short, jl.Short](classOf[jl.Short], "java.lang.Short")
/** Typeable instance for `java.lang.Character`. */
given jlCharacterTypeable: Typeable[jl.Character] = ValueTypeable[jl.Character, jl.Character](classOf[jl.Character], "java.lang.Character")
/** Typeable instance for `java.lang.Integer`. */
given jlIntegerTypeable: Typeable[jl.Integer] = ValueTypeable[jl.Integer, jl.Integer](classOf[jl.Integer], "java.lang.Integer")
/** Typeable instance for `java.lang.Long`. */
given jlLongTypeable: Typeable[jl.Long] = ValueTypeable[jl.Long, jl.Long](classOf[jl.Long], "java.lang.Long")
/** Typeable instance for `java.lang.Float`. */
given jlFloatTypeable: Typeable[jl.Float] = ValueTypeable[jl.Float, jl.Float](classOf[jl.Float], "java.lang.Float")
/** Typeable instance for `java.lang.Double`. */
given jlDoubleTypeable: Typeable[jl.Double] = ValueTypeable[jl.Double, jl.Double](classOf[jl.Double], "java.lang.Double")
/** Typeable instance for `java.lang.Boolean`. */
given jlBooleanTypeable: Typeable[jl.Boolean] = ValueTypeable[jl.Boolean, jl.Boolean](classOf[jl.Boolean], "java.lang.Boolean")
/** Typeable instance for `scala.runtime.BoxedUnit`. */
given srBoxedUnitTypeable: Typeable[scala.runtime.BoxedUnit] = ValueTypeable[scala.runtime.BoxedUnit, scala.runtime.BoxedUnit](classOf[scala.runtime.BoxedUnit], "scala.runtime.BoxedUnit")

def isAnyValClass[T](clazz: Class[T]) =
(classOf[jl.Number] isAssignableFrom clazz) ||
clazz == classOf[jl.Byte] ||
clazz == classOf[jl.Short] ||
clazz == classOf[jl.Integer] ||
clazz == classOf[jl.Long] ||
clazz == classOf[jl.Float] ||
clazz == classOf[jl.Double] ||
clazz == classOf[jl.Boolean] ||
clazz == classOf[jl.Character] ||
clazz == classOf[scala.runtime.BoxedUnit]
Expand Down Expand Up @@ -412,7 +436,7 @@ trait TypeCase[T] extends Serializable {
}

object TypeCase {
import syntax.typeable.given
import syntax.typeable.*
def apply[T](using tt: Typeable[T]): TypeCase[T] =
new TypeCase[T] {
def unapply(t: Any): Option[T] = t.cast[T]
Expand Down
Loading