8000 [Issue#37] Fix anyval transformers and standardize instance names by arainko · Pull Request #44 · arainko/ducktape · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

[Issue#37] Fix anyval transformers an 8000 d standardize instance names #44

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ ThisBuild / developers := List(
url("https://github.com/arainko")
)
)
ThisBuild / scalaVersion := "3.2.1"
ThisBuild / scalaVersion := "3.2.2"
ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.6.0"

ThisBuild / versionPolicyIntention := Compatibility.BinaryCompatible
Expand All @@ -30,7 +30,9 @@ lazy val previousArtifacts =
Set(
"io.github.arainko" %% "ducktape" % "0.1.0",
"io.github.arainko" %% "ducktape" % "0.1.1",
"io.github.arainko" %% "ducktape" % "0.1.2"
"io.github.arainko" %% "ducktape" % "0.1.2",
"io.github.arainko" %% "ducktape" % "0.1.3",
"io.github.arainko" %% "ducktape" % "0.1.4",
)

lazy val root =
Expand Down
135 changes: 101 additions & 34 deletions ducktape/src/main/scala/io/github/arainko/ducktape/Transformer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,103 @@ object Transformer {
inline def showCode[A](inline value: A): A = DebugMacros.code(value)
}

given identity[Source, Dest >: Source]: Identity[Source, Dest] = Identity[Source, Dest]

given betweenNonOptionOption[Source, Dest](using Transformer[Source, Dest]): Transformer[Source, Option[Dest]] =
from => Transformer[Source, Dest].transform.andThen(Some.apply)(from)

given betweenOptions[Source, Dest](using
Transformer[Source, Dest]
): Transformer[Option[Source], Option[Dest]] =
from => from.map(Transformer[Source, Dest].transform)

given betweenCollections[Source, Dest, SourceCollection[elem] <: Iterable[elem], DestCollection[elem] <: Iterable[elem]](using
transformer: Transformer[Source, Dest],
factory: Factory[Dest, DestCollection[Dest]]
): Transformer[SourceCollection[Source], DestCollection[Dest]] = from => from.map(transformer.transform).to(factory)

given betweenEithers[Source1, Source2, Dest1, Dest2](using
Transformer[Source1, Source2],
Transformer[Dest1, Dest2]
): Transformer[Either[Source1, Dest1], Either[Source2, Dest2]] = {
case Right(value) => Right(Transformer[Dest1, Dest2].transform(value))
case Left(value) => Left(Transformer[Source1, Source2].transform(value))
}

inline given betweenProducts[Source, Dest](using
Mirror.ProductOf[Source],
Mirror.ProductOf[Dest]
): Transformer.ForProduct[Source, Dest] =
Transformer.ForProduct.make(DerivedTransformers.product[Source, Dest])

inline given betweenCoproducts[Source, Dest](using
Mirror.SumOf[Source],
Mirror.SumOf[Dest]
): Transformer.ForCoproduct[Source, Dest] =
Transformer.ForCoproduct.make(DerivedTransformers.coproduct[Source, Dest])

inline given betweenUnwrappedWrapped[Source, Dest](using
Dest <:< AnyVal,
Dest <:< Product
): Transformer.ToAnyVal[Source, Dest] =
Transformer.ToAnyVal.make(DerivedTransformers.toAnyVal[Source, Dest])

inline given betweenWrappedUnwrapped[Source, Dest](using
Source <:< AnyVal,
Source <:< Product
): Transformer.FromAnyVal[Source, Dest] =
Transformer.FromAnyVal.make(DerivedTransformers.fromAnyVal[Source, Dest])

@deprecated(message = "Use 'Transformer.identity' instead", since = "0.1.5")
final def given_Identity_Source_Dest[Source, Dest >: Source]: Identity[Source, Dest] = identity[Source, Dest]

@deprecated(message = "Use 'Transformer.betweenProducts' instead", since = "0.1.5")
inline def forProducts[Source, Dest](using Mirror.ProductOf[Source], Mirror.ProductOf[Dest]): ForProduct[Source, Dest] =
ForProduct.make(DerivedTransformers.product[Source, Dest])

@deprecated(message = "Use 'Transformer.betweenCoproducts' instead", since = "0.1.5")
inline def forCoproducts[Source, Dest](using Mirror.SumOf[Source], Mirror.SumOf[Dest]): ForCoproduct[Source, Dest] =
ForCoproduct.make(DerivedTransformers.coproduct[Source, Dest])

@deprecated(message = "Use 'Transformer.betweenNonOptionOption' instead", since = "0.1.5")
final def given_Transformer_Source_Option[Source, Dest](using Transformer[Source, Dest]): Transformer[Source, Option[Dest]] =
betweenNonOptionOption[Source, Dest]

@deprecated(message = "Use 'Transformer.betweenOptions' instead", since = "0.1.5")
final def given_Transformer_Option_Option[Source, Dest](using
Transformer[Source, Dest]
): Transformer[Option[Source], Option[Dest]] =
from => from.map(Transformer[Source, Dest].transform)

@deprecated(message = "Use 'Transformer.betweenEithers' instead", since = "0.1.5")
final def given_Transformer_Either_Either[A1, A2, B1, B2](using
Transformer[A1, A2],
Transformer[B1, B2]
): Transformer[Either[A1, B1], Either[A2, B2]] = {
case Right(value) => Right(Transformer[B1, B2].transform(value))
case Left(value) => Left(Transformer[A1, A2].transform(value))
}

@deprecated(message = "Use 'Transformer.betweenCollections' instead", since = "0.1.5")
final def given_Transformer_SourceCollection_DestCollection[
Source,
Dest,
SourceCollection[elem] <: Iterable[elem],
DestCollection[elem] <: Iterable[elem]
](using
trans: Transformer[Source, Dest],
factory: Factory[Dest, DestCollection[Dest]]
): Transformer[SourceCollection[Source], DestCollection[Dest]] =
betweenCollections[Source, Dest, SourceCollection, DestCollection]

@deprecated(message = "Use 'Transformer.betweenUnwrappedWrapped' instead", since = "0.1.5")
inline def fromAnyVal[Source <: AnyVal, Dest]: FromAnyVal[Source, Dest] =
FromAnyVal.make(DerivedTransformers.fromAnyVal[Source, Dest])

@deprecated(message = "Use 'Transformer.betweenWrappedUnwrapped' instead", since = "0.1.5")
inline def toAnyVal[Source, Dest <: AnyVal]: ToAnyVal[Source, Dest] =
ToAnyVal.make(DerivedTransformers.toAnyVal[Source, Dest])

final class Identity[Source, Dest >: Source] private[Transformer] extends Transformer[Source, Dest] {
def transform(from: Source): Dest = from
}
Expand Down Expand Up @@ -57,7 +154,7 @@ object Transformer {
}
}

sealed trait FromAnyVal[Source <: AnyVal, Dest] extends Transformer[Source, Dest]
sealed trait FromAnyVal[Source, Dest] extends Transformer[Source, Dest]

object FromAnyVal {
@deprecated(message = "Use the variant with a Transformer instead", since = "0.1.1")
Expand All @@ -66,13 +163,13 @@ object Transformer {
def transform(from: Source): Dest = f(from)
}

private[ducktape] def make[Source <: AnyVal, Dest](transformer: Transformer[Source, Dest]): FromAnyVal[Source, Dest] =
private[ducktape] def make[Source, Dest](transformer: Transformer[Source, Dest]): FromAnyVal[Source, Dest] =
new {
def transform(from: Source): Dest = transformer.transform(from)
}
}

sealed trait ToAnyVal[Source, Dest <: AnyVal] extends Transformer[Source, Dest]
sealed trait ToAnyVal[Source, Dest] extends Transformer[Source, Dest]

object ToAnyVal {
@deprecated(message = "Use the variant with a Transformer instead", since = "0.1.1")
Expand All @@ -81,39 +178,9 @@ object Transformer {
def transform(from: Source): Dest = f(from)
}

private[ducktape] def make[Source, Dest <: AnyVal](transformer: Transformer[Source, Dest]): ToAnyVal[Source, Dest] =
private[ducktape] def make[Source, Dest](transformer: Transformer[Source, Dest]): ToAnyVal[Source, Dest] =
new {
def transform(from: Source): Dest = transformer.transform(from)
}
}

given [Source, Dest >: Source]: Identity[Source, Dest] = Identity[Source, Dest]

inline given forProducts[Source, Dest](using Mirror.ProductOf[Source], Mirror.ProductOf[Dest]): ForProduct[Source, Dest] =
ForProduct.make(DerivedTransformers.product[Source, Dest])

inline given forCoproducts[Source, Dest](using Mirror.SumOf[Source], Mirror.SumOf[Dest]): ForCoproduct[Source, Dest] =
ForCoproduct.make(DerivedTransformers.coproduct[Source, Dest])

given [Source, Dest](using Transformer[Source, Dest]): Transformer[Source, Option[Dest]] =
from => Transformer[Source, Dest].transform.andThen(Some.apply)(from)

given [Source, Dest](using Transformer[Source, Dest]): Transformer[Option[Source], Option[Dest]] =
from => from.map(Transformer[Source, Dest].transform)

given [A1, A2, B1, B2](using Transformer[A1, A2], Transformer[B1, B2]): Transformer[Either[A1, B1], Either[A2, B2]] = {
case Right(value) => Right(Transformer[B1, B2].transform(value))
case Left(value) => Left(Transformer[A1, A2].transform(value))
}

given [Source, Dest, SourceCollection[elem] <: Iterable[elem], DestCollection[elem] <: Iterable[elem]](using
trans: Transformer[Source, Dest],
factory: Factory[Dest, DestCollection[Dest]]
): Transformer[SourceCollection[Source], DestCollection[Dest]] = from => from.map(trans.transform).to(factory)

inline given fromAnyVal[Source <: AnyVal, Dest]: FromAnyVal[Source, Dest] =
FromAnyVal.make(DerivedTransformers.fromAnyVal[Source, Dest])

inline given toAnyVal[Source, Dest <: AnyVal]: ToAnyVal[Source, Dest] =
ToAnyVal.make(DerivedTransformers.toAnyVal[Source, Dest])
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ private[ducktape] object DerivedTransformers {
)(using Quotes): Expr[Transformer[Source, Dest]] =
'{ source => ${ CoproductTransformations.transform[Source, Dest]('source, Source, Dest) } }

inline def toAnyVal[Source, Dest <: AnyVal]: Transformer[Source, Dest] =
inline def toAnyVal[Source, Dest]: Transformer[Source, Dest] =
${ deriveToAnyValTransformerMacro[Source, Dest] }

def deriveToAnyValTransformerMacro[Source: Type, Dest <: AnyVal: Type](using Quotes): Expr[Transformer[Source, Dest]] =
def deriveToAnyValTransformerMacro[Source: Type, Dest: Type](using Quotes): Expr[Transformer[Source, Dest]] =
'{ source => ${ ProductTransformations.transformToAnyVal('source) } }

inline def fromAnyVal[Source <: AnyVal, Dest] =
inline def fromAnyVal[Source, Dest] =
${ deriveFromAnyValTransformerMacro[Source, Dest] }

def deriveFromAnyValTransformerMacro[Source <: AnyVal: Type, Dest: Type](using Quotes): Expr[Transformer[Source, Dest]] =
def deriveFromAnyValTransformerMacro[Source: Type, Dest: Type](using Quotes): Expr[Transformer[Source, Dest]] =
'{ source => ${ ProductTransformations.transformFromAnyVal('source) } }
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ private[ducktape] object LiftTransformation {
appliedTo: Expr[A]
)(using Quotes): Option[Expr[B]] =
PartialFunction.condOpt(transformer) {
case '{ Transformer.given_Transformer_Source_Option[source, dest](using $transformer) } =>
case '{ Transformer.betweenNonOptionOption[source, dest](using $transformer) } =>
val field = appliedTo.asExprOf[source]
val lifted = liftTransformation(transformer, field)
'{ Some($lifted) }.asExprOf[B]

case '{ Transformer.given_Transformer_Option_Option[source, dest](using $transformer) } =>
case '{ Transformer.betweenOptions[source, dest](using $transformer) } =>
val field = appliedTo.asExprOf[ 9E81 Option[source]]
'{ $field.map(src => ${ liftTransformation(transformer, 'src) }) }.asExprOf[B]

Expand All @@ -50,7 +50,7 @@ private[ducktape] object LiftTransformation {
// https://github.com/lampepfl/dotty/discussions/12446
// Because of that we need to do some more shenanigans to get the exact collection type we transform into
case '{
Transformer.given_Transformer_SourceCollection_DestCollection[
Transformer.betweenCollections[
source,
dest,
Iterable,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ private[ducktape] object ProductTransformations {
.asExprOf[Dest]
}

def transformFromAnyVal[Source <: AnyVal: Type, Dest: Type](
def transformFromAnyVal[Source: Type, Dest: Type](
sourceValue: Expr[Source]
)(using Quotes): Expr[Dest] = {
import quotes.reflect.*
Expand All @@ -119,7 +119,7 @@ private[ducktape] object ProductTransformations {
accessField(sourceValue, fieldSymbol.name).asExprOf[Dest]
}

def transformToAnyVal[Source: Type, Dest <: AnyVal: Type](
def transformToAnyVal[Source: Type, Dest: Type](
sourceValue: Expr[Source]
)(using Quotes): Expr[Dest] = {
import quotes.reflect.*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.github.arainko.ducktape.issues

import io.github.arainko.ducktape.*

// https://github.com/arainko/ducktape/issues/37
class Issue37Spec extends DucktapeSuite {
final case class Rec[A](value: A, rec: Option[Rec[A]])

given rec[A, B](using Transformer[A, B]): Transformer[Rec[A], Rec[B]] = Transformer.define[Rec[A], Rec[B]].build()

test("value class transformers don't interfere with primitives") {
val actual = rec[Int, Option[Int]].transform(Rec(1, Some(Rec(2, None))))
val expected: Rec[Option[Int]] = Rec(Some(1), Some(Rec(Some(2), None)))

assertEquals(actual, expected)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Issue41Spec extends DucktapeSuite {
final case class Inside2(str: String)

val roughAstCount = AstInstanceCounter.roughlyCount[Transformer[?, ?]](summon[Transformer[TestClass, TestClass2]])
assert(roughAstCount == 10)
assert(clue(roughAstCount) == 10)
}

test("Nested transformers are optimized away when case class' companion has vals inside") {
Expand Down Expand Up @@ -45,6 +45,6 @@ class Issue41Spec extends DucktapeSuite {
val roughAstCount =
AstInstanceCounter.roughlyCount[Transformer[?, ?]](summon[Transformer[TestClass, TestClass2]])

assert(roughAstCount == 10)
assert(clue(roughAstCount) == 10)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import scala.quoted.*
class MakeTransformerSpec extends DucktapeSuite {

test("should match ForProduct.make") {
MakeTransformerChecker.check(Transformer.forProducts[ComplexPerson, PrimitivePerson])
MakeTransformerChecker.check(Transformer.betweenProducts[ComplexPerson, PrimitivePerson])
}

test("should match FromAnyVal.make") {
MakeTransformerChecker.check(Transformer.fromAnyVal[Hobby, String])
MakeTransformerChecker.check(Transformer.betweenWrappedUnwrapped[Hobby, String])
}

test("should match ToAnyVal.make") {
MakeTransformerChecker.check(Transformer.toAnyVal[String, Hobby])
MakeTransformerChecker.check(Transformer.betweenUnwrappedWrapped[String, Hobby])
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import io.github.arainko.ducktape.model.*

class TransformerLambdaSpec extends DucktapeSuite {
test("should match ForProduct") {
TransformerLambdaChecker.check[Transformer.ForProduct.type](Transformer.forProducts[ComplexPerson, PrimitivePerson])
TransformerLambdaChecker.check[Transformer.ForProduct.type](Transformer.betweenProducts[ComplexPerson, PrimitivePerson])
}

test("should match FromAnyVal") {
TransformerLambdaChecker
.check[Transformer.FromAnyVal.type](Transformer.fromAnyVal[Hobby, String])
.check[Transformer.FromAnyVal.type](Transformer.betweenWrappedUnwrapped[Hobby, String])
}

test("should match ToAnyVal") {
TransformerLambdaChecker.check[Transformer.ToAnyVal.type](Transformer.toAnyVal[String, Hobby])
TransformerLambdaChecker.check[Transformer.ToAnyVal.type](Transformer.betweenUnwrappedWrapped[String, Hobby])
}
}
0