diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/Interpreter.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/Interpreter.scala index 16d86e74..1c664dd3 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/Interpreter.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/Interpreter.scala @@ -106,7 +106,7 @@ object Interpreter { case Plan.BetweenUnwrappedWrapped(sourceTpe, destTpe) => Constructor(destTpe.repr).appliedTo(value.asTerm).asExpr - + case Plan.UserDefined(source, dest, transformer) => transformer match { case '{ $t: UserDefinedTransformer[src, dest] } => diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/PathMatcher.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/PathMatcher.scala new file mode 100644 index 00000000..e063be29 --- /dev/null +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/PathMatcher.scala @@ -0,0 +1,37 @@ +package io.github.arainko.ducktape + +import scala.quoted.* +import io.github.arainko.ducktape.internal.macros.DebugMacros +import scala.collection.mutable.ListBuffer +import scala.annotation.tailrec + +object PathMatcher { + // inline def run[A](inline expr: Selector ?=> A => Any) = ${ readPath('expr) } + + def readPath[A: Type](expr: Expr[Selector ?=> A => Any])(using Quotes): List[String | Type[?]] = { + import quotes.reflect.{ Selector as _, * } + + @tailrec + def recurse(using Quotes)(acc: List[String | Type[?]], term: quotes.reflect.Term): List[String | Type[?]] = { + import quotes.reflect.* + + given Printer[TypeRepr] = Printer.TypeReprShortCode + + term match { + case Inlined(_, _, tree) => + recurse(acc, tree) + case Lambda(_, tree) => + recurse(acc, tree) + case Block(_, tree) => + recurse(acc, tree) + case Select(tree, name) => + recurse(name :: acc, tree) + case TypeApply(Apply(TypeApply(Select(Ident(_), "at"), _), tree :: Nil), tpe :: Nil) => + recurse(tpe.tpe.asType :: acc, tree) + case Ident(_) => acc + case other => report.errorAndAbort(other.show(using Printer.TreeShortCode)) + } + } + recurse(Nil, expr.asTerm) + } +} diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/Plan.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/Plan.scala index c3cc1c44..6d3272d2 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/Plan.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/Plan.scala @@ -37,9 +37,9 @@ enum Plan[+E <: PlanError] { s"UserDefined(${sourceTpe.show}, ${destTpe.show}, Transformer[...])" case PlanError(sourceTpe, destTpe, context, message) => s"Error(${sourceTpe.show}, ${destTpe.show}, ${context.render}, ${message})" - case BetweenUnwrappedWrapped(sourceTpe, destTpe) => + case BetweenUnwrappedWrapped(sourceTpe, destTpe) => s"BetweenUnwrappedWrapped(${sourceTpe.show}, ${destTpe.show})" - case BetweenWrappedUnwrapped(sourceTpe, destTpe, fieldName) => + case BetweenWrappedUnwrapped(sourceTpe, destTpe, fieldName) => s"BetweenWrappedUnwrapped(${sourceTpe.show}, ${destTpe.show}, $fieldName)" } } @@ -78,15 +78,15 @@ enum Plan[+E <: PlanError] { } case Upcast(sourceTpe: Type[?], destTpe: Type[?]) extends Plan[Nothing] + case UserDefined(sourceTpe: Type[?], destTpe: Type[?], transformer: Expr[UserDefinedTransformer[?, ?]]) extends Plan[Nothing] + case BetweenUnwrappedWrapped(sourceTpe: Type[?], destTpe: Type[?]) extends Plan[Nothing] + case BetweenWrappedUnwrapped(sourceTpe: Type[?], destTpe: Type[?], fieldName: String) extends Plan[Nothing] + case BetweenSingletons(sourceTpe: Type[?], destTpe: Type[?], expr: Expr[Any]) extends Plan[Nothing] case BetweenProducts(sourceTpe: Type[?], destTpe: Type[?], fieldPlans: Map[String, Plan[E]]) extends Plan[E] case BetweenCoproducts(sourceTpe: Type[?], destTpe: Type[?], casePlans: Map[String, Plan[E]]) extends Plan[E] case BetweenOptions(sourceTpe: Type[?], destTpe: Type[?], plan: Plan[E]) extends Plan[E] case BetweenNonOptionOption(sourceTpe: Type[?], destTpe: Type[?], plan: Plan[E]) extends Plan[E] case BetweenCollections(destCollectionTpe: Type[?], sourceTpe: Type[?], destTpe: Type[?], plan: Plan[E]) extends Plan[E] - case BetweenSingletons(sourceTpe: Type[?], destTpe: Type[?], expr: Expr[Any]) extends Plan[Nothing] - case UserDefined(sourceTpe: Type[?], destTpe: Type[?], transformer: Expr[UserDefinedTransformer[?, ?]]) extends Plan[Nothing] - case BetweenUnwrappedWrapped(sourceTpe: Type[?], destTpe: Type[?]) extends Plan[Nothing] - case BetweenWrappedUnwrapped(sourceTpe: Type[?], destTpe: Type[?], fieldName: String) extends Plan[Nothing] // TODO: Use a well typed error definition here and not a String case Error(sourceTpe: Type[?], destTpe: Type[?], context: Plan.Context, message: String) extends Plan[Plan.Error] } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/Planner.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/Planner.scala index 90d4284a..a744374a 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/Planner.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/Planner.scala @@ -2,6 +2,7 @@ package io.github.arainko.ducktape import scala.quoted.* import io.github.arainko.ducktape.internal.modules.* +import io.github.arainko.ducktape.Plan.Context object Planner { import Structure.* @@ -10,17 +11,19 @@ object Planner { def printMacro[A: Type, B: Type](using Quotes): Expr[Unit] = { val plan = createPlan[A, B] - quotes.reflect.report.info(plan.toString()) + quotes.reflect.report.info(plan.show) '{} } - def createPlan[Source: Type, Dest: Type](using Quotes): Plan[Plan.Error] = recurseAndCreatePlan[Source, Dest](Plan.Context(Type[Source], Type[Dest], Vector.empty)) + def createPlan[Source: Type, Dest: Type](using Quotes): Plan[Plan.Error] = + recurseAndCreatePlan[Source, Dest](Plan.Context(Type[Source], Type[Dest], Vector.empty)) private def recurseAndCreatePlan[Source: Type, Dest: Type](context: Plan.Context)(using Quotes): Plan[Plan.Error] = { val src = Structure.of[Source] val dest = Structure.of[Dest] val plan = recurse(src, dest, context) - val updated = plan.updateAt(Type.of[Sum1.Leaf2] :: "duspko" :: Nil)(plan => Plan.BetweenSingletons(Type.of[Int], Type.of[Int], '{ 123 })) + val updated = + plan.updateAt(Type.of[Sum1.Leaf2] :: "duspko" :: Nil)(plan => Plan.BetweenSingletons(Type.of[Int], Type.of[Int], '{ 123 })) // println(updated.get.show) println() println(plan.show) @@ -47,23 +50,36 @@ object Planner { case (source: Product, dest: Product) => val fieldPlans = dest.fields.map { (destField, destFieldStruct) => - val plan = + val plan = source.fields .get(destField) .map(recurse(_, destFieldStruct, context.add(destField))) - .getOrElse(Plan.Error(source.tpe, dest.tpe, context.add(destField), s"No field named '$destField' found in ${source.tpe.repr.show}")) + .getOrElse( + Plan.Error( + source.tpe, + dest.tpe, + context.add(destField), + s"No field named '$destField' found in ${source.tpe.repr.show}" + ) + ) destField -> plan } Plan.BetweenProducts(source.tpe, dest.tpe, fieldPlans) case (source: Coproduct, dest: Coproduct) => val casePlans = source.children.map { (sourceName, sourceCaseStruct) => - val plan = - dest - .children + val plan = + dest.children .get(sourceName) .map(recurse(sourceCaseStruct, _, context.add(sourceCaseStruct.tpe))) - .getOrElse(Plan.Error(source.tpe, dest.tpe, context.add(sourceCaseStruct.tpe), s"No child named '$sourceName' found in ${dest.tpe.repr.show}")) + .getOrElse( + Plan.Error( + source.tpe, + dest.tpe, + context.add(sourceCaseStruct.tpe), + s"No child named '$sourceName' found in ${dest.tpe.repr.show}" + ) + ) sourceName -> plan } Plan.BetweenCoproducts(source.tpe, dest.tpe, casePlans) @@ -71,14 +87,19 @@ object Planner { case (source: Structure.Singleton, dest: Structure.Singleton) if source.name == dest.name => Plan.BetweenSingletons(source.tpe, dest.tpe, dest.value) - case (source: ValueClass, dest) if source.paramTpe.repr <:< dest.tpe.repr => + case (source: ValueClass, dest) if source.paramTpe.repr <:< dest.tpe.repr => Plan.BetweenWrappedUnwrapped(source.tpe, dest.tpe, source.paramFieldName) - case (source, dest: ValueClass) if source.tpe.repr <:< dest.paramTpe.repr => + case (source, dest: ValueClass) if source.tpe.repr <:< dest.paramTpe.repr => Plan.BetweenUnwrappedWrapped(source.tpe, dest.tpe) - case (source, dest) => - Plan.Error(source.tpe, dest.tpe, context, s"Couldn't build a transformation plan between ${source.tpe.repr.show} and ${dest.tpe.repr.show}") + case (source, dest) => + Plan.Error( + source.tpe, + dest.tpe, + context, + s"Couldn't build a transformation plan between ${source.tpe.repr.show} and ${dest.tpe.repr.show}" + ) } object UserDefinedTransformation { diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/Playground.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/Playground.scala index a11e3734..76be3a71 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/Playground.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/Playground.scala @@ -1,5 +1,8 @@ package io.github.arainko.ducktape +import io.github.arainko.ducktape.internal.macros.DebugMacros +import scala.annotation.nowarn + final case class Value(int: Int) extends AnyVal final case class ValueGen[A](int: A) extends AnyVal @@ -18,8 +21,40 @@ enum Sum2 { case Single } +trait Selector { + extension [A](self: A) def at[B <: A]: B +} + @main def main = { + def costam[A](selectors: (Selector ?=> A => Any)*) = ??? + + val sel: Selector = ??? + + // Inlined( + // None, + // Nil, + // TypeApply( + // Apply(TypeApply(Select(Ident("sel"), "at"), List(TypeIdent("Sum1"))), List(Select(Ident("Sum1"), "Single"))), + // List(TypeSelect(Ident("Sum1"), "Leaf1")) + // ) + // ) + + // val evidence$1 = + // DebugMacros.structure( + // sel.at[Sum1](Sum1.Single)[Sum1.Leaf1] + // ) + + + PathMatcher.run[Sum1](_.at[Sum1].at[Sum1.Single.type]) + + // DebugMacros.code { + // costam[Sum1]( + // _.at[Sum1.Leaf1].int.at[Int].toByte.toByte.toByte.at[Byte] + // // _.at[Sum1.Leaf2].str, + // ) + // } + final case class HKT[F[_]](value: F[Int]) final case class Person1(int: Int, str: String, opt: List[Nested1]) @@ -32,12 +67,11 @@ enum Sum2 { val p = Person1(1, "asd", Nested1(1) :: Nil) // Planner.print[Person1, Person2] - // Planner.print[Person1, Person2] // Transformer.Debug.showCode { - Interpreter.transformPlanned[Person1, Person2](p) + // Interpreter.transformPlanned[Person1, Person2](p) // } }