Skip to content

Commit a7f2379

Browse files
authored
Merge pull request #1 from typelevel/topic/scalajs
Add support for Scala.js
2 parents 549a284 + 2ed2122 commit a7f2379

File tree

7 files changed

+125
-100
lines changed

7 files changed

+125
-100
lines changed

.github/workflows/ci.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ jobs:
2121
build:
2222
name: Build and Test
2323
strategy:
24+
fail-fast: false
2425
matrix:
2526
os: [ubuntu-latest]
2627
scala: [3.0.0]
2728
29+
platform: [jvm, js]
2830
runs-on: ${{ matrix.os }}
2931
steps:
3032
- name: Checkout current branch (full)
@@ -53,8 +55,13 @@ jobs:
5355
run: sbt ++${{ matrix.scala }} githubWorkflowCheck
5456

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

61+
- name: Validate JS
62+
if: matrix.platform == 'js'
63+
run: sbt ++${{ matrix.scala }} validateJS
64+
5865
publish:
5966
name: Publish Artifacts
6067
needs: [build]

build.sbt

Lines changed: 59 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,23 @@ ThisBuild / githubWorkflowJavaVersions := Seq("[email protected]")
1111

1212
ThisBuild / githubWorkflowArtifactUpload := false
1313

14+
ThisBuild / githubWorkflowBuildMatrixFailFast := Some(false)
15+
16+
val JvmCond = s"matrix.platform == 'jvm'"
17+
val JsCond = s"matrix.platform == 'js'"
18+
1419
ThisBuild / githubWorkflowBuild := Seq(
15-
WorkflowStep.Sbt(List("validateJVM"), name = Some("Validate JVM"))
20+
WorkflowStep.Sbt(List("validateJVM"), name = Some("Validate JVM"), cond = Some(JvmCond)),
21+
WorkflowStep.Sbt(List("validateJS"), name = Some("Validate JS"), cond = Some(JsCond))
1622
)
1723

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

28+
ThisBuild / githubWorkflowBuildMatrixAdditions +=
29+
"platform" -> List("jvm", "js")
30+
2231
ThisBuild / githubWorkflowPublishPreamble +=
2332
WorkflowStep.Use(UseRef.Public("olafurpg", "setup-gpg", "v3"))
2433

@@ -34,15 +43,17 @@ ThisBuild / githubWorkflowPublish := Seq(
3443
)
3544
)
3645

37-
addCommandAlias("validateJVM", ";clean;compile;test")
38-
39-
lazy val modules: List[ProjectReference] = List(
40-
deriving,
41-
test,
42-
typeable
43-
)
46+
addCommandAlias("validate", ";clean;validateJVM;validateJS")
47+
addCommandAlias("validateJVM", ";buildJVM;testJVM")
48+
addCommandAlias("validateJS", ";buildJS;testJS")
49+
addCommandAlias("buildJVM", ";derivingJVM/compile;testJVM/compile;typeableJVM/compile")
50+
addCommandAlias("buildJS", ";derivingJS/compile;testJS/compile;typeableJS/compile")
51+
addCommandAlias("testJVM", ";derivingJVM/test;testJVM/test;typeableJVM/test")
52+
addCommandAlias("testJS", ";derivingJS/test;testJS/test;typeableJS/test")
4453

4554
lazy val commonSettings = Seq(
55+
crossScalaVersions := (ThisBuild / crossScalaVersions).value,
56+
4657
scalacOptions ++= Seq(
4758
"-Xfatal-warnings",
4859
"-Yexplicit-nulls"
@@ -52,38 +63,64 @@ lazy val commonSettings = Seq(
5263
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test",
5364
)
5465

55-
lazy val root = project.in(file("."))
56-
.aggregate(modules:_*)
57-
.settings(commonSettings:_*)
66+
lazy val root = project
67+
.in(file("."))
68+
.settings(commonSettings)
69+
.settings(crossScalaVersions := Seq())
5870
.settings(noPublishSettings)
71+
.aggregate(
72+
derivingJVM,
73+
derivingJS,
74+
testJVM,
75+
testJS,
76+
typeableJVM,
77+
typeableJS
78+
)
5979

60-
lazy val deriving = project
80+
lazy val deriving = crossProject(JSPlatform, JVMPlatform)
81+
.crossType(CrossType.Pure)
6182
.in(file("modules/deriving"))
6283
.dependsOn(test % "test")
6384
.settings(
64-
moduleName := "shapeless3-deriving"
85+
moduleName := "shapeless3-deriving",
86+
libraryDependencies += "org.typelevel" %%% "cats-core" % "2.6.1" % "test"
6587
)
66-
.settings(commonSettings: _*)
88+
.settings(commonSettings)
6789
.settings(publishSettings)
90+
.jsConfigure(_.enablePlugins(ScalaJSJUnitPlugin))
91+
92+
lazy val derivingJVM = deriving.jvm
93+
lazy val derivingJS = deriving.js
6894

69-
lazy val test = project
95+
lazy val test = crossProject(JSPlatform, JVMPlatform)
96+
.crossType(CrossType.Pure)
7097
.in(file("modules/test"))
7198
.settings(
7299
moduleName := "shapeless3-test"
73100
)
74-
.settings(commonSettings: _*)
101+
.settings(commonSettings)
75102
.settings(publishSettings)
103+
.jsConfigure(_.enablePlugins(ScalaJSJUnitPlugin))
76104

77-
lazy val typeable = project
105+
lazy val testJVM = test.jvm
106+
lazy val testJS = test.js
107+
108+
lazy val typeable = crossProject(JSPlatform, JVMPlatform)
109+
.crossType(CrossType.Pure)
78110
.in(file("modules/typeable"))
79111
.dependsOn(test % "test")
80112
.settings(
81113
moduleName := "shapeless3-typeable"
82114
)
83-
.settings(commonSettings: _*)
115+
.settings(commonSettings)
84116
.settings(publishSettings)
117+
.jsConfigure(_.enablePlugins(ScalaJSJUnitPlugin))
118+
119+
lazy val typeableJVM = typeable.jvm
120+
lazy val typeableJS = typeable.js
85121

86-
lazy val local = project
122+
lazy val local = crossProject(JSPlatform, JVMPlatform)
123+
.crossType(CrossType.Pure)
87124
.in(file("local"))
88125
.dependsOn(deriving, test, typeable)
89126
.settings(
@@ -93,10 +130,11 @@ lazy val local = project
93130
Compile / console / scalacOptions -= "-Xprint:postInlining",
94131
console / initialCommands := """import shapeless3.deriving.* ; import scala.deriving.*"""
95132
)
96-
.settings(commonSettings: _*)
133+
.settings(commonSettings)
97134
.settings(noPublishSettings)
135+
.jsConfigure(_.enablePlugins(ScalaJSJUnitPlugin))
98136

99-
lazy val publishSettings = Seq(
137+
lazy val publishSettings: Seq[Setting[_]] = Seq(
100138
Test / publishArtifact := false,
101139
pomIncludeRepository := (_ => false),
102140
homepage := Some(url("https://github.com/milessabin/shapeless")),

modules/deriving/src/test/scala/shapeless3/deriving/deriving.scala

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@ package shapeless3.deriving
1818

1919
import org.junit.Test
2020

21+
import scala.annotation.tailrec
2122
import scala.compiletime.constValueTuple
2223

24+
import cats.Eval
25+
2326
import adts._
2427
import OptE.{SmE, NnE}
2528

@@ -162,55 +165,50 @@ class DerivationTests {
162165
assert(v8.map(NnE)(identity) == NnE)
163166
}
164167

165-
//Sanity check our Eval implementation for making Foldable#foldRight lazy
166-
@Test
167-
def eval: Unit = {
168-
assert(Eval.now("foo").force == "foo")
169-
assert(Eval.later("foo").force == "foo")
170-
assert(Eval.later("foo").map(_ ++ "!").force == "foo!")
171-
assert(Eval.later("foo").flatMap(s => Eval.now(s ++ "!")).force == "foo!")
172-
173-
def loop(n: Int): Eval[Int] = if (n == 1) Eval.now(1) else loop(n - 1).flatMap(n => Eval.now(n + 1))
174-
175-
//Stacksafe construction and evaluation
176-
assert(loop(20000).force == 20000)
168+
def mkCList(n: Int) = {
169+
@tailrec
170+
def loop(n: Int, acc: CList[Int]): CList[Int] =
171+
if(n == 0) acc
172+
else loop(n-1, CCons(1, acc))
173+
loop(n, CNil)
177174
}
178175

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

185182
val v1 = Foldable[Sm]
186183
assert(v1.foldLeft(Sm(1))(0)((acc: Int, x: Int) => acc + x) == 1)
187-
assert(v1.foldRight(Sm(1))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).force == 1)
184+
assert(v1.foldRight(Sm(1))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 1)
188185
val v2 = Foldable[Const[Nn.type]]
189186
assert(v2.foldLeft(Nn)(0)((acc: Int, x: Int) => acc + x) == 0)
190-
assert(v2.foldRight(Nn)(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).force == 0)
187+
assert(v2.foldRight(Nn)(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 0)
191188
val v3 = Foldable[Opt]
192189
assert(v3.foldLeft(Sm(1))(0)((acc: Int, x: Int) => acc + x) == 1)
193-
assert(v3.foldRight(Sm(1))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).force == 1)
190+
assert(v3.foldRight(Sm(1))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 1)
194191
assert(v3.foldLeft(Nn)(0)((acc: Int, x: Int) => acc + x) == 0)
195-
assert(v3.foldRight(Nn)(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).force == 0)
192+
assert(v3.foldRight(Nn)(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 0)
196193

197194
val v4 = Foldable[Const[CNil.type]]
198195
assert(v4.foldLeft(CNil)(0)((acc: Int, x: Int) => acc + x) == 0)
199-
assert(v4.foldRight(CNil)(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).force == 0)
196+
assert(v4.foldRight(CNil)(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 0)
200197
val v5 = Foldable[CCons]
201198
assert(v5.foldLeft(CCons(1, CCons(2, CNil)))(0)((acc: Int, x: Int) => acc + x) == 3)
202-
assert(v5.foldRight(CCons(1, CCons(2, CNil)))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).force == 3)
199+
assert(v5.foldRight(CCons(1, CCons(2, CNil)))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 3)
203200
val v6 = Foldable[CList]
204201
assert(v6.foldLeft(CNil)(0)((acc: Int, x: Int) => acc + x) == 0)
205-
assert(v6.foldRight(CNil)(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).force == 0)
202+
assert(v6.foldRight(CNil)(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 0)
206203
assert(v6.foldLeft(CCons(1, CCons(2, CNil)))(0)((acc: Int, x: Int) => acc + x) == 3)
207-
assert(v6.foldRight(CCons(1, CCons(2, CNil)))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).force == 3)
204+
assert(v6.foldRight(CCons(1, CCons(2, CNil)))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 3)
205+
assert(v6.foldRight(mkCList(20000))(Eval.later(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 20000)
208206

209207
val v7 = Foldable[OptE]
210208
assert(v7.foldLeft(SmE(1))(0)((acc: Int, x: Int) => acc + x) == 1)
211-
assert(v7.foldRight(SmE(1))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).force == 1)
209+
assert(v7.foldRight(SmE(1))(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 1)
212210
assert(v7.foldLeft(NnE)(0)((acc: Int, x: Int) => acc + x) == 0)
213-
assert(v7.foldRight(NnE)(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).force == 0)
211+
assert(v7.foldRight(NnE)(Eval.now(0))((x: Int, acc: Eval[Int]) => acc.map(_ + x)).value == 0)
214212
}
215213

216214
@Test

modules/deriving/src/test/scala/shapeless3/deriving/type-classes.scala

Lines changed: 5 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package shapeless3.deriving
1919
import scala.annotation.tailrec
2020
import scala.compiletime._
2121

22+
import cats.Eval
23+
2224
// Type classes
2325

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

235-
//Encodes lazy evaluation for the sake of Foldable#foldRight
236-
sealed trait Eval[A] {
237-
238-
def map[B](f: A => B): Eval[B] = flatMap(f.andThen(Eval.now(_)))
239-
240-
def flatMap[B](f: A => Eval[B]): Eval[B] = Bind(this, f)
241-
242-
//Simplistic, no error handling, etc, etc
243-
def force: A = {
244-
var stack: List[Any => Eval[Any]] = Nil
245-
246-
@tailrec
247-
def go(e: Eval[Any]): Any =
248-
e match {
249-
case Now(a) => stack match {
250-
case Nil => a
251-
case k :: ks => {
252-
stack = ks
253-
go(k(a))
254-
}
255-
}
256-
case Later(thunk) => stack match {
257-
case Nil => thunk()
258-
case k :: ks => {
259-
stack = ks
260-
go(k(thunk()))
261-
}
262-
}
263-
case Bind(e, f) => {
264-
stack = f.asInstanceOf :: stack
265-
go(e.asInstanceOf)
266-
}
267-
}
268-
269-
go(this.asInstanceOf[Eval[Any]]).asInstanceOf[A]
270-
}
271-
}
272-
case class Now[A](a: A) extends Eval[A]
273-
case class Later[A](thunk: () => A) extends Eval[A]
274-
case class Bind[X, A](ev: Eval[X], f: X => Eval[A]) extends Eval[A]
275-
276-
object Eval {
277-
def now[A](value: A): Eval[A] = Now(value)
278-
279-
def later[A](thunk: => A): Eval[A] = Later(() => thunk)
280-
}
281-
282237
trait Foldable[F[_]] {
283238
def foldLeft[A, B](fa: F[A])(b: B)(f: (B, A) => B): B
284239

@@ -307,7 +262,8 @@ object Foldable {
307262

308263
def foldRight[A, B](fa: F[A])(lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
309264
inst.foldRight[A, Eval[B]](fa)(lb)(
310-
[t[_]] => (fd: Foldable[t], t0: t[A], acc: Eval[B]) => Continue(fd.foldRight(t0)(acc)(f))
265+
[t[_]] => (fd: Foldable[t], t0: t[A], acc: Eval[B]) =>
266+
Continue(Eval.defer(fd.foldRight(t0)(acc)(f)))
311267
)
312268

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

319275
def foldRight[A, B](fa: F[A])(lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
320276
inst.fold[A, Eval[B]](fa)(
321-
[t[_]] => (fd: Foldable[t], t0: t[A]) => fd.foldRight(t0)(lb)(f)
277+
[t[_]] => (fd: Foldable[t], t0: t[A]) => Eval.defer(fd.foldRight(t0)(lb)(f))
322278
)
323279

324280
inline def derived[F[_]](using gen: K1.Generic[F]): Foldable[F] =

modules/typeable/src/main/scala/shapeless3/typeable/typeable.scala

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ trait Typeable[T] extends Serializable {
3333

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

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

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

94+
/** Typeable instance for `java.lang.Byte`. */
95+
given jlByteTypeable: Typeable[jl.Byte] = ValueTypeable[jl.Byte, jl.Byte](classOf[jl.Byte], "java.lang.Byte")
96+
/** Typeable instance for `java.lang.Short`. */
97+
given jlShortTypeable: Typeable[jl.Short] = ValueTypeable[jl.Short, jl.Short](classOf[jl.Short], "java.lang.Short")
98+
/** Typeable instance for `java.lang.Character`. */
99+
given jlCharacterTypeable: Typeable[jl.Character] = ValueTypeable[jl.Character, jl.Character](classOf[jl.Character], "java.lang.Character")
100+
/** Typeable instance for `java.lang.Integer`. */
101+
given jlIntegerTypeable: Typeable[jl.Integer] = ValueTypeable[jl.Integer, jl.Integer](classOf[jl.Integer], "java.lang.Integer")
102+
/** Typeable instance for `java.lang.Long`. */
103+
given jlLongTypeable: Typeable[jl.Long] = ValueTypeable[jl.Long, jl.Long](classOf[jl.Long], "java.lang.Long")
104+
/** Typeable instance for `java.lang.Float`. */
105+
given jlFloatTypeable: Typeable[jl.Float] = ValueTypeable[jl.Float, jl.Float](classOf[jl.Float], "java.lang.Float")
106+
/** Typeable instance for `java.lang.Double`. */
107+
given jlDoubleTypeable: Typeable[jl.Double] = ValueTypeable[jl.Double, jl.Double](classOf[jl.Double], "java.lang.Double")
108+
/** Typeable instance for `java.lang.Boolean`. */
109+
given jlBooleanTypeable: Typeable[jl.Boolean] = ValueTypeable[jl.Boolean, jl.Boolean](classOf[jl.Boolean], "java.lang.Boolean")
110+
/** Typeable instance for `scala.runtime.BoxedUnit`. */
111+
given srBoxedUnitTypeable: Typeable[scala.runtime.BoxedUnit] = ValueTypeable[scala.runtime.BoxedUnit, scala.runtime.BoxedUnit](classOf[scala.runtime.BoxedUnit], "scala.runtime.BoxedUnit")
112+
94113
def isAnyValClass[T](clazz: Class[T]) =
95-
(classOf[jl.Number] isAssignableFrom clazz) ||
114+
clazz == classOf[jl.Byte] ||
115+
clazz == classOf[jl.Short] ||
116+
clazz == classOf[jl.Integer] ||
117+
clazz == classOf[jl.Long] ||
118+
clazz == classOf[jl.Float] ||
119+
clazz == classOf[jl.Double] ||
96120
clazz == classOf[jl.Boolean] ||
97121
clazz == classOf[jl.Character] ||
98122
clazz == classOf[scala.runtime.BoxedUnit]
@@ -412,7 +436,7 @@ trait TypeCase[T] extends Serializable {
412436
}
413437

414438
object TypeCase {
415-
import syntax.typeable.given
439+
import syntax.typeable.*
416440
def apply[T](using tt: Typeable[T]): TypeCase[T] =
417441
new TypeCase[T] {
418442
def unapply(t: Any): Option[T] = t.cast[T]

0 commit comments

Comments
 (0)