diff --git a/README.md b/README.md index 456897d1..0a9ecdbf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ # derevo + +[![Build & Release](https://github.com/tofu-tf/derevo/workflows/Scala%20CI/badge.svg)](https://github.com/tofu-tf/derevo/actions?query=workflow%3A%22Scala+CI%22) +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/tf.tofu/derevo-core_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/tf.tofu/derevo-core_2.13) +[![Scala Steward badge](https://img.shields.io/badge/Scala_Steward-helping-blue.svg?style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAMAAAARSr4IAAAAVFBMVEUAAACHjojlOy5NWlrKzcYRKjGFjIbp293YycuLa3pYY2LSqql4f3pCUFTgSjNodYRmcXUsPD/NTTbjRS+2jomhgnzNc223cGvZS0HaSD0XLjbaSjElhIr+AAAAAXRSTlMAQObYZgAAAHlJREFUCNdNyosOwyAIhWHAQS1Vt7a77/3fcxxdmv0xwmckutAR1nkm4ggbyEcg/wWmlGLDAA3oL50xi6fk5ffZ3E2E3QfZDCcCN2YtbEWZt+Drc6u6rlqv7Uk0LdKqqr5rk2UCRXOk0vmQKGfc94nOJyQjouF9H/wCc9gECEYfONoAAAAASUVORK5CYII=)](https://scala-steward.org) + + Multiple instance derivations inside a single macro annotation. + ## Basic Usage If a library has support for `derevo`, you can simply write ```scala @@ -9,8 +16,8 @@ case class Foo(...) ``` where `SomeCaseClass`, `AnotherCaseClass`, `fooDerivation`, `barDerivation` are some objects, extending one of the `InstanceDef` traits ([see module source](https://github.com/tofu-tf/derevo/blob/supertagged/core/src/main/scala/derevo/package.scala#L13:L21)). -For every element of `@derive` macro, there will be generated `implicit val` or `implicit def` (when `Foo` has type parameters) providing the corresponding typeclass. -For simple type derivation, if `Foo` has type parameters, the instance will require an instance of the same or specified another typeclass for proper derivation. +For every element of the `@derive` macro, there will be generated `implicit val` or `implicit def` (when `Foo` has type parameters) providing the corresponding typeclass. +For simple type derivation, if `Foo` has type parameters, the instance will require an instance of the same or specified other typeclass for proper derivation. Example: ```scala @derive(encoder) @@ -20,15 +27,16 @@ case class Foo[@phantom A, B](...) implicit def encoder$macro$1[A, B: Encoder]: Encoder.AsObject[Foo[A, B]] = ... ``` -`Foo` can be also a newtype in form of [estatico](https://github.com/estatico/scala-newtype) or [supertagged](https://github.com/rudogma/scala-supertagged) libraries. +`Foo` can also be a newtype in form of [estatico](https://github.com/estatico/scala-newtype) or [supertagged](https://github.com/rudogma/scala-supertagged) libraries. + +If you have problems with the initialization order you can optionally put the +`insertInstancesHere()` call to the body of your companion object to specify the place where `implicit val`s should be inserted. -If you have problems with initialization order you can optionally put the -`insertInstancesHere()` to the body of your companion object to specify the place where `implicit val`s should be inserted. -## Making your own derivation. -First, extend the object (companion object for your type would be the best) from one of the [`InstanceDef` traits](https://github.com/tofu-tf/derevo/blob/supertagged/core/src/main/scala/derevo/package.scala#L13:L21)). +## Making your own derivation. +First, extend the object (the companion object for your type is the best option) from one of the [`InstanceDef` traits](https://github.com/tofu-tf/derevo/blob/supertagged/core/src/main/scala/derevo/package.scala#L13:L21). -Then implement method `instance` for your object so it could derive corresponding instance. +Then implement the `instance` method for your object so it could derive the corresponding instance. Example: ```scala trait TypeClass[A] { @@ -40,10 +48,19 @@ object TypeClass extends Derivaton[TypeClass] { } ``` -Also, you can define additional methods `def apply(...)` or `def foo(...)` in your derivation object. This would allow instance creation as -`@derive(TypeClass(...))` or `@derive(TypeClass.foo(...))` +Also, you can define additional methods `def apply(...)` or `def foo(...)` in your derivation object. This will allow instance creation as +`@derive(TypeClass(...))` or `@derive(TypeClass.foo(...))`. + +To support `newtype` derivation, extend your object with (`NewtypeDerivation`)[https://github.com/tofu-tf/derevo/blob/master/core/src/main/scala/derevo/NewTypeRepr.scala#L8]. Alternatively, your object may have the `newtype[R]` method with a single type parameter that receives the underlying type and returns another object that has the `instance` method that works as described earlier. + +Sometimes, the required constraint may differ from the provided typeclass, e.g. `circe.Encoder` which requires +`Encoder` for each field but provides `Encoder.AsObject` for the target case class. +In this case you should extend `SpecificDerivation` instead, like `object foo extends SpecificDerivation[FromTc, ToTc, NT]`, where + + - `FromTc[A]` - typeclass that will be required for non-phantom type parameters + - `ToTc[A]` - normal typeclass provided by the instance + - `NT[A]` - type class that will be forwarded by the newtype derivation -To support `newtype` derivation, extend your object with (`NewtypeDerivation`)[https://github.com/tofu-tf/derevo/blob/master/core/src/main/scala/derevo/NewTypeRepr.scala#L8]. Alternatively, your object should have method `newtype[R]` with a single type parameter, that will receive underlying type, and should return another object which has method `instance` that should work as described earlier. ## Expression table @@ -52,28 +69,19 @@ To support `newtype` derivation, extend your object with (`NewtypeDerivation`)[h | `@derive(...foo, ...)` | `implicit val foo$macro: Foo[A] = foo.instance` | | `@derive(...foo(bar))` | `implicit val foo$macro : Foo[A] = foo(bar)` | | `@derive(...foo.quux(bar))` | `implicit val foo$macro : Foo[A] = foo.quux(bar)` | -| `@derive(...foo, ...)` when `A` is a newtype over`B` | `implicit val foo$macro: Foo[A] = foo.newtype[B].instance` | - +| `@derive(...foo, ...)` when `A` is a newtype over `B` | `implicit val foo$macro: Foo[A] = foo.newtype[B].instance` | ## Delegating to other macros -`Derevo` supports macro delegation when corresponding macro function is provided by another library. +`Derevo` supports macro delegation when the corresponding macro function is provided by another library. This works by adding the `@delegating` annotation to your delegator object: ```scala @delegating("full.qualified.method.path") ``` Then call `macro Derevo.delegate`, `macro Derevo.delegateParams`, `macro Derevo.delegateParams2` or `macro Derevo.delegateParams3` in the corresponding methods. -Example could be found [here](https://github.com/tofu-tf/derevo/blob/supertagged/circe/src/main/scala/derevo/circe/circe.scala). - -| CI | Release | -| --- | --- | -| ![Scala CI](https://github.com/manatki/derevo/workflows/Scala%20CI/badge.svg) | [![Maven Central](https://img.shields.io/maven-central/v/org.manatki/derevo-core_2.12.svg)](https://search.maven.org/search?q=derevo) | - -## Breaking changes in 0.11 -`org.manatki.derevo` pkg was shortened to `derevo`. +An example can be found [here](https://github.com/tofu-tf/derevo/blob/supertagged/circe/src/main/scala/derevo/circe/circe.scala). -Use [scalafix](https://scalacenter.github.io/scalafix/docs/users/installation) and [this rule](https://gist.github.com/REDNBLACK/9bc56ad71e4b01a63001339fa61b4cfd) for migration ## Installation For Scala 2.12 and older: @@ -87,7 +95,7 @@ scalacOptions += "-Ymacro-annotations" ``` ## IntelliJ Integration -Provides full support and visibility of implicits declared in `@derive` annotation. +Provides full support and visibility of implicits declared in the `@derive` annotation. To activate, simply click 'Yes' on the extensions popup, after adding any of the `derevo-` integration libraries to your project. ![](https://i.imgur.com/E6BKTeH.png) diff --git a/build.sbt b/build.sbt index 4934228c..597b2ead 100644 --- a/build.sbt +++ b/build.sbt @@ -1,7 +1,7 @@ name := "derevo" import com.typesafe.sbt.SbtGit.git -val publishVersion = "0.12.0" +val publishVersion = "0.12.1" val common = List( scalaVersion := "2.13.4", diff --git a/circe/src/main/scala/derevo/circe/circe.scala b/circe/src/main/scala/derevo/circe/circe.scala index 558102ca..18171336 100644 --- a/circe/src/main/scala/derevo/circe/circe.scala +++ b/circe/src/main/scala/derevo/circe/circe.scala @@ -4,6 +4,7 @@ import derevo.{Derevo, Derivation, PolyDerivation, delegating} import io.circe.derivation.renaming import io.circe.{Codec, Decoder, Encoder} import derevo.NewTypeDerivation +import derevo.SpecificDerivation @delegating("io.circe.derivation.deriveDecoder") object decoder extends Derivation[Decoder] with NewTypeDerivation[Decoder] { @@ -18,7 +19,7 @@ object decoder extends Derivation[Decoder] with NewTypeDerivation[Decoder] { } @delegating("io.circe.derivation.deriveEncoder") -object encoder extends PolyDerivation[Encoder, Encoder.AsObject] with NewTypeDerivation[Encoder] { +object encoder extends SpecificDerivation[Encoder, Encoder.AsObject, Encoder] with NewTypeDerivation[Encoder] { def instance[A]: Encoder.AsObject[A] = macro Derevo.delegate[Encoder.AsObject, A] /** @param arg1 naming function. For example io.circe.derivation.renaming.snakeCase @@ -29,7 +30,7 @@ object encoder extends PolyDerivation[Encoder, Encoder.AsObject] with NewTypeDer } @delegating("io.circe.derivation.deriveCodec") -object codec extends PolyDerivation[Codec, Codec.AsObject] with NewTypeDerivation[Codec] { +object codec extends SpecificDerivation[Codec, Codec.AsObject, Codec] with NewTypeDerivation[Codec] { def instance[A]: Codec.AsObject[A] = macro Derevo.delegate[Codec.AsObject, A] /** @param arg1 naming function. For example io.circe.derivation.renaming.snakeCase @@ -45,12 +46,12 @@ object snakeDecoder extends Derivation[Decoder] { } @delegating("io.circe.derivation.deriveEncoder", renaming.snakeCase, None) -object snakeEncoder extends PolyDerivation[Encoder, Encoder.AsObject] { +object snakeEncoder extends SpecificDerivation[Encoder, Encoder.AsObject, Encoder] { def instance[A]: Encoder.AsObject[A] = macro Derevo.delegate[Encoder.AsObject, A] } @delegating("io.circe.derivation.deriveCodec", renaming.snakeCase, false, None) -object snakeCodec extends PolyDerivation[Codec, Codec.AsObject] { +object snakeCodec extends SpecificDerivation[Codec, Codec.AsObject, Encoder] { def instance[A]: Codec.AsObject[A] = macro Derevo.delegate[Codec.AsObject, A] } @@ -60,11 +61,11 @@ object kebabDecoder extends Derivation[Decoder] { } @delegating("io.circe.derivation.deriveEncoder", renaming.kebabCase, None) -object kebabEncoder extends PolyDerivation[Encoder, Encoder.AsObject] { +object kebabEncoder extends SpecificDerivation[Encoder, Encoder.AsObject, Encoder] { def instance[A]: Encoder.AsObject[A] = macro Derevo.delegate[Encoder.AsObject, A] } @delegating("io.circe.derivation.deriveCodec", renaming.kebabCase, false, None) -object kebabCodec extends PolyDerivation[Codec, Codec.AsObject] { +object kebabCodec extends SpecificDerivation[Codec, Codec.AsObject, Encoder] { def instance[A]: Codec.AsObject[A] = macro Derevo.delegate[Codec.AsObject, A] } diff --git a/circe/src/test/scala/derevo/circe/CirceDerivationSpec.scala b/circe/src/test/scala/derevo/circe/CirceDerivationSpec.scala index a9086447..ac0e4e0b 100644 --- a/circe/src/test/scala/derevo/circe/CirceDerivationSpec.scala +++ b/circe/src/test/scala/derevo/circe/CirceDerivationSpec.scala @@ -7,6 +7,8 @@ import io.circe.parser._ import io.circe.derivation.renaming import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.must.Matchers +import io.estatico.newtype.macros.newtype +import CirceDerivationSpec.{Moe, Nya} class CirceDerivationSpec extends AnyFlatSpec with Matchers { "Circe derivation" should "derive simple codecs" in { @@ -87,6 +89,15 @@ class CirceDerivationSpec extends AnyFlatSpec with Matchers { it should "reject decode with inserted params" in { decode[ShelloBaz](shelloKebab).left.getOrElse(null) mustBe a[DecodingFailure] } + + it should "encode object newtype" in { + val bar: SealedTrait = SealedTrait.Bar(143) + assert(Moe(bar).asJson === (bar).asJson) + } + + it should "encode string newtype" in { + assert(Nya("uwu").asJson === "uwu".asJson) + } } @derive(encoder(identity, Some("type")), decoder(identity, false, Some("type"))) @@ -98,5 +109,14 @@ object SealedTrait { @derive(encoder, decoder) case class Baz(baz: String) extends SealedTrait +} + +object CirceDerivationSpec { + @derive(encoder, decoder) + @newtype + case class Moe(tutu: SealedTrait) + @derive(encoder, decoder) + @newtype + case class Nya(string: String) } diff --git a/core/src/main/scala/derevo/Derevo.scala b/core/src/main/scala/derevo/Derevo.scala index 271c218d..336dd2cb 100644 --- a/core/src/main/scala/derevo/Derevo.scala +++ b/core/src/main/scala/derevo/Derevo.scala @@ -11,7 +11,7 @@ class Derevo(val c: blackbox.Context) { val instanceDefs = Vector( ) - val IsSpecificDerivation = isInstanceDef[PolyDerivation[Any, Any]]() + val IsSpecificDerivation = isInstanceDef[SpecificDerivation[Any, Any, Any]]() val IsDerivation = isInstanceDef[Derivation[Any]]() val HKDerivation = new DerivationList( @@ -188,7 +188,8 @@ class Derevo(val c: blackbox.Context) { } if (allTparams.isEmpty) { - val resT = mkAppliedType(mode.to, tq"$typRef") + val resTc = if (newType.isDefined) mode.newtype else mode.to + val resT = mkAppliedType(resTc, tq"$typRef") q""" @java.lang.SuppressWarnings(scala.Array("org.wartremover.warts.All", "scalafix:All", "all")) implicit val $tn: $resT = $call @@ -244,6 +245,7 @@ class Derevo(val c: blackbox.Context) { val name: String, val from: Type, val to: Type, + val newtype: Type, val drop: Int, val cascade: Boolean ) @@ -253,25 +255,25 @@ class Derevo(val c: blackbox.Context) { val name = c.freshName(mangledName) c.typecheck(obj).tpe match { - case IsSpecificDerivation(f, t, d) => new NameAndTypes(name, f, t, d, true) - case IsDerivation(f, t, d) => new NameAndTypes(name, f, t, d, true) - case HKDerivation(f, t, d) => new NameAndTypes(name, f, t, d, false) - case _ => abort(s"$obj seems not extending InstanceDef traits") + case IsSpecificDerivation(f, t, nt, d) => new NameAndTypes(name, f, t, nt, d, true) + case IsDerivation(f, t, nt, d) => new NameAndTypes(name, f, t, nt, d, true) + case HKDerivation(f, t, nt, d) => new NameAndTypes(name, f, t, nt, d, false) + case _ => abort(s"$obj seems not extending InstanceDef traits") } } class DerivationList(ds: IsInstanceDef*) { - def unapply(objType: Type): Option[(Type, Type, Int)] = + def unapply(objType: Type): Option[(Type, Type, Type, Int)] = ds.iterator.flatMap(_.unapply(objType)).collectFirst { case x => x } } class IsInstanceDef(t: Type, drop: Int) { - val constrSymbol = t.typeConstructor.typeSymbol - def unapply(objType: Type): Option[(Type, Type, Int)] = + val constrSymbol = t.typeConstructor.typeSymbol + def unapply(objType: Type): Option[(Type, Type, Type, Int)] = objType.baseType(constrSymbol) match { - case TypeRef(_, _, List(tc)) => Some((tc, tc, drop)) - case TypeRef(_, _, List(from, to)) => Some((from, to, drop)) - case _ => None + case TypeRef(_, _, List(tc)) => Some((tc, tc, tc, drop)) + case TypeRef(_, _, List(from, to, nt)) => Some((from, to, nt, drop)) + case _ => None } } def isInstanceDef[T: TypeTag](dropTParams: Int = 0) = new IsInstanceDef(typeOf[T], dropTParams) diff --git a/core/src/main/scala/derevo/package.scala b/core/src/main/scala/derevo/package.scala index c8313d0a..89e94614 100644 --- a/core/src/main/scala/derevo/package.scala +++ b/core/src/main/scala/derevo/package.scala @@ -10,15 +10,15 @@ package derevo { //* numeration according to https://docs.tofu.tf/docs/internal/kind-enumeration sealed trait InstanceDef - trait Derivation[TC[_]] extends InstanceDef - trait DerivationKN1[TC[f[_]]] extends InstanceDef - trait DerivationKN2[TC[bf[_, _]]] extends InstanceDef - trait DerivationKN3[TC[alg[f[_]]]] extends InstanceDef - trait DerivationKN4[TC[tr[f[_], _]]] extends InstanceDef - trait DerivationKN5[TC[tf[_, _, _]]] extends InstanceDef - trait DerivationKN11[TC[alg[bf[_, _]]]] extends InstanceDef - trait DerivationKN17[TC[alg[btr[_, _], _, _]]] extends InstanceDef - trait PolyDerivation[FromTC[_], ToTC[_]] extends InstanceDef + trait Derivation[TC[_]] extends InstanceDef + trait DerivationKN1[TC[f[_]]] extends InstanceDef + trait DerivationKN2[TC[bf[_, _]]] extends InstanceDef + trait DerivationKN3[TC[alg[f[_]]]] extends InstanceDef + trait DerivationKN4[TC[tr[f[_], _]]] extends InstanceDef + trait DerivationKN5[TC[tf[_, _, _]]] extends InstanceDef + trait DerivationKN11[TC[alg[bf[_, _]]]] extends InstanceDef + trait DerivationKN17[TC[alg[btr[_, _], _, _]]] extends InstanceDef + trait SpecificDerivation[FromTC[_], ToTC[_], NT[_]] extends InstanceDef } @@ -34,4 +34,5 @@ package object derevo { type DerivationBi2[TC[alf[bf[_, _]]]] = DerivationKN11[TC] type DerivationTr[TC[T[f[_], a]]] = DerivationKN4[TC] type DerivationBiTr[TC[T[f[_, _], a, b]]] = DerivationKN17[TC] + type PolyDerivation[FromTC[_], ToTC[_]] = SpecificDerivation[FromTC, ToTC, ToTC] } diff --git a/reactivemongo/src/main/scala/derevo/reactivemongo/package.scala b/reactivemongo/src/main/scala/derevo/reactivemongo/package.scala index 56647c38..f0655998 100644 --- a/reactivemongo/src/main/scala/derevo/reactivemongo/package.scala +++ b/reactivemongo/src/main/scala/derevo/reactivemongo/package.scala @@ -9,13 +9,15 @@ package object reactivemongo { @delegating("reactivemongo.bson.Macros.writer") object bsonDocumentWriter - extends PolyDerivation[BsonValueWriter, BSONDocumentWriter] with NewTypeDerivation[BSONDocumentWriter] { + extends SpecificDerivation[BsonValueWriter, BSONDocumentWriter, BsonValueWriter] + with NewTypeDerivation[BSONDocumentWriter] { def instance[A]: BSONDocumentWriter[A] = macro Derevo.delegate[BSONDocumentWriter, A] } @delegating("reactivemongo.bson.Macros.reader") object bsonDocumentReader - extends PolyDerivation[BsonValueReader, BSONDocumentReader] with NewTypeDerivation[BSONDocumentReader] { + extends SpecificDerivation[BsonValueReader, BSONDocumentReader, BsonValueReader] + with NewTypeDerivation[BSONDocumentReader] { def instance[A]: BSONDocumentReader[A] = macro Derevo.delegate[BSONDocumentReader, A] } } diff --git a/tethys/src/main/scala/derevo/tethys/tethys.scala b/tethys/src/main/scala/derevo/tethys/tethys.scala index c73f07ba..b7bf3e0e 100644 --- a/tethys/src/main/scala/derevo/tethys/tethys.scala +++ b/tethys/src/main/scala/derevo/tethys/tethys.scala @@ -1,9 +1,10 @@ package derevo.tethys -import derevo.{Derevo, Derivation, PolyDerivation, delegating} +import derevo.{Derevo, Derivation, delegating} import tethys.derivation.builder._ import tethys.{JsonObjectWriter, JsonReader, JsonWriter} import derevo.NewTypeDerivation +import derevo.SpecificDerivation @delegating("tethys.derivation.semiauto.jsonReader") object tethysReader extends Derivation[JsonReader] with NewTypeDerivation[JsonReader] { @@ -14,7 +15,8 @@ object tethysReader extends Derivation[JsonReader] with NewTypeDerivation[JsonRe } @delegating("tethys.derivation.semiauto.jsonWriter") -object tethysWriter extends PolyDerivation[JsonWriter, JsonObjectWriter] with NewTypeDerivation[JsonWriter] { +object tethysWriter + extends SpecificDerivation[JsonWriter, JsonObjectWriter, JsonWriter] with NewTypeDerivation[JsonWriter] { def instance[A]: JsonObjectWriter[A] = macro Derevo.delegate[JsonObjectWriter, A] def apply[A](arg: WriterDerivationConfig): JsonObjectWriter[A] =