Skip to content

Commit

Permalink
semiauto derivation working without instanceOf in user code
Browse files Browse the repository at this point in the history
uses trick explained in softwaremill/magnolia#107 (comment)
  • Loading branch information
jtjeferreira committed Feb 22, 2020
1 parent c22eb9f commit 34a2ee5
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 48 deletions.
6 changes: 4 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ name := "magnolia-bson"

version := "0.1"

scalaVersion := "2.11.12"
scalaVersion := "2.12.10"

libraryDependencies += "com.propensive" %% "magnolia" % "0.9.0"
crossScalaVersions := Seq("2.12.10", "2.13.1")

libraryDependencies += "com.propensive" %% "magnolia" % "0.12.6"
libraryDependencies += "org.reactivemongo" %% "reactivemongo-bson" % "0.12.6"

//scalacOptions += "-Xlog-implicits"
Expand Down
56 changes: 17 additions & 39 deletions src/main/scala/magnolia/bson/BSONDerivation.scala
Original file line number Diff line number Diff line change
@@ -1,49 +1,32 @@
package magnolia.bson

import magnolia._
import reactivemongo.bson
import reactivemongo.bson._

import scala.util.{Failure, Success}

//case class Exported[T](instance: T) extends AnyVal
//object Exported {
// implicit final def a[A](implicit reader: BSONReader[_ <: BSONValue, A]): Exported[BSONReader[_ <: BSONValue, A]] = Exported(reader)
// implicit final def b[A](implicit reader: BSONWriter[A, _ <: BSONValue]): Exported[BSONWriter[A, _ <: BSONValue]] = Exported(reader)
//}

object BSONReadDerivation {

type Typeclass[T] = BSONReader[_ <: BSONValue, T]

def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] = {
new BSONReader[BSONValue, T] {
override def read(bson: BSONValue): T = bson match {
case doc: BSONDocument =>
caseClass.construct { p =>
doc.getAsUnflattenedTry(p.label)(p.typeclass) match {
case Success(Some(v)) => v
case Success(None) => p.default.get // TODO better ex
case Failure(ex) => throw ex
}
}
case _ => throw new IllegalStateException("we only handle-case classes")
BSONDocumentReader(doc =>
caseClass.construct { p =>
doc.getAsUnflattenedTry(p.label)(p.typeclass) match {
case Success(Some(v)) => v
case Success(None) => p.default.get // TODO better ex
case Failure(ex) => throw ex
}
}
}
)
}

def dispatch[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] = {
new BSONReader[BSONValue, T] {
override def read(bson: BSONValue): T = {
bson match {
case doc: BSONDocument =>
val className = doc.getAs[String]("className").getOrElse(throw new IllegalStateException("'className' is required for sealed traits"))
val subtype = sealedTrait.subtypes.find(_.typeName.full == className).get
subtype.typeclass.asInstanceOf[BSONReader[BSONValue, T]].read(doc)
case _ => throw new IllegalStateException("we only handle-case classes")
}
}
}
BSONDocumentReader(doc => {
val className = doc.getAs[String]("className").getOrElse(throw new IllegalStateException("'className' is required for sealed traits"))
val subtype = sealedTrait.subtypes.find(_.typeName.full == className).get
subtype.typeclass.asInstanceOf[BSONReader[BSONValue, T]].read(doc)
})
}

}
Expand All @@ -53,26 +36,21 @@ object BSONWriteDerivation {
type Typeclass[T] = BSONWriter[T, _ <: BSONValue]

def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] = {
new BSONWriter[T, BSONValue]() {
override def write(t: T): BSONDocument = BSONDocument(caseClass.parameters.map(p => (p.label, p.typeclass.write(p.dereference(t)))))
}
BSONDocumentWriter(t => BSONDocument(caseClass.parameters.map(p => (p.label, p.typeclass.write(p.dereference(t))))))
}

def dispatch[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] = {
new BSONWriter[T, BSONValue]() {
override def write(t: T): BSONValue = sealedTrait.dispatch(t) { subType =>
BSONDocumentWriter(t => sealedTrait.dispatch(t) { subType =>
subType.typeclass.writeTry(subType.cast(t)) match {
case Success(doc@BSONDocument(_)) => (doc ++ ("className" -> subType.typeName.full))
case Success(v) => v
case Success(v) => throw new Exception("should write a document...")
case Failure(ex) => throw ex
}
}
}
})
}
}

object test {
import reactivemongo.bson.DefaultBSONHandlers._
val a0: BSONReader[BSONString, String] = DefaultBSONHandlers.BSONStringHandler
val a1: BSONReader[_, String] = a0

Expand Down
15 changes: 12 additions & 3 deletions src/main/scala/magnolia/bson/derivation/reader/semiauto.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,30 @@ import magnolia.{CaseClass, Magnolia, SealedTrait, debug}
import reactivemongo.bson.{BSONDocument, BSONDocumentReader, BSONReader, BSONValue, VariantBSONReader}

import scala.language.experimental.macros
import scala.reflect.macros.whitebox

object semiauto {

// type B <: BSONValue
private type Typeclass[T] = BSONReadDerivation.Typeclass[T]
//
// val d = new BSONReadDerivation[B] {}

def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] =
BSONReadDerivation.combine(caseClass)

def dispatch[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] =
BSONReadDerivation.dispatch(sealedTrait)

def deriveMagnoliaReader[T]: Typeclass[T] = macro Magnolia.gen[T]
def deriveMagnoliaReader[T]: BSONDocumentReader[T] = macro ExportedMagnolia.materializeImpl[T]
// Wrap the output of Magnolia in an Exported to force it to a lower priority.
// This seems to work, despite magnolia hardcode checks for `macroApplication` symbol
// and relying on getting a diverging implicit expansion error for auto-mode.
// Thankfully at least it doesn't check the output type of its `macroApplication`
object ExportedMagnolia {
def materializeImpl[A](c: whitebox.Context)(implicit t: c.WeakTypeTag[A]): c.Expr[BSONDocumentReader[A]] = {
val magnoliaTree = c.Expr[Typeclass[A]](Magnolia.gen[A](c))
c.universe.reify(magnoliaTree.splice.asInstanceOf[BSONDocumentReader[A]])
}
}
}

//object semiauto extends semiauto
15 changes: 13 additions & 2 deletions src/main/scala/magnolia/bson/derivation/writer/semiauto.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package magnolia.bson.derivation.writer

import magnolia.bson.BSONWriteDerivation
import magnolia.{CaseClass, Magnolia, SealedTrait}
import reactivemongo.bson.{BSONDocument, BSONValue, BSONWriter}
import reactivemongo.bson.{BSONDocument, BSONDocumentReader, BSONDocumentWriter, BSONValue, BSONWriter}

import scala.language.experimental.macros
import scala.reflect.macros.whitebox

object semiauto {

Expand All @@ -19,5 +20,15 @@ object semiauto {
def dispatch[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] =
BSONWriteDerivation.dispatch(sealedTrait)

def deriveMagnoliaWriter[T]: Typeclass[T] = macro Magnolia.gen[T]
def deriveMagnoliaWriter[T]: BSONDocumentWriter[T] = macro ExportedMagnolia.materializeImpl[T]
// Wrap the output of Magnolia in an Exported to force it to a lower priority.
// This seems to work, despite magnolia hardcode checks for `macroApplication` symbol
// and relying on getting a diverging implicit expansion error for auto-mode.
// Thankfully at least it doesn't check the output type of its `macroApplication`
object ExportedMagnolia {
def materializeImpl[A](c: whitebox.Context)(implicit t: c.WeakTypeTag[A]): c.Expr[BSONDocumentWriter[A]] = {
val magnoliaTree = c.Expr[Typeclass[A]](Magnolia.gen[A](c))
c.universe.reify(magnoliaTree.splice.asInstanceOf[BSONDocumentWriter[A]])
}
}
}
4 changes: 2 additions & 2 deletions src/test/scala/BSONDerivationTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ object examples {
implicit val cityReader = deriveMagnoliaReader[City]
implicit val cityWriter = deriveMagnoliaWriter[City]

implicit val tripReader: BSONReader[BSONValue, Trip] = deriveMagnoliaReader[Trip].asInstanceOf[BSONReader[BSONValue, Trip]]
implicit val tripWriter: BSONWriter[Trip, BSONValue] = deriveMagnoliaWriter[Trip].asInstanceOf[BSONWriter[Trip,BSONValue]]
implicit val tripReader: BSONDocumentReader[Trip] = deriveMagnoliaReader[Trip]
implicit val tripWriter: BSONDocumentWriter[Trip] = deriveMagnoliaWriter[Trip]
}
}

Expand Down

0 comments on commit 34a2ee5

Please sign in to comment.