diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/Field.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/Field.scala index 0e0f23e1..7257c848 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/Field.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/Field.scala @@ -26,6 +26,8 @@ object Field { @compileTimeOnly("Field.default is only useable as a field configuration for transformations") def default[A, B, FieldType](selector: Selector ?=> B => FieldType): Field[A, B] = ??? + def useNones[A, B, FieldType](selector: Selector ?=> B => FieldType): Field[A, B] = ??? + @compileTimeOnly("Field.allMatching is only useable as a field configuration for transformations") def allMatching[A, B, DestFieldTpe, ProductTpe](selector: Selector ?=> B => DestFieldTpe, product: ProductTpe): Field[A, B] = ??? diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/Playground.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/Playground.scala new file mode 100644 index 00000000..19690367 --- /dev/null +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/Playground.scala @@ -0,0 +1,21 @@ +package io.github.arainko.ducktape + +final case class SourceToplevel(level1: SourceLevel1) +final case class SourceLevel1(str: String) + +final case class DestToplevel(extra: Option[Int], level1: DestLevel1) +final case class DestLevel1(extra: Option[Int], str: String) + +object Playground extends App { + val source = SourceToplevel(SourceLevel1("str")) + + val dest = + source + .into[DestToplevel] + .transform( + Field.const(_.extra, Some(1)), + Field.useNones(a => a.level1) + ) + + println(dest) +} diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/Configuration.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/Configuration.scala index d2c0f86b..64100e4b 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/Configuration.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/Configuration.scala @@ -15,12 +15,22 @@ private[ducktape] enum Configuration derives Debug { private[ducktape] object Configuration { + trait Modifier { + def apply(plan: Plan.Error)(using Quotes): Configuration | plan.type + } + + object Modifier { + given Debug[Modifier] = new: + extension (self: Modifier) def show(using Quotes): String = "Modifier(...)" + } + enum At derives Debug { def path: Path def target: Target def span: Span case Successful(path: Path, target: Target, config: Configuration, span: Span) + case Regional(path: Path, target: Target, modifier: Modifier, span: Span) case Failed(path: Path, target: Target, message: String, span: Span) } @@ -120,6 +130,18 @@ private[ducktape] object Configuration { Span.fromPosition(cfg.pos) ) :: Nil + case cfg @ Apply( + TypeApply(Select(IdentOfType('[Field.type]), "useNones"), a :: b :: destFieldTpe :: Nil), + PathSelector(path) :: Nil + ) => + val modifier: Modifier = new: + def apply(plan: Plan.Error)(using Quotes): Configuration | plan.type = + plan.destTpe match { + case tpe @ '[Option[a]] => Configuration.Const('{ None }, tpe) + case _ => plan + } + Configuration.At.Regional(path, Target.Dest, modifier, Span.fromPosition(cfg.pos)) :: Nil + case DeprecatedConfig(configs) => configs case oopsie => diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/Logger.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/Logger.scala index 019bedb6..469c364c 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/Logger.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/Logger.scala @@ -8,7 +8,7 @@ import scala.quoted.* private[ducktape] object Logger { // Logger Config - private[ducktape] transparent inline given level: Level = Level.Off + private[ducktape] transparent inline given level: Level = Level.Info private val output = Output.StdOut private def filter(msg: String, meta: Metainformation) = true diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/Plan.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/Plan.scala index 6899968c..443f153d 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/Plan.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/Plan.scala @@ -6,9 +6,7 @@ import io.github.arainko.ducktape.internal.* import scala.collection.immutable.ListMap import scala.quoted.* -private[ducktape] type PlanError = Plan.Error - -private[ducktape] enum Plan[+E <: PlanError] { +private[ducktape] sealed trait Plan[+E <: Plan.Error] { import Plan.* def sourceTpe: Type[?] @@ -22,15 +20,17 @@ private[ducktape] enum Plan[+E <: PlanError] { final def configureAll(configs: List[Configuration.At])(using Quotes): Plan.Reconfigured = PlanConfigurer.run(this, configs) final def refine: Either[NonEmptyList[Plan.Error], Plan[Nothing]] = PlanRefiner.run(this) +} - case Upcast( +private[ducktape] object Plan { + case class Upcast( sourceTpe: Type[?], destTpe: Type[?], sourceContext: Path, destContext: Path ) extends Plan[Nothing] - case UserDefined( + case class UserDefined( sourceTpe: Type[?], destTpe: Type[?], sourceContext: Path, @@ -38,7 +38,7 @@ private[ducktape] enum Plan[+E <: PlanError] { transformer: Expr[Transformer[?, ?]] ) extends Plan[Nothing] - case Derived( + case class Derived( sourceTpe: Type[?], destTpe: Type[?], sourceContext: Path, @@ -46,7 +46,7 @@ private[ducktape] enum Plan[+E <: PlanError] { transformer: Expr[Transformer.Derived[?, ?]] ) extends Plan[Nothing] - case Configured( + case class Configured( sourceTpe: Type[?], destTpe: Type[?], sourceContext: Path, @@ -54,7 +54,7 @@ private[ducktape] enum Plan[+E <: PlanError] { config: Configuration ) extends Plan[Nothing] - case BetweenProductFunction( + case class BetweenProductFunction[+E <: Plan.Error]( sourceTpe: Type[?], destTpe: Type[?], sourceContext: Path, @@ -63,14 +63,14 @@ private[ducktape] enum Plan[+E <: PlanError] { function: Function ) extends Plan[E] - case BetweenUnwrappedWrapped( + case class BetweenUnwrappedWrapped( sourceTpe: Type[?], destTpe: Type[?], sourceContext: Path, destContext: Path ) extends Plan[Nothing] - case BetweenWrappedUnwrapped( + case class BetweenWrappedUnwrapped( sourceTpe: Type[?], destTpe: Type[?], sourceContext: Path, @@ -78,7 +78,7 @@ private[ducktape] enum Plan[+E <: PlanError] { fieldName: String ) extends Plan[Nothing] - case BetweenSingletons( + case class BetweenSingletons( sourceTpe: Type[?], destTpe: Type[?], sourceContext: Path, @@ -86,7 +86,7 @@ private[ducktape] enum Plan[+E <: PlanError] { expr: Expr[Any] ) extends Plan[Nothing] - case BetweenProducts( + case class BetweenProducts[+E <: Plan.Error]( sourceTpe: Type[?], destTpe: Type[?], sourceContext: Path, @@ -94,7 +94,7 @@ private[ducktape] enum Plan[+E <: PlanError] { fieldPlans: Map[String, Plan[E]] ) extends Plan[E] - case BetweenCoproducts( + case class BetweenCoproducts[+E <: Plan.Error]( sourceTpe: Type[?], destTpe: Type[?], sourceContext: Path, @@ -102,7 +102,7 @@ private[ducktape] enum Plan[+E <: PlanError] { casePlans: Vector[Plan[E]] ) extends Plan[E] - case BetweenOptions( + case class BetweenOptions[+E <: Plan.Error]( sourceTpe: Type[?], destTpe: Type[?], sourceContext: Path, @@ -110,7 +110,7 @@ private[ducktape] enum Plan[+E <: PlanError] { plan: Plan[E] ) extends Plan[E] - case BetweenNonOptionOption( + case class BetweenNonOptionOption[+E <: Plan.Error]( sourceTpe: Type[?], destTpe: Type[?], sourceContext: Path, @@ -118,7 +118,7 @@ private[ducktape] enum Plan[+E <: PlanError] { plan: Plan[E] ) extends Plan[E] - case BetweenCollections( + case class BetweenCollections[+E <: Plan.Error]( destCollectionTpe: Type[? <: Iterable[?]], sourceTpe: Type[?], destTpe: Type[?], @@ -127,7 +127,7 @@ private[ducktape] enum Plan[+E <: PlanError] { plan: Plan[E] ) extends Plan[E] - case Error( + case class Error( sourceTpe: Type[?], destTpe: Type[?], sourceContext: Path, @@ -135,9 +135,6 @@ private[ducktape] enum Plan[+E <: PlanError] { message: ErrorMessage, suppressed: Option[Plan.Error] ) extends Plan[Plan.Error] -} - -private[ducktape] object Plan { object Error { def from(plan: Plan[Plan.Error], message: ErrorMessage, suppressed: Option[Plan.Error]): Plan.Error = diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/PlanConfigurer.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/PlanConfigurer.scala index 5af362d9..5aa3b5dd 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/PlanConfigurer.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/PlanConfigurer.scala @@ -113,6 +113,9 @@ private[ducktape] object PlanConfigurer { ) .tap(errors.addOne) + case cfg @ Configuration.At.Regional(path, target, modifier, span) => + regional(current, modifier) + case cfg @ Configuration.At.Failed(path, target, message, span) => Plan.Error.from(current, ErrorMessage.ConfigurationFailed(cfg), None).tap(errors.addOne) } @@ -125,4 +128,51 @@ private[ducktape] object PlanConfigurer { case other => Plan.Error.from(plan, ErrorMessage.InvalidPathSegment(segment, config.target, config.span), None) } + + def regional( + plan: Plan[Plan.Error], + modifier: Configuration.Modifier + )(using Quotes): Plan[Plan.Error] = + plan match { + case plan: Upcast => plan + + case plan: UserDefined => plan + + case plan: Derived => plan + + case plan: Configured => plan + + case plan: BetweenProductFunction[Plan.Error] => + plan.copy(argPlans = plan.argPlans.transform((_, plan) => regional(plan, modifier))) + + case plan: BetweenUnwrappedWrapped => plan + + case plan: BetweenWrappedUnwrapped => plan + + case plan: BetweenSingletons => plan + + case plan: BetweenProducts[Plan.Error] => + plan.copy(fieldPlans = plan.fieldPlans.transform((_, plan) => regional(plan, modifier))) + + case plan: BetweenCoproducts[Plan.Error] => + plan.copy(casePlans = plan.casePlans.map(regional(_, modifier))) + + case plan: BetweenOptions[Plan.Error] => + plan.copy(plan = regional(plan.plan, modifier)) + + case plan: BetweenNonOptionOption[Plan.Error] => + plan.copy(plan = regional(plan.plan, modifier)) + + case plan: BetweenCollections[Plan.Error] => + plan.copy(plan = regional(plan.plan, modifier)) + + case plan: Error => + modifier(plan) match { + case config: Configuration => + Plan.Configured(plan.sourceTpe, plan.destTpe, plan.sourceContext, plan.destContext, config) + case other: plan.type => + other + } + } + } diff --git a/tooling/src/main/scala/io/github/arainko/tooling/Debug.scala b/tooling/src/main/scala/io/github/arainko/tooling/Debug.scala index d7cc4613..60af1dde 100644 --- a/tooling/src/main/scala/io/github/arainko/tooling/Debug.scala +++ b/tooling/src/main/scala/io/github/arainko/tooling/Debug.scala @@ -76,10 +76,6 @@ private[ducktape] object Debug { s"Deferred(...)" } - given function: Debug[Nothing => Any] with { - extension (self: Nothing => Any) def show(using Quotes): String = s"Function(...)" - } - given expr[A]: Debug[Expr[A]] with { extension (value: Expr[A]) def show(using Quotes): String = { import quotes.reflect.*