Skip to content

Commit

Permalink
Write an example implementation of a library with Chimney derivarion …
Browse files Browse the repository at this point in the history
…engine but based on a different type-class
  • Loading branch information
MateuszKubuszok committed Jul 18, 2024
1 parent 6d2fa3e commit 0c3535d
Show file tree
Hide file tree
Showing 9 changed files with 247 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ package io.scalaland.chimney.internal.compiletime
*
* def deriveBody(src: c.Expr[From]): c.Expr[To] = {
* val cfg = TransformerConfiguration() // customize, read config with DSL etc
* val context = TransformationContext.ForTotal(src, cfg)
* val context = TransformationContext.ForTotal.create(src, cfg)
*
* deriveFinalTransformationResultExpr(context).toEither.fold(
* derivationErrors => reportError(derivationErrors.toString), // customize
Expand Down Expand Up @@ -118,7 +118,7 @@ package io.scalaland.chimney.internal.compiletime
*
* def deriveBody(src: Expr[From]): Expr[To] = {
* val cfg = TransformerConfiguration() // customize, read config with DSL etc
* val context = TransformationContext.ForTotal(src, cfg)
* val context = TransformationContext.ForTotal.create(src, cfg)
*
* deriveFinalTransformationResultExpr(context).toEither.fold(
* derivationErrors => reportError(derivationErrors.toString), // customize
Expand Down Expand Up @@ -150,4 +150,20 @@ trait DerivationEngine
with derivation.transformer.integrations.PartiallyBuildIterables
with derivation.transformer.integrations.TotallyBuildIterables
with derivation.transformer.integrations.TotallyOrPartiallyBuildIterables
with derivation.transformer.rules.TransformationRules
with derivation.transformer.rules.TransformationRules {

type DerivationResult[+A] = io.scalaland.chimney.internal.compiletime.DerivationResult[A]
val DerivationResult = io.scalaland.chimney.internal.compiletime.DerivationResult

/** Adapts TransformationExpr[To] to expected type of transformation */
def deriveFinalTransformationResultExpr[From, To](implicit
ctx: TransformationContext[From, To]
): DerivationResult[Expr[ctx.Target]] =
DerivationResult.log(s"Start derivation with context: $ctx") >>
deriveTransformationResultExpr[From, To]
.map { transformationExpr =>
ctx.fold(_ => transformationExpr.ensureTotal.asInstanceOf[Expr[ctx.Target]])(_ =>
transformationExpr.ensurePartial.asInstanceOf[Expr[ctx.Target]]
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.scalaland.chimney.example

import scala.language.experimental.macros

trait MyTypeClassCompanionPlatform { this: MyTypeClass.type =>

def deriving[From, To]: MyTypeClass[From, To] = macro internal.MyTypeClassMacros.derivingImpl[From, To]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package io.scalaland.chimney.example.internal

import io.scalaland.chimney.example.MyTypeClass
import io.scalaland.chimney.example.internal.MyTypeClassDerivation
import io.scalaland.chimney.internal.compiletime.{DerivationEnginePlatform, StandardRules}

trait MyTypeClassDerivationPlatform extends DerivationEnginePlatform with MyTypeClassDerivation with StandardRules {

// in Scala-2-specific code, remember to import content of the universe
import c.universe.*

protected object MyTypes extends MyTypesModule {

import Type.platformSpecific.*

object MyTypeClass extends MyTypeClassModule {
def apply[From: Type, To: Type]: Type[MyTypeClass[From, To]] = weakTypeTag[MyTypeClass[From, To]]
def unapply[A](A: Type[A]): Option[(??, ??)] =
A.asCtor[MyTypeClass[?, ?]].map(A0 => A0.param(0) -> A0.param(1)) // utility from Type.platformSpecific.*
}
}

protected object MyExprs extends MyExprsModule {

def callMyTypeClass[From: Type, To: Type](tc: Expr[MyTypeClass[From, To]], from: Expr[From]): Expr[To] =
c.Expr[To](q"""$tc.convert($from)""")

def createTypeClass[From: Type, To: Type](body: Expr[From] => Expr[To]): Expr[MyTypeClass[From, To]] = {
val name = ExprPromise.platformSpecific.freshTermName("from")
// remember to use full qualified names in Scala 2 macros!!!
c.Expr[MyTypeClass[From, To]](
q"""
new _root_.io.scalaland.chimney.example.MyTypeClass[${Type[From]}, ${Type[To]}] {
def convert($name: ${Type[From]}): ${Type[To]} = ${body(c.Expr[From](q"$name"))}
}
"""
)
}
}

final override protected val rulesAvailableForPlatform: List[Rule] = List(
MyTypeClassImplicitRule, // replacing TransformImplicitRule
TransformSubtypesRule,
TransformToSingletonRule,
TransformOptionToOptionRule,
TransformPartialOptionToNonOptionRule,
TransformToOptionRule,
TransformValueClassToValueClassRule,
TransformValueClassToTypeRule,
TransformTypeToValueClassRule,
TransformEitherToEitherRule,
TransformMapToMapRule,
TransformIterableToIterableRule,
TransformProductToProductRule,
TransformSealedHierarchyToSealedHierarchyRule
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.scalaland.chimney.example.internal

import io.scalaland.chimney.example.MyTypeClass

import scala.reflect.macros.blackbox

// Scala 2 macro bundle
class MyTypeClassMacros(val c: blackbox.Context) extends MyTypeClassDerivationPlatform {

// Scala 2 is kinda unaware during macro expansion that myTypeClassDerivation takes c.WeakTypeTag, and we need to
// point it out for it, explicitly
def derivingImpl[From: c.WeakTypeTag, To: c.WeakTypeTag]: c.Expr[MyTypeClass[From, To]] =
myTypeClassDerivation[From, To]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.scalaland.chimney.example

trait MyTypeClassCompanionPlatform { this: MyTypeClass.type =>

inline def deriving[From, To] = ${ internal.MyTypeClassMacros.derivingImpl[From, To] }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package io.scalaland.chimney.example.internal

import io.scalaland.chimney.example.MyTypeClass
import io.scalaland.chimney.example.internal.MyTypeClassDerivation
import io.scalaland.chimney.internal.compiletime.{DerivationEnginePlatform, StandardRules}

abstract class MyTypeClassDerivationPlatform(q: scala.quoted.Quotes)
extends DerivationEnginePlatform(q)
with MyTypeClassDerivation
with StandardRules {

// in Scala-3-specific code, remember to import content of the quotes and quotes.reflect
import q.*, q.reflect.*

protected object MyTypes extends MyTypesModule {

object MyTypeClass extends MyTypeClassModule {
def apply[From: Type, To: Type]: Type[MyTypeClass[From, To]] = scala.quoted.Type.of[MyTypeClass[From, To]]
def unapply[A](A: Type[A]): Option[(??, ??)] = A match
case '[MyTypeClass[from, to]] => Some((Type[from].as_??, Type[to].as_??))
case _ => None
}
}

protected object MyExprs extends MyExprsModule {

def callMyTypeClass[From: Type, To: Type](tc: Expr[MyTypeClass[From, To]], from: Expr[From]): Expr[To] =
'{ ${ tc }.convert(${ from }) }

def createTypeClass[From: Type, To: Type](body: Expr[From] => Expr[To]): Expr[MyTypeClass[From, To]] =
'{ new MyTypeClass[From, To] { def convert(from: From): To = ${ body('{ from }) } } }
}

final override protected val rulesAvailableForPlatform: List[Rule] = List(
MyTypeClassImplicitRule, // replacing TransformImplicitRule
TransformSubtypesRule,
TransformToSingletonRule,
TransformOptionToOptionRule,
TransformPartialOptionToNonOptionRule,
TransformToOptionRule,
TransformValueClassToValueClassRule,
TransformValueClassToTypeRule,
TransformTypeToValueClassRule,
TransformEitherToEitherRule,
TransformMapToMapRule,
TransformIterableToIterableRule,
TransformProductToProductRule,
TransformSealedHierarchyToSealedHierarchyRule
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.scalaland.chimney.example.internal

import io.scalaland.chimney.example.MyTypeClass

import scala.quoted.*

class MyTypeClassMacros(q: Quotes) extends MyTypeClassDerivationPlatform(q)
object MyTypeClassMacros {

// Scala 3 expect all macros to be methods in objects, no Scala 2's macro-bundle-like equivalents, so we have to
// instantiate a class and call it ourselves
def derivingImpl[From: Type, To: Type](using q: Quotes): Expr[MyTypeClass[From, To]] =
new MyTypeClassMacros(q).myTypeClassDerivation[From, To]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.scalaland.chimney.example

trait MyTypeClass[From, To] {

def convert(from: From): To
}
object MyTypeClass extends MyTypeClassCompanionPlatform
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package io.scalaland.chimney.example.internal

import io.scalaland.chimney.example.MyTypeClass
import io.scalaland.chimney.internal.compiletime.DerivationEngine

trait MyTypeClassDerivation extends DerivationEngine {

// example of platform-independent type definition
protected val MyTypes: MyTypesModule
protected trait MyTypesModule { this: MyTypes.type =>

// Provides
// - MyTypeClass.apply[From, To]: Type[MyTypeClass[From, To]]
// - MyTypeClass.unapply(tpe: Type[Any]): Option[(??, ??)] // existential types
val MyTypeClass: MyTypeClassModule
trait MyTypeClassModule extends Type.Ctor2[MyTypeClass] { this: MyTypeClass.type => }

// use in platform-independent code (it cannot generate Type instances, as opposed to Scala 2/Scala 3 macros)
object Implicits {

implicit def MyTypeClassType[From: Type, To: Type]: Type[MyTypeClass[From, To]] = MyTypeClass[From, To]
}
}

// example of platform-independent expr utility
protected val MyExprs: MyExprsModule
protected trait MyExprsModule { this: MyExprs.type =>

import MyTypes.Implicits.*

def callMyTypeClass[From: Type, To: Type](tc: Expr[MyTypeClass[From, To]], from: Expr[From]): Expr[To]

def createTypeClass[From: Type, To: Type](body: Expr[From] => Expr[To]): Expr[MyTypeClass[From, To]]

def summonMyTypeClass[From: Type, To: Type]: Option[Expr[MyTypeClass[From, To]]] =
Expr.summonImplicit[MyTypeClass[From, To]]

// use in platform-independent code (since it does not have quotes nor quasiquotes)
object Implicits {

implicit class MyTypeClassOps[From: Type, To: Type](private val tc: Expr[MyTypeClass[From, To]]) {

def convert(from: Expr[From]): Expr[To] = callMyTypeClass(tc, from)
}
}
}

import MyExprs.Implicits.*

// example of a platform-independent Rule
object MyTypeClassImplicitRule extends Rule("MyTypeClassImplicit") {

override def expand[From, To](implicit
ctx: TransformationContext[From, To]
): DerivationResult[Rule.ExpansionResult[To]] =
MyExprs.summonMyTypeClass[From, To] match {
case Some(myTypeClass) => DerivationResult.expandedTotal(myTypeClass.convert(ctx.src))
case None => DerivationResult.attemptNextRule
}
}

def myTypeClassDerivation[From: Type, To: Type]: Expr[MyTypeClass[From, To]] =
MyExprs.createTypeClass[From, To] { (from: Expr[From]) =>
val cfg = TransformerConfiguration() // customize, read config with DSL etc
val context = TransformationContext.ForTotal.create[From, To](from, cfg)

deriveFinalTransformationResultExpr(context).toEither.fold(
derivationErrors => reportError(derivationErrors.toString), // customize
identity
)
}
}

0 comments on commit 0c3535d

Please sign in to comment.