Skip to content

Commit

Permalink
Scala 3 support (including DeriveSchema) (zio#243)
Browse files Browse the repository at this point in the history
* scala 3 macros WIP

* derive case classes

* derive enums

* get annotations for case classes

* handle recursion

* reorganize test

* recursive enums

* formatting

* fixes

* remove trait organization to fix dotty compilation

* fix build

* fix build
  • Loading branch information
kitlangton authored Apr 29, 2022
1 parent 0ed75b2 commit 08eabea
Show file tree
Hide file tree
Showing 60 changed files with 3,584 additions and 444 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
fail-fast: false
matrix:
java: ['[email protected]', '[email protected]']
scala: ['2.12.15', '2.13.6']
scala: ['2.12.15', '2.13.8', '3.1.1']
steps:
- uses: actions/[email protected]
- uses: olafurpg/setup-scala@v13
Expand Down
4 changes: 4 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ lazy val zioSchemaDerivation = crossProject(JSPlatform, JVMPlatform)
.in(file("zio-schema-derivation"))
.dependsOn(zioSchema, zioSchema, zioSchema % "test->test")
.settings(stdSettings("zio-schema-derivation"))
.settings(dottySettings)
.settings(crossProjectSettings)
.settings(buildInfoSettings("zio.schema"))
.settings(
Expand Down Expand Up @@ -171,6 +172,7 @@ lazy val zioSchemaProtobuf = crossProject(JSPlatform, JVMPlatform)
.in(file("zio-schema-protobuf"))
.dependsOn(zioSchema, zioSchemaDerivation, tests % "test->test")
.settings(stdSettings("zio-schema-protobuf"))
.settings(dottySettings)
.settings(crossProjectSettings)
.settings(buildInfoSettings("zio.schema.protobuf"))

Expand All @@ -184,6 +186,7 @@ lazy val zioSchemaThrift = crossProject(JSPlatform, JVMPlatform)
.in(file("zio-schema-thrift"))
.dependsOn(zioSchema, zioSchemaDerivation, tests % "test->test")
.settings(stdSettings("zio-schema-thrift"))
.settings(dottySettings)
.settings(crossProjectSettings)
.settings(buildInfoSettings("zio.schema.thrift"))
.settings(
Expand All @@ -202,6 +205,7 @@ lazy val zioSchemaOptics = crossProject(JSPlatform, JVMPlatform)
.in(file("zio-schema-optics"))
.dependsOn(zioSchema, zioSchemaDerivation, tests % "test->test")
.settings(stdSettings("zio-schema-optics"))
.settings(dottySettings)
.settings(crossProjectSettings)
.settings(buildInfoSettings("zio.schema.optics"))
.settings(
Expand Down
51 changes: 15 additions & 36 deletions project/BuildHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ object BuildHelper {
list.map(v => (v.split('.').take(2).mkString("."), v)).toMap
}

val Scala212: String = versions("2.12")
val Scala213: String = versions("2.13")
val ScalaDotty: String = "3.1.0" //versions.getOrElse("3.0", versions("3.1"))
val Scala212: String = versions("2.12")
val Scala213: String = versions("2.13")
val Scala3: String = versions("3.1") //versions.getOrElse("3.0", versions("3.1"))

val zioVersion = "1.0.13"
val zioJsonVersion = "0.2.0-M2"
Expand All @@ -40,7 +40,7 @@ object BuildHelper {
def macroDefinitionSettings = Seq(
scalacOptions += "-language:experimental.macros",
libraryDependencies ++= {
if (scalaVersion.value == ScalaDotty) Seq()
if (scalaVersion.value == Scala3) Seq()
else
Seq(
"org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided",
Expand All @@ -51,7 +51,7 @@ object BuildHelper {

private def compileOnlyDeps(scalaVersion: String) = {
val stdCompileOnlyDeps = {
if (scalaVersion == ScalaDotty)
if (scalaVersion == Scala3)
Seq(
"com.github.ghik" % s"silencer-lib_$Scala213" % silencerVersion % Provided
)
Expand Down Expand Up @@ -80,17 +80,19 @@ object BuildHelper {
"-language:existentials"
) ++ {
if (sys.env.contains("CI")) {
Seq("-Xfatal-warnings", "-Ypatmat-exhaust-depth", "80")
Seq("-Xfatal-warnings")
} else {
Seq("-Ypatmat-exhaust-depth", "80")
Seq()
}
}

val std2xOptions = Seq(
"-language:higherKinds",
"-explaintypes",
"-Yrangepos",
"-Xlint:_,-missing-interpolator,-type-parameter-shadow",
"-Xlint:_,-missing-interpolator,-type-parameter-shadow,-infer-any",
"-Ypatmat-exhaust-depth",
"40",
"-Ywarn-numeric-widen",
"-Ywarn-value-discard",
"-Xsource:3.0"
Expand Down Expand Up @@ -124,7 +126,6 @@ object BuildHelper {
"-Ywarn-unused",
"-Yno-adapted-args",
"-Ywarn-inaccessible",
"-Ywarn-infer-any",
"-Ywarn-nullary-override",
"-Ywarn-nullary-unit"
) ++ std2xOptions ++ optimizerOptions
Expand All @@ -135,34 +136,12 @@ object BuildHelper {
}

val dottySettings = Seq(
crossScalaVersions += ScalaDotty,
scalacOptions ++= {
if (scalaVersion.value == ScalaDotty)
Seq("-noindent")
else
Seq()
},
crossScalaVersions += Scala3,
scalacOptions --= {
if (scalaVersion.value == ScalaDotty)
if (scalaVersion.value == Scala3)
Seq("-Xfatal-warnings")
else
Seq()
},
Compile / doc / sources := {
val old = (Compile / doc / sources).value
if (scalaVersion.value == ScalaDotty) {
Nil
} else {
old
}
},
Test / parallelExecution := {
val old = (Test / parallelExecution).value
if (scalaVersion.value == ScalaDotty) {
false
} else {
old
}
}
)

Expand Down Expand Up @@ -215,11 +194,11 @@ object BuildHelper {
def stdSettings(prjName: String) =
Seq(
name := s"$prjName",
crossScalaVersions := Seq(Scala213, Scala212),
ThisBuild / scalaVersion := crossScalaVersions.value.head, //ScalaDotty,
crossScalaVersions := Seq(Scala213, Scala212, Scala3),
ThisBuild / scalaVersion := Scala213, //crossScalaVersions.value.head, //Scala3,
scalacOptions := compilerOptions(scalaVersion.value, optimize = !isSnapshot.value),
libraryDependencies ++= compileOnlyDeps(scalaVersion.value) ++ testDeps,
ThisBuild / semanticdbEnabled := scalaVersion.value != ScalaDotty, // enable SemanticDB,
ThisBuild / semanticdbEnabled := scalaVersion.value != Scala3, // enable SemanticDB,
ThisBuild / semanticdbOptions += "-P:semanticdb:synthetics:on",
ThisBuild / semanticdbVersion := scalafixSemanticdb.revision,
ThisBuild / scalafixScalaBinaryVersion := CrossVersion.binaryScalaVersion(scalaVersion.value),
Expand Down
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.5")
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.33")
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.10.0")
addSbtPlugin("com.github.cb372" % "sbt-explicit-dependencies" % "0.2.10")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.9.0")
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ object DynamicValueGen {
case typ: StandardType.DoubleType.type => gen(typ, Gen.anyDouble)
case typ: StandardType.StringType.type => gen(typ, Gen.anyString)
case typ: StandardType.ShortType.type => gen(typ, Gen.anyShort)
case typ: StandardType.ByteType.type => gen(typ, Gen.anyByte)
case typ: StandardType.IntType.type => gen(typ, Gen.anyInt)
case typ: StandardType.LongType.type => gen(typ, Gen.anyLong)
case typ: StandardType.FloatType.type => gen(typ, Gen.anyFloat)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,9 @@ object OrderingSpec extends DefaultRunnableSpec {
def genOrderedPairIdentityTransform[A](schema: Schema[A]): Gen[Random with Sized, SchemaAndPair[_]] =
for {
(small, large) <- genOrderedPair(schema)
} yield (schema.transformOrFail({ a: A =>
} yield (schema.transformOrFail({ (a: A) =>
Right(a)
}, { a: A =>
}, { (a: A) =>
Right(a)
}), small, large)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ object SchemaGen {
case class JArray(fields: List[Json]) extends Json

object Json {
implicit lazy val schema: Schema.Enum6[JArray, JDecimal, JNull.type, JNumber, JObject, JString, Json] =
implicit lazy val schema: Schema.Enum6[_, _, _, _, _, _, Json] =
DeriveSchema.gen[Json]

val leafGen: Gen[Random with Sized, Json] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,21 +121,22 @@ object types {
object Arity21 {
implicit lazy val schema: Schema[Arity21] = DeriveSchema.gen
}
case class Arity22(f1: Arity1, f2: Arity2, f3: Arity3, f4: Arity4, f5: Arity5, f6: Arity6, f7: Arity7, f8: Arity8, f9: Arity9, f10: Arity10, f11: Arity11, f12: Arity12, f13: Arity13, f14: Arity14, f15: Arity15, f16: Arity16, f17: Arity17, f18: Arity18, f19: Arity19, f20: Arity20, f21: Arity21, f22: ZonedDateTime) extends Arities
// case class Arity22(f1: Arity1, f2: Arity2, f3: Arity3, f4: Arity4, f5: Arity5, f6: Arity6, f7: Arity7, f8: Arity8, f9: Arity9, f10: Arity10, f11: Arity11, f12: Arity12, f13: Arity13, f14: Arity14, f15: Arity15, f16: Arity16, f17: Arity17, f18: Arity18, f19: Arity19, f20: Arity20, f21: Arity21, f22: ZonedDateTime) extends Arities

object Arity22 {
implicit lazy val schema: Schema[Arity22] = DeriveSchema.gen
}
case class Arity23(f1: Arity1, f2: Arity2, f3: Arity3, f4: Arity4, f5: Arity5, f6: Arity6, f7: Arity7, f8: Arity8, f9: Arity9, f10: Arity10, f11: Arity11, f12: Arity12, f13: Arity13, f14: Arity14, f15: Arity15, f16: Arity16, f17: Arity17, f18: Arity18, f19: Arity19, f20: Arity20, f21: Arity21, f22: Arity22, f23: OffsetTime) extends Arities
// object Arity22 {
// implicit lazy val schema: Schema[Arity22] = DeriveSchema.gen
// }
// case class Arity23(f1: Arity1, f2: Arity2, f3: Arity3, f4: Arity4, f5: Arity5, f6: Arity6, f7: Arity7, f8: Arity8, f9: Arity9, f10: Arity10, f11: Arity11, f12: Arity12, f13: Arity13, f14: Arity14, f15: Arity15, f16: Arity16, f17: Arity17, f18: Arity18, f19: Arity19, f20: Arity20, f21: Arity21, f22: Arity22, f23: OffsetTime) extends Arities

object Arity23 {
implicit lazy val schema: Schema[Arity23] = DeriveSchema.gen
}
case class Arity24(f1: Arity1, f2: Arity2, f3: Arity3, f4: Arity4, f5: Arity5, f6: Arity6, f7: Arity7, f8: Arity8, f9: Arity9, f10: Arity10, f11: Arity11, f12: Arity12, f13: Arity13, f14: Arity14, f15: Arity15, f16: Arity16, f17: Arity17, f18: Arity18, f19: Arity19, f20: Arity20, f21: Arity21, f22: Arity22, f23: Arity23, f24: OffsetDateTime) extends Arities
// object Arity23 {
// implicit lazy val schema: Schema[Arity23] = DeriveSchema.gen
// }

object Arity24 {
implicit lazy val schema: Schema[Arity24] = DeriveSchema.gen
}
// case class Arity24(f1: Arity1, f2: Arity2, f3: Arity3, f4: Arity4, f5: Arity5, f6: Arity6, f7: Arity7, f8: Arity8, f9: Arity9, f10: Arity10, f11: Arity11, f12: Arity12, f13: Arity13, f14: Arity14, f15: Arity15, f16: Arity16, f17: Arity17, f18: Arity18, f19: Arity19, f20: Arity20, f21: Arity21, f22: Arity22, f23: Arity23, f24: OffsetDateTime) extends Arities

// object Arity24 {
// implicit lazy val schema: Schema[Arity24] = DeriveSchema.gen
// }

implicit lazy val schema: Schema[Arities] = DeriveSchema.gen

Expand Down Expand Up @@ -229,13 +230,13 @@ object types {
Gen.const(Schema[Arities])
)

def anySchemaAndValue: Gen[Random with Sized, SchemaAndValue[_]] =
def anySchemaAndValue: Gen[Random with Sized, SchemaAndValue[Any]] =
for {
schema <- anySchema
dynamicValue <- DynamicValueGen.anyDynamicValueOfSchema(schema)
} yield schema.asInstanceOf[Schema[Any]] -> dynamicValue.toTypedValue(schema).toOption.get

def anySchemaAndValuePair: Gen[Random with Sized, SchemaAndValuePair[_]] =
def anySchemaAndValuePair: Gen[Random with Sized, SchemaAndValuePair[Any]] =
for {
schema <- anySchema
dynamicValue1 <- DynamicValueGen.anyDynamicValueOfSchema(schema)
Expand All @@ -245,7 +246,7 @@ object types {
.toOption
.get)

def anySchemaAndValues(n: Int): Gen[Random with Sized, SchemaAndValues[_]] =
def anySchemaAndValues(n: Int): Gen[Random with Sized, SchemaAndValues[Any]] =
for {
schema <- anySchema
dynamicValues <- Gen.listOfN(n)(DynamicValueGen.anyDynamicValueOfSchema(schema))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ object DefaultValueSpec extends DefaultRunnableSpec {
object Ok {
implicit lazy val schema: Schema[Ok] = DeriveSchema.gen[Ok]
}

case class Failed(code: Int, reason: String, additionalExplanation: Option[String], remark: String = "oops")
extends Status

object Failed {
implicit lazy val schema: Schema[Failed] = DeriveSchema.gen[Failed]
}
case object Pending extends Status

case object Pending extends Status {
implicit lazy val schema: Schema[Pending.type] = DeriveSchema.gen[Pending.type]
}

object Status {
implicit lazy val schema: Schema[Status] = DeriveSchema.gen[Status]
Expand Down
7 changes: 3 additions & 4 deletions tests/shared/src/test/scala/zio/schema/SchemaSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ object SchemaSpec extends DefaultRunnableSpec {
},
test("sequence") {
assert(Schema.chunk(schemaUnit))(equalTo(Schema.chunk(schemaUnit)))
},
} @@ TestAspect.scala2Only,
test("tuple") {
assert(Schema.Tuple(schemaUnit, schemaUnit))(equalTo(Schema.Tuple(schemaUnit, schemaUnit))) &&
assert(Schema.Tuple(schemaTransform, schemaTransform))(equalTo(Schema.Tuple(schemaTransform, schemaTransform)))
Expand All @@ -28,15 +28,14 @@ object SchemaSpec extends DefaultRunnableSpec {
test("transform") {
assert(schemaTransform)(equalTo(schemaTransform)) &&
assert(schemaTransformMethod)(equalTo(schemaTransformMethod))
},
} @@ TestAspect.scala2Only,
test("optional") {
assert(Schema.Optional(schemaUnit))(equalTo(Schema.Optional(schemaUnit)))
},
test("enumeration") {
assert(schemaEnum("key"))(equalTo(schemaEnum("key"))) &&
assert(schemaEnum("key1"))(not(equalTo(schemaEnum("key2"))))

}
} @@ TestAspect.scala2Only
),
test("Tuple.toRecord should preserve annotations") {
val left = Schema.primitive(StandardType.StringType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ object NodePathSpec extends DefaultRunnableSpec {
testM("partition path into internal path and leaf label") {
check(anyPathOfN(2)) { path =>
val (internalPath, leaf) = path.partitionLeaf

assertTrue(internalPath == NodePath(path.dropRight(1)))
assert(internalPath)(Assertion.equalTo(NodePath(path.dropRight(1)))) &&
assertTrue(leaf == Some(path.last))
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package zio.schema

import scala.annotation.StaticAnnotation

enum Color {
case Red
case Green
case Blue
}

case class name(string: String) extends StaticAnnotation

case class PersonId(int: Int)

@name("kit")
case class Person(
id: PersonId,
@name("wow")
@name("name field")
name: String,
@name("person age")
age: Int,
isAlive: Boolean,
friends: List[Person]
)

case class Address(
street: String,
city: String,
state: String,
zip: Int
)

case class PersonWithAddress(
person: Person,
address: Address,
meta: Int
)
Loading

0 comments on commit 08eabea

Please sign in to comment.