From b8de7120c6da25a0c7bf84ebff6ca795f9b97078 Mon Sep 17 00:00:00 2001 From: Mateusz Kubuszok Date: Wed, 15 Mar 2023 19:30:03 +0100 Subject: [PATCH] The first draft of an elternative structure for macros towards which current code could be migrated, one rule by one --- .../ConfigurationDefinitionsPlatform.scala | 153 +++++++++ .../compiletime/DefinitionsPlatform.scala | 185 ++++++++++ .../ResultDefinitionsPlatform.scala | 26 ++ .../DerivationDefinitionsPlatform.scala | 17 + .../DerivationGatewayPlatform.scala | 42 +++ .../ConfigurationDefinitions.scala | 144 ++++++++ .../internal/compiletime/Definitions.scala | 268 +++++++++++++++ .../compiletime/ResultDefinitions.scala | 324 ++++++++++++++++++ .../derivation/DerivationDefinitions.scala | 91 +++++ .../derivation/DerivationGateway.scala | 58 ++++ .../compiletime/dsl/DslDefinitions.scala | 5 + 11 files changed, 1313 insertions(+) create mode 100644 chimney/src/main/scala-2/io/scalaland/chimney/internal/compiletime/ConfigurationDefinitionsPlatform.scala create mode 100644 chimney/src/main/scala-2/io/scalaland/chimney/internal/compiletime/DefinitionsPlatform.scala create mode 100644 chimney/src/main/scala-2/io/scalaland/chimney/internal/compiletime/ResultDefinitionsPlatform.scala create mode 100644 chimney/src/main/scala-2/io/scalaland/chimney/internal/compiletime/derivation/DerivationDefinitionsPlatform.scala create mode 100644 chimney/src/main/scala-2/io/scalaland/chimney/internal/compiletime/derivation/DerivationGatewayPlatform.scala create mode 100644 chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/ConfigurationDefinitions.scala create mode 100644 chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/Definitions.scala create mode 100644 chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/ResultDefinitions.scala create mode 100644 chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/derivation/DerivationDefinitions.scala create mode 100644 chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/derivation/DerivationGateway.scala create mode 100644 chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/dsl/DslDefinitions.scala diff --git a/chimney/src/main/scala-2/io/scalaland/chimney/internal/compiletime/ConfigurationDefinitionsPlatform.scala b/chimney/src/main/scala-2/io/scalaland/chimney/internal/compiletime/ConfigurationDefinitionsPlatform.scala new file mode 100644 index 000000000..c1174ab4a --- /dev/null +++ b/chimney/src/main/scala-2/io/scalaland/chimney/internal/compiletime/ConfigurationDefinitionsPlatform.scala @@ -0,0 +1,153 @@ +package io.scalaland.chimney.internal.compiletime + +import io.scalaland.chimney.dsl as dsls +import io.scalaland.chimney.internal + +private[compiletime] trait ConfigurationDefinitionsPlatform extends ConfigurationDefinitions { + this: DefinitionsPlatform => + + import DefinitionsPlatform.* + import c.universe.{internal as _, Transformer as _, *} + + final protected def readConfigPlatform[ + Cfg <: internal.TransformerCfg: WeakTypeTag, + InstanceFlags <: internal.TransformerFlags: WeakTypeTag, + ScopeFlags <: internal.TransformerFlags: WeakTypeTag + ]: TransformerConfig = { + implicit val Ctg: Type[Cfg] = typeImpl.fromWeak[Cfg] + implicit val InstanceFlags: Type[InstanceFlags] = typeImpl.fromWeak[InstanceFlags] + implicit val ScopeFlags: Type[ScopeFlags] = typeImpl.fromWeak[ScopeFlags] + readConfig[Cfg, InstanceFlags, ScopeFlags] + } + + final override protected def readConfig[ + Cfg <: internal.TransformerCfg: Type, + InstanceFlags <: internal.TransformerFlags: Type, + SharedFlags <: internal.TransformerFlags: Type + ]: TransformerConfig = { + val sharedFlags = extractTransformerFlags[SharedFlags](TransformerFlags()) + val allFlags = extractTransformerFlags[InstanceFlags](sharedFlags) + extractTransformerConfig[Cfg](runtimeDataIdx = 0).copy(flags = allFlags) + } + + protected[this] type FlagHead <: internal.TransformerFlags.Flag + protected[this] type FlagTail <: internal.TransformerFlags + private val enableTC = typeOf[internal.TransformerFlags.Enable[?, ?]].typeConstructor + private val disableTC = typeOf[internal.TransformerFlags.Disable[?, ?]].typeConstructor + private val implicitConflictResolutionTC = + typeOf[internal.TransformerFlags.ImplicitConflictResolution[?]].typeConstructor + // TODO: this coule be tailrec + private def extractTransformerFlags[Flag <: internal.TransformerFlags: Type]( + defaultFlags: TransformerFlags + ): TransformerFlags = { + val flags = Type[Flag].dealias + + if (flags =:= Type.TransformerFlags.Default) { + defaultFlags + } else if (flags.typeConstructor =:= enableTC) { + val List(h, t) = flags.typeArgs + implicit val Flag: Type[FlagHead] = typeImpl.fromUntyped(h) + implicit val Tail: Type[FlagTail] = typeImpl.fromUntyped(t) + + if (Flag.typeConstructor =:= implicitConflictResolutionTC) { + val preference = Flag.typeArgs.head + if (preference =:= Type.PreferTotalTransformer) { + extractTransformerFlags[FlagTail](defaultFlags).setImplicitConflictResolution( + Some(dsls.PreferTotalTransformer) + ) + } else if (preference =:= Type.PreferPartialTransformer) { + extractTransformerFlags[FlagTail](defaultFlags).setImplicitConflictResolution( + Some(dsls.PreferPartialTransformer) + ) + } else { + // $COVERAGE-OFF$ + c.abort(c.enclosingPosition, "Invalid implicit conflict resolution preference type!!") + // $COVERAGE-ON$ + } + } else { + extractTransformerFlags[FlagTail](defaultFlags).setBoolFlag[FlagHead](value = true) + } + } else if (flags.typeConstructor =:= disableTC) { + val List(h, t) = flags.typeArgs + implicit val Flag: Type[FlagHead] = typeImpl.fromUntyped(h) + implicit val Tail: Type[FlagTail] = typeImpl.fromUntyped(t) + + if (flags.typeConstructor =:= implicitConflictResolutionTC) { + extractTransformerFlags[FlagTail](defaultFlags).setImplicitConflictResolution(None) + } else { + extractTransformerFlags[FlagTail](defaultFlags).setBoolFlag[FlagHead](value = false) + } + } else { + // $COVERAGE-OFF$ + c.abort(c.enclosingPosition, "Bad internal transformer flags type shape!") + // $COVERAGE-ON$ + } + } + + protected[this] type CfgTail <: internal.TransformerCfg + private val emptyT = typeOf[internal.TransformerCfg.Empty] + private val fieldConstTC = typeOf[internal.TransformerCfg.FieldConst[?, ?]].typeConstructor + private val fieldConstPartialTC = typeOf[internal.TransformerCfg.FieldConstPartial[?, ?]].typeConstructor + private val fieldComputedTC = typeOf[internal.TransformerCfg.FieldComputed[?, ?]].typeConstructor + private val fieldComputedPartialTC = typeOf[internal.TransformerCfg.FieldComputedPartial[?, ?]].typeConstructor + private val fieldRelabelledTC = typeOf[internal.TransformerCfg.FieldRelabelled[?, ?, ?]].typeConstructor + private val coproductInstanceTC = typeOf[internal.TransformerCfg.CoproductInstance[?, ?, ?]].typeConstructor + private val coproductInstancePartialTC = + typeOf[internal.TransformerCfg.CoproductInstancePartial[?, ?, ?]].typeConstructor + // TODO: this coule be tailrec + private def extractTransformerConfig[Cfg <: internal.TransformerCfg: Type](runtimeDataIdx: Int): TransformerConfig = { + val cfgTpe = Type[Cfg].dealias + + if (cfgTpe =:= emptyT) { + TransformerConfig() + } else if (cfgTpe.typeConstructor =:= fieldConstTC) { + val List(fieldNameT, rest) = cfgTpe.typeArgs + val fieldName = fieldNameT.asStringSingletonType + implicit val CfgTail: Type[CfgTail] = typeImpl.fromUntyped(rest) + extractTransformerConfig[CfgTail](1 + runtimeDataIdx) + .fieldOverride(fieldName, FieldOverride.Const(runtimeDataIdx)) + } else if (cfgTpe.typeConstructor =:= fieldComputedTC) { + val List(fieldNameT, rest) = cfgTpe.typeArgs + val fieldName = fieldNameT.asStringSingletonType + implicit val CfgTail: Type[CfgTail] = typeImpl.fromUntyped(rest) + extractTransformerConfig[CfgTail](1 + runtimeDataIdx) + .fieldOverride(fieldName, FieldOverride.Computed(runtimeDataIdx)) + } else if (cfgTpe.typeConstructor =:= fieldRelabelledTC) { + val List(fieldNameFromT, fieldNameToT, rest) = cfgTpe.typeArgs + val fieldNameFrom = fieldNameFromT.asStringSingletonType + val fieldNameTo = fieldNameToT.asStringSingletonType + implicit val CfgTail: Type[CfgTail] = typeImpl.fromUntyped(rest) + extractTransformerConfig[CfgTail](runtimeDataIdx) + .fieldOverride(fieldNameTo, FieldOverride.RenamedFrom(fieldNameFrom)) + } else if (cfgTpe.typeConstructor =:= coproductInstanceTC) { + val List(instanceType, targetType, rest) = cfgTpe.typeArgs + implicit val From: Type[Arbitrary] = typeImpl.fromUntyped(instanceType) + implicit val To: Type[Arbitrary2] = typeImpl.fromUntyped(targetType) + implicit val CfgTail: Type[CfgTail] = typeImpl.fromUntyped(rest) + extractTransformerConfig[CfgTail](1 + runtimeDataIdx).coproductInstance[Arbitrary, Arbitrary2](runtimeDataIdx) + } else if (cfgTpe.typeConstructor =:= fieldConstPartialTC) { + val List(fieldNameT, rest) = cfgTpe.typeArgs + val fieldName = fieldNameT.asStringSingletonType + implicit val Tail: Type[CfgTail] = typeImpl.fromUntyped(rest) + extractTransformerConfig[CfgTail](1 + runtimeDataIdx) + .fieldOverride(fieldName, FieldOverride.ConstPartial(runtimeDataIdx)) + } else if (cfgTpe.typeConstructor =:= fieldComputedPartialTC) { + val List(fieldNameT, rest) = cfgTpe.typeArgs + val fieldName = fieldNameT.asStringSingletonType + implicit val Tail: Type[CfgTail] = typeImpl.fromUntyped(rest) + extractTransformerConfig[CfgTail](1 + runtimeDataIdx) + .fieldOverride(fieldName, FieldOverride.ComputedPartial(runtimeDataIdx)) + } else if (cfgTpe.typeConstructor =:= coproductInstancePartialTC) { + val List(instanceType, targetType, rest) = cfgTpe.typeArgs + implicit val From: Type[Arbitrary] = typeImpl.fromUntyped(instanceType) + implicit val To: Type[Arbitrary2] = typeImpl.fromUntyped(targetType) + implicit val Tail: Type[CfgTail] = typeImpl.fromUntyped(rest) + extractTransformerConfig[CfgTail](1 + runtimeDataIdx) + .coproductInstancePartial[Arbitrary, Arbitrary2](runtimeDataIdx) + } else { + // $COVERAGE-OFF$ + c.abort(c.enclosingPosition, "Bad internal transformer config type shape!") + // $COVERAGE-ON$ + } + } +} diff --git a/chimney/src/main/scala-2/io/scalaland/chimney/internal/compiletime/DefinitionsPlatform.scala b/chimney/src/main/scala-2/io/scalaland/chimney/internal/compiletime/DefinitionsPlatform.scala new file mode 100644 index 000000000..4b5d8ef39 --- /dev/null +++ b/chimney/src/main/scala-2/io/scalaland/chimney/internal/compiletime/DefinitionsPlatform.scala @@ -0,0 +1,185 @@ +package io.scalaland.chimney.internal.compiletime + +import io.scalaland.chimney.dsl as dsls +import io.scalaland.chimney.internal +import io.scalaland.chimney.{partial, PartialTransformer, Patcher, Transformer} + +import scala.reflect.macros.blackbox + +private[compiletime] trait DefinitionsPlatform + extends Definitions + with ConfigurationDefinitionsPlatform + with ResultDefinitionsPlatform { + + val c: blackbox.Context + + import DefinitionsPlatform.* + import c.universe.{internal as _, Transformer as _, *} + + type Tagged[U] = { type Tag = U } + type @@[T, U] = T & Tagged[U] + + final override type Type[T] = c.Type @@ T + protected object typeImpl extends TypeDefinitionsImpl { + def fromUntyped[T](untyped: c.Type): Type[T] = untyped.asInstanceOf[Type[T]] + def fromWeak[T: WeakTypeTag]: Type[T] = fromUntyped(weakTypeOf[T]) + def fromWeakTC[Unswapped: WeakTypeTag, T](args: c.Type*): Type[T] = fromUntyped { + val ee = weakTypeOf[Unswapped].etaExpand + // $COVERAGE-OFF$ + if (ee.typeParams.size != args.size) { + val een = ee.typeParams.size + val argsn = args.size + c.abort(c.enclosingPosition, s"Type $ee has different arity ($een) than applied to applyTypeArgs ($argsn)!") + } + // $COVERAGE-ON$ + ee.finalResultType.substituteTypes(ee.typeParams, args.toList) + } + + override val Any: Type[Any] = fromWeak[Any] + override val Int: Type[Int] = fromWeak[Int] + override val Unit: Type[Unit] = fromWeak[Unit] + + override def Function1[From: Type, To: Type]: Type[From => To] = + fromWeakTC[Any => Any, From => To](Type[From], Type[To]) + override def Array[T: Type]: Type[Array[T]] = fromWeakTC[Array[Arbitrary], Array[T]](Type[T]) + override def Option[T: Type]: Type[Option[T]] = fromWeakTC[Option[Arbitrary], Option[T]](Type[T]) + override def Either[L: Type, R: Type]: Type[Either[L, R]] = + fromWeakTC[Either[Arbitrary, Arbitrary2], Either[L, R]](Type[L], Type[R]) + + override def Transformer[From: Type, To: Type]: Type[Transformer[From, To]] = + fromWeakTC[Transformer[Arbitrary, Arbitrary2], Transformer[From, To]](Type[From], Type[To]) + override def PartialTransformer[From: Type, To: Type]: Type[PartialTransformer[From, To]] = + fromWeakTC[PartialTransformer[Arbitrary, Arbitrary2], PartialTransformer[From, To]](Type[From], Type[To]) + override def Patcher[T: Type, Patch: Type]: Type[Patcher[T, Patch]] = + fromWeakTC[Patcher[Arbitrary, Arbitrary2], Patcher[T, Patch]](Type[T], Type[Patch]) + + override def PartialResult[T: Type]: Type[partial.Result[T]] = + fromWeakTC[partial.Result[Arbitrary], partial.Result[T]](Type[T]) + override def PartialResultValue[T: Type]: Type[partial.Result.Value[T]] = + fromWeakTC[partial.Result.Value[Arbitrary], partial.Result.Value[T]](Type[T]) + override def PartialResultErrors: Type[partial.Result.Errors] = + fromWeak[partial.Result.Errors] + + override def PreferTotalTransformer: Type[io.scalaland.chimney.dsl.PreferTotalTransformer.type] = + fromWeak[io.scalaland.chimney.dsl.PreferTotalTransformer.type] + override def PreferPartialTransformer: Type[io.scalaland.chimney.dsl.PreferPartialTransformer.type] = + fromWeak[io.scalaland.chimney.dsl.PreferPartialTransformer.type] + + override def TransformerFlagsDefault: Type[internal.TransformerFlags.Default] = + fromUntyped(c.typeOf[internal.TransformerFlags.Default]) + // def TransformerFlagsEnable[]: Type[internal.TransformerFlags.Default] + override def TransformerFlagsDefaultValues: Type[internal.TransformerFlags.DefaultValues] = + fromWeak[internal.TransformerFlags.DefaultValues] + override def TransformerFlagsBeanGetters: Type[internal.TransformerFlags.BeanGetters] = + fromWeak[internal.TransformerFlags.BeanGetters] + override def TransformerFlagsBeanSetters: Type[internal.TransformerFlags.BeanSetters] = + fromWeak[internal.TransformerFlags.BeanSetters] + override def TransformerFlagsMethodAccessors: Type[internal.TransformerFlags.MethodAccessors] = + fromWeak[internal.TransformerFlags.MethodAccessors] + override def TransformerFlagsOptionDefaultsToNone: Type[internal.TransformerFlags.OptionDefaultsToNone] = + fromWeak[internal.TransformerFlags.OptionDefaultsToNone] + override def TransformerFlagsImplicitConflictResolution[R <: dsls.ImplicitTransformerPreference: Type] + : Type[internal.TransformerFlags.ImplicitConflictResolution[R]] = fromWeakTC[ + internal.TransformerFlags.ImplicitConflictResolution[io.scalaland.chimney.dsl.PreferTotalTransformer.type], + internal.TransformerFlags.ImplicitConflictResolution[R], + ](Type[R]) + + def isSubtypeOf[S, T](S: Type[S], T: Type[T]): Boolean = S.<:<(T) + def isSameAs[S, T](S: Type[S], T: Type[T]): Boolean = S.=:=(T) + } + + final override type Expr[A] = c.Expr[A] + protected object exprImpl extends ExprDefinitionsImpl { + + override def Unit: Expr[Unit] = c.Expr(q"()") + + override def Array[A: Type](args: Expr[A]*): Expr[Array[A]] = c.Expr(q"_root_.scala.Array[${Type[A]}](..${args})") + + override def Option[A: Type](value: Expr[A]): Expr[Option[A]] = + c.Expr(q"_root_.scala.Option[${Type[A]}]($value)") + override def OptionEmpty[A: Type]: Expr[Option[A]] = + c.Expr(q"_root_.scala.Option.empty[${Type[A]}]") + override def OptionApply[A: Type]: Expr[A => Option[A]] = + c.Expr(q"_root_.scala.Option.apply[${Type[A]}](_)") + override def None: Expr[scala.None.type] = c.Expr(q"_root_.scala.None") + + override def Left[L: Type, R: Type](value: Expr[L]): Expr[Left[L, R]] = + c.Expr(q"new _root_.scala.util.Left[${Type[L]}, ${Type[R]}]($value)") + override def Right[L: Type, R: Type](value: Expr[R]): Expr[Right[L, R]] = + c.Expr(q"new _root_.scala.util.Right[${Type[L]}, ${Type[R]}]($value)") + + override def PartialResultValue[T: Type](value: Expr[T]): Expr[partial.Result.Value[T]] = + c.Expr(q"_root_.io.scalaland.chimney.partial.Result.Value[${Type[T]}]($value)") + + override def PartialResultErrorsMerge( + errors1: Expr[partial.Result.Errors], + errors2: Expr[partial.Result.Errors] + ): Expr[partial.Result.Errors] = + c.Expr(q"_root_.io.scalaland.chimney.partial.Result.Errors.merge($errors1, $errors2)") + + override def PartialResultErrorsMergeResultNullable[T: Type]( + errorsNullable: Expr[partial.Result.Errors], + result: Expr[partial.Result[T]] + ): Expr[partial.Result.Errors] = + c.Expr( + q"_root_.io.scalaland.chimney.partial.Result.Errors.__mergeResultNullable[${Type[T]}]($errorsNullable, $result)" + ) + override def PartialResultEmpty[T: Type]: Expr[partial.Result[T]] = + c.Expr(q"_root_.io.scalaland.chimney.partial.Result.fromEmpty[${Type[T]}]") + override def PartialResultFunction[S: Type, T: Type](f: Expr[S => T]): Expr[S => partial.Result[T]] = + c.Expr(q"_root_.io.scalaland.chimney.partial.Result.fromFunction[${Type[S]}, ${Type[T]}]($f)") + override def PartialResultTraverse[M: Type, A: Type, B: Type]( + it: Expr[Iterator[A]], + f: Expr[A => partial.Result[B]], + failFast: Expr[Boolean] + ): Expr[partial.Result[M]] = + c.Expr( + q"_root_.io.scalaland.chimney.partial.Result.traverse[${Type[M]}, ${Type[A]}, ${Type[B]}]($it, $f, $failFast)" + ) + override def PartialResultSequence[M: Type, A: Type]( + it: Expr[Iterator[partial.Result[A]]], + failFast: Expr[Boolean] + ): Expr[partial.Result[M]] = + c.Expr(q"_root_.io.scalaland.chimney.partial.Result.sequence[${Type[M]}, ${Type[A]}]($it, $failFast)") + override def PartialResultMap2[A: Type, B: Type, C: Type]( + fa: Expr[partial.Result[A]], + fb: Expr[partial.Result[B]], + f: Expr[(A, B) => C], + failFast: Expr[Boolean] + ): Expr[partial.Result[C]] = + c.Expr( + q"_root_.io.scalaland.chimney.partial.Result.map2[${Type[A]}, ${Type[B]}, ${Type[C]}]($fa, $fb, $f, $failFast)" + ) + override def PartialResultProduct[A: Type, B: Type]( + fa: Expr[partial.Result[A]], + fb: Expr[partial.Result[B]], + failFast: Expr[Boolean] + ): Expr[partial.Result[(A, B)]] = + c.Expr(q"_root_.io.scalaland.chimney.partial.Result.product[${Type[A]}, ${Type[B]}]($fa, $fb, $failFast)") + + override def PathElementAccessor(targetName: Expr[String]): Expr[partial.PathElement.Accessor] = + c.Expr(q"_root_.io.scalaland.chimney.partial.PathElement.Accessor($targetName)") + override def PathElementIndex(index: Expr[Int]): Expr[partial.PathElement.Index] = + c.Expr(q"_root_.io.scalaland.chimney.partial.PathElement.Index($index)") + override def PathElementMapKey(key: Expr[Any]): Expr[partial.PathElement.MapKey] = + c.Expr(q"_root_.io.scalaland.chimney.partial.PathElement.MapKey($key)") + override def PathElementMapValue(key: Expr[Any]): Expr[partial.PathElement.MapValue] = + c.Expr(q"_root_.io.scalaland.chimney.partial.PathElement.MapValue($key)") + + override def AsInstanceOf[T, S: Type](expr: Expr[T]): c.Expr[S] = c.Expr(q"${expr}.asInstanceOf[${Type[S]}]") + } + + implicit class UntypedTypeOps(private val tpe: c.Type) { + + /** Assumes that this `tpe` is String singleton type and extracts its value */ + def asStringSingletonType: String = tpe + .asInstanceOf[scala.reflect.internal.Types#UniqueConstantType] + .value + .value + .asInstanceOf[String] + } +} +private[compiletime] object DefinitionsPlatform { + type Arbitrary + type Arbitrary2 +} diff --git a/chimney/src/main/scala-2/io/scalaland/chimney/internal/compiletime/ResultDefinitionsPlatform.scala b/chimney/src/main/scala-2/io/scalaland/chimney/internal/compiletime/ResultDefinitionsPlatform.scala new file mode 100644 index 000000000..5a5c4567c --- /dev/null +++ b/chimney/src/main/scala-2/io/scalaland/chimney/internal/compiletime/ResultDefinitionsPlatform.scala @@ -0,0 +1,26 @@ +package io.scalaland.chimney.internal.compiletime + +import scala.annotation.nowarn + +@nowarn("msg=The outer reference in this type test cannot be checked at run time.") +private[compiletime] trait ResultDefinitionsPlatform extends ResultDefinitions { this: DefinitionsPlatform => + + final override protected def reportOrReturn[A](context: Context, value: DerivationResult.Value[A]): A = { + value match { + case Left(errors) => + val output = new StringBuilder() + output.append("Derivation failed due to following errors:\n") + (errors.head +: errors.tail).foreach { + case DerivationError.MacroException(throwable) => + output.append(s"- Unexpected exception was thrown: ${throwable.getMessage}\n") + case DerivationError.NotYetImplemented(functionality) => + output.append(s"- Expansion touched not yet implemented branch of code: $functionality") + } + output.append("\n") + output.append("Consult the documentation") + c.abort(c.enclosingPosition, output.result()) + case Right(value) => + value + } + } +} diff --git a/chimney/src/main/scala-2/io/scalaland/chimney/internal/compiletime/derivation/DerivationDefinitionsPlatform.scala b/chimney/src/main/scala-2/io/scalaland/chimney/internal/compiletime/derivation/DerivationDefinitionsPlatform.scala new file mode 100644 index 000000000..9fce245a2 --- /dev/null +++ b/chimney/src/main/scala-2/io/scalaland/chimney/internal/compiletime/derivation/DerivationDefinitionsPlatform.scala @@ -0,0 +1,17 @@ +package io.scalaland.chimney.internal.compiletime.derivation + +import io.scalaland.chimney.{partial, PartialTransformer, Transformer} +import io.scalaland.chimney.internal.compiletime.DefinitionsPlatform + +private[derivation] trait DerivationDefinitionsPlatform extends DerivationDefinitions { this: DefinitionsPlatform => + + final override protected def instantiateTotalTransformer[From: Type, To: Type]( + f: Expr[From] => DerivationResult[Expr[To]] + ): DerivationResult[Expr[Transformer[From, To]]] = + DerivationResult.notYetImplemented("Turning (From => To) into Transformer[From, To]") + + final override protected def instantiatePartialTransformer[From: Type, To: Type]( + f: (Expr[From], Expr[Boolean]) => DerivationResult[Expr[partial.Result[To]]] + ): DerivationResult[Expr[PartialTransformer[From, To]]] = + DerivationResult.notYetImplemented("Turning (From => To) into PartialTransformer[From, To]") +} diff --git a/chimney/src/main/scala-2/io/scalaland/chimney/internal/compiletime/derivation/DerivationGatewayPlatform.scala b/chimney/src/main/scala-2/io/scalaland/chimney/internal/compiletime/derivation/DerivationGatewayPlatform.scala new file mode 100644 index 000000000..54000dcf1 --- /dev/null +++ b/chimney/src/main/scala-2/io/scalaland/chimney/internal/compiletime/derivation/DerivationGatewayPlatform.scala @@ -0,0 +1,42 @@ +package io.scalaland.chimney.internal.compiletime.derivation + +import io.scalaland.chimney.{internal, PartialTransformer, Transformer} +import io.scalaland.chimney.internal.compiletime.DefinitionsPlatform + +import scala.annotation.unused + +private[compiletime] trait DerivationGatewayPlatform extends DerivationGateway { + this: DefinitionsPlatform & DerivationDefinitionsPlatform => + + import c.universe.{internal as _, Transformer as _, *} + + def deriveTotalTransformerImpl[ + From: WeakTypeTag, + To: WeakTypeTag, + Cfg <: internal.TransformerCfg: WeakTypeTag, + InstanceFlags <: internal.TransformerFlags: WeakTypeTag, + SharedFlags <: internal.TransformerFlags: WeakTypeTag + ](@unused tc: c.Tree): Expr[Transformer[From, To]] = { + implicit val From: Type[From] = typeImpl.fromWeak[From] + implicit val To: Type[To] = typeImpl.fromWeak[To] + implicit val Cfg: Type[Cfg] = typeImpl.fromWeak[Cfg] + implicit val InstanceFlags: Type[InstanceFlags] = typeImpl.fromWeak[InstanceFlags] + implicit val SharedFlags: Type[SharedFlags] = typeImpl.fromWeak[SharedFlags] + deriveTotalTransformerUnsafe[From, To, Cfg, InstanceFlags, SharedFlags] + } + + def derivePartialTransformerImpl[ + From: WeakTypeTag, + To: WeakTypeTag, + Cfg <: internal.TransformerCfg: WeakTypeTag, + InstanceFlags <: internal.TransformerFlags: WeakTypeTag, + SharedFlags <: internal.TransformerFlags: WeakTypeTag + ](@unused tc: c.Tree): Expr[PartialTransformer[From, To]] = { + implicit val From: Type[From] = typeImpl.fromWeak[From] + implicit val To: Type[To] = typeImpl.fromWeak[To] + implicit val Cfg: Type[Cfg] = typeImpl.fromWeak[Cfg] + implicit val InstanceFlags: Type[InstanceFlags] = typeImpl.fromWeak[InstanceFlags] + implicit val SharedFlags: Type[SharedFlags] = typeImpl.fromWeak[SharedFlags] + derivePartialTransformerUnsafe[From, To, Cfg, InstanceFlags, SharedFlags] + } +} diff --git a/chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/ConfigurationDefinitions.scala b/chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/ConfigurationDefinitions.scala new file mode 100644 index 000000000..babb8a99a --- /dev/null +++ b/chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/ConfigurationDefinitions.scala @@ -0,0 +1,144 @@ +package io.scalaland.chimney.internal.compiletime + +import io.scalaland.chimney.dsl.ImplicitTransformerPreference +import io.scalaland.chimney.internal + +import scala.annotation.nowarn + +@nowarn("msg=The outer reference in this type test cannot be checked at run time.") +private[compiletime] trait ConfigurationDefinitions { this: Definitions => + + sealed abstract protected class FieldOverride(val needValueLevelAccess: Boolean) extends Product with Serializable + protected object FieldOverride { + final case class Const(runtimeDataIdx: Int) extends FieldOverride(true) + final case class ConstPartial(runtimeDataIdx: Int) extends FieldOverride(true) + final case class Computed(runtimeDataIdx: Int) extends FieldOverride(true) + final case class ComputedPartial(runtimeDataIdx: Int) extends FieldOverride(true) + final case class RenamedFrom(sourceName: String) extends FieldOverride(false) + } + + sealed protected trait DerivationTarget extends Product with Serializable { + def targetType[To: Type]: ComputedType + def isPartial: Boolean + } + protected object DerivationTarget { + // derivation target instance of `Transformer[A, B]` + case object TotalTransformer extends DerivationTarget { + override def targetType[To: Type]: ComputedType = ComputedType(Type[To]) + + // $COVERAGE-OFF$ + override def isPartial = false + // $COVERAGE-ON$ + } + + // derivation target instance of `PartialTransformer[A, B]` + final case class PartialTransformer(failFastExpr: Expr[Boolean]) extends DerivationTarget { + override def targetType[To: Type]: ComputedType = ComputedType(Type.PartialResult[To]) + + // $COVERAGE-OFF$ + override def isPartial = true + // $COVERAGE-ON$ + } + } + + final protected case class TransformerFlags( + processDefaultValues: Boolean = false, + beanSetters: Boolean = false, + beanGetters: Boolean = false, + methodAccessors: Boolean = false, + optionDefaultsToNone: Boolean = false, + implicitConflictResolution: Option[ImplicitTransformerPreference] = None + ) { + + def setBoolFlag[Flag <: internal.TransformerFlags.Flag: Type](value: Boolean): TransformerFlags = { + if (Type[Flag] =:= Type.TransformerFlags.Flags.DefaultValues) { + copy(processDefaultValues = value) + } else if (Type[Flag] =:= Type.TransformerFlags.Flags.BeanSetters) { + copy(beanSetters = value) + } else if (Type[Flag] =:= Type.TransformerFlags.Flags.BeanGetters) { + copy(beanGetters = value) + } else if (Type[Flag] =:= Type.TransformerFlags.Flags.MethodAccessors) { + copy(methodAccessors = value) + } else if (Type[Flag] =:= Type.TransformerFlags.Flags.OptionDefaultsToNone) { + copy(optionDefaultsToNone = value) + } else { + // $COVERAGE-OFF$ + // TODO + /// c.abort(c.enclosingPosition, s"Invalid transformer flag type: $flagTpe!") + ??? + // $COVERAGE-ON$ + } + } + + def setImplicitConflictResolution(preference: Option[ImplicitTransformerPreference]): TransformerFlags = { + copy(implicitConflictResolution = preference) + } + } + + final protected case class TransformerConfig( // TODO: rename to TransformerContext + // srcPrefixTree: Tree = EmptyTree, + derivationTarget: DerivationTarget = DerivationTarget.TotalTransformer, + flags: TransformerFlags = TransformerFlags(), + fieldOverrides: Map[String, FieldOverride] = Map.empty, + coproductInstanceOverrides: Map[TransformerConfig.CoproductInstanceOverrideTotal, Int] = Map.empty, + coproductInstancesPartialOverrides: Map[TransformerConfig.CoproductInstanceOverridePartial, Int] = Map.empty, + // transformerDefinitionPrefix: Tree = EmptyTree, + definitionScope: Option[TransformerConfig.DefinitionScope] = None + ) { + + import TransformerConfig.* + + // def withSrcPrefixTree(srcPrefixTree: Tree): TransformerConfig = + // copy(srcPrefixTree = srcPrefixTree) + + def withDerivationTarget(derivationTarget: DerivationTarget): TransformerConfig = + copy(derivationTarget = derivationTarget) + + // def withTransformerDefinitionPrefix(tdPrefix: Tree): TransformerConfig = + // copy(transformerDefinitionPrefix = tdPrefix) + + def withDefinitionScope[From: Type, To: Type]: TransformerConfig = + copy(definitionScope = Some(DefinitionScope(ComputedType(Type[From]), ComputedType(Type[To])))) + + def rec: TransformerConfig = + copy( + definitionScope = None, + fieldOverrides = Map.empty + ) + + def valueLevelAccessNeeded: Boolean = + fieldOverrides.exists { case (_, fo) => fo.needValueLevelAccess } || + coproductInstanceOverrides.nonEmpty || + coproductInstancesPartialOverrides.nonEmpty + + def fieldOverride(fieldName: String, fieldOverride: FieldOverride): TransformerConfig = + copy(fieldOverrides = fieldOverrides + (fieldName -> fieldOverride)) + + def coproductInstance[From: Type, To: Type](runtimeDataIdx: Int): TransformerConfig = + copy(coproductInstanceOverrides = + coproductInstanceOverrides + (CoproductInstanceOverrideTotal( + ComputedType(Type[From]), + ComputedType(Type[To]) + ) -> runtimeDataIdx) + ) + + def coproductInstancePartial[From: Type, To: Type](runtimeDataIdx: Int): TransformerConfig = + copy(coproductInstancesPartialOverrides = + coproductInstancesPartialOverrides + (CoproductInstanceOverridePartial( + ComputedType(Type[From]), + ComputedType(Type[To]) + ) -> runtimeDataIdx) + ) + } + protected object TransformerConfig { + final case class CoproductInstanceOverrideTotal(from: ComputedType, to: ComputedType) + final case class CoproductInstanceOverridePartial(from: ComputedType, to: ComputedType) + final case class DefinitionScope(from: ComputedType, to: ComputedType) + } + + protected def readConfig[ + Cfg <: internal.TransformerCfg: Type, + InstanceFlags <: internal.TransformerFlags: Type, + SharedFlags <: internal.TransformerFlags: Type + ]: TransformerConfig +} diff --git a/chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/Definitions.scala b/chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/Definitions.scala new file mode 100644 index 000000000..2bc3f0055 --- /dev/null +++ b/chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/Definitions.scala @@ -0,0 +1,268 @@ +package io.scalaland.chimney.internal.compiletime + +import io.scalaland.chimney.dsl.ImplicitTransformerPreference +import io.scalaland.chimney.internal +import io.scalaland.chimney.{partial, PartialTransformer, Patcher, Transformer} + +private[compiletime] trait Definitions extends ConfigurationDefinitions with ResultDefinitions { + + /** Platform-specific type representation (c.universe.Type in 2, scala.quoted.Type[A] in 3) */ + protected type Type[T] + protected object Type { + final def apply[T](implicit T: Type[T]): Type[T] = T + + val Any: Type[Any] = typeImpl.Any + val Int: Type[Int] = typeImpl.Int + val Unit: Type[Unit] = typeImpl.Unit + + object Function1 { + def apply[From: Type, To: Type]: Type[From => To] = typeImpl.Function1[From, To] + } + + object Array { + def apply[T: Type]: Type[Array[T]] = typeImpl.Array[T] + val Any: Type[Array[Any]] = apply(Type.Any) + } + + object Option { + def apply[T: Type]: Type[Option[T]] = typeImpl.Option[T] + } + + object Either { + def apply[L: Type, R: Type]: Type[Either[L, R]] = typeImpl.Either[L, R] + } + + object Transformer { + def apply[From: Type, To: Type]: Type[Transformer[From, To]] = typeImpl.Transformer[From, To] + } + object PartialTransformer { + def apply[From: Type, To: Type]: Type[PartialTransformer[From, To]] = typeImpl.PartialTransformer[From, To] + } + object Patcher { + def Patcher[T: Type, Patch: Type]: Type[Patcher[T, Patch]] = typeImpl.Patcher[T, Patch] + } + + object PartialResult { + object Value { + def apply[T: Type]: Type[partial.Result.Value[T]] = typeImpl.PartialResultValue[T] + } + val Errors: Type[partial.Result.Errors] = typeImpl.PartialResultErrors + def apply[T: Type]: Type[partial.Result[T]] = typeImpl.PartialResult[T] + } + + val PreferTotalTransformer: Type[io.scalaland.chimney.dsl.PreferTotalTransformer.type] = + typeImpl.PreferTotalTransformer + val PreferPartialTransformer: Type[io.scalaland.chimney.dsl.PreferPartialTransformer.type] = + typeImpl.PreferPartialTransformer + + object TransformerFlags { + + val Default: Type[internal.TransformerFlags.Default] = typeImpl.TransformerFlagsDefault + // TODO: Enable + // TODO: Disable + + object Flags { + + val DefaultValues: Type[internal.TransformerFlags.DefaultValues] = typeImpl.TransformerFlagsDefaultValues + val BeanGetters: Type[internal.TransformerFlags.BeanGetters] = typeImpl.TransformerFlagsBeanGetters + val BeanSetters: Type[internal.TransformerFlags.BeanSetters] = typeImpl.TransformerFlagsBeanSetters + val MethodAccessors: Type[internal.TransformerFlags.MethodAccessors] = typeImpl.TransformerFlagsMethodAccessors + val OptionDefaultsToNone: Type[internal.TransformerFlags.OptionDefaultsToNone] = + typeImpl.TransformerFlagsOptionDefaultsToNone + // TODO: ImplicitConflictResolution + } + } + } + implicit class TypeOps[T](private val tpe: Type[T]) { + + def <:<[S](another: Type[S]): Boolean = typeImpl.isSubtypeOf(tpe, another) + def =:=[S](another: Type[S]): Boolean = typeImpl.isSameAs(tpe, another) + } + + /** Used to erase the type of Type, while providing the utilities to still make it useful */ + type ComputedType = { type Underlying } + object ComputedType { + def apply[T](tpe: Type[T]): ComputedType = tpe.asInstanceOf[ComputedType] + } + implicit class ComputedTypeOps(val ct: ComputedType) { + def Type: Type[ct.Underlying] = ct.asInstanceOf[Type[ct.Underlying]] + def use[Out](thunk: Type[ct.Underlying] => Out): Out = thunk(Type) + } + + /** Platform-specific expression representation (c.universe.Expr[A] in 2, quotes.Expr[A] in 3 */ + protected type Expr[A] + protected object Expr { + + val Unit: Expr[Unit] = exprImpl.Unit + + object Array { + def apply[A: Type](args: Expr[A]*): Expr[Array[A]] = exprImpl.Array[A](args*) + } + + object Option { + def apply[A: Type](a: Expr[A]): Expr[Option[A]] = exprImpl.Option[A](a) + def empty[A: Type]: Expr[Option[A]] = exprImpl.OptionEmpty[A] + def apply[A: Type]: Expr[A => Option[A]] = exprImpl.OptionApply[A] + } + val None: Expr[scala.None.type] = exprImpl.None + + object Either { + def Left[L: Type, R: Type](value: Expr[L]): Expr[Left[L, R]] = exprImpl.Left[L, R](value) + def Right[L: Type, R: Type](value: Expr[R]): Expr[Right[L, R]] = exprImpl.Right[L, R](value) + } + + object PartialResult { + object Value { + def apply[T: Type](value: Expr[T]): Expr[partial.Result.Value[T]] = exprImpl.PartialResultValue[T](value) + } + object Errors { + def merge( + errors1: Expr[partial.Result.Errors], + errors2: Expr[partial.Result.Errors] + ): Expr[partial.Result.Errors] = exprImpl.PartialResultErrorsMerge(errors1, errors2) + def mergeResultNullable[T: Type]( + errorsNullable: Expr[partial.Result.Errors], + result: Expr[partial.Result[T]] + ): Expr[partial.Result.Errors] = exprImpl.PartialResultErrorsMergeResultNullable(errorsNullable, result) + } + + def fromEmpty[T: Type]: Expr[partial.Result[T]] = exprImpl.PartialResultEmpty + def fromFunction[S: Type, T: Type](f: Expr[S => T]): Expr[S => partial.Result[T]] = + exprImpl.PartialResultFunction[S, T](f) + + def traverse[M: Type, A: Type, B: Type]( + it: Expr[Iterator[A]], + f: Expr[A => partial.Result[B]], + failFast: Expr[Boolean] + ): Expr[partial.Result[M]] = exprImpl.PartialResultTraverse[M, A, B](it, f, failFast) + def sequence[M: Type, A: Type]( + it: Expr[Iterator[partial.Result[A]]], + failFast: Expr[Boolean] + ): Expr[partial.Result[M]] = exprImpl.PartialResultSequence[M, A](it, failFast) + def map2[A: Type, B: Type, C: Type]( + fa: Expr[partial.Result[A]], + fb: Expr[partial.Result[B]], + f: Expr[(A, B) => C], + failFast: Expr[Boolean] + ): Expr[partial.Result[C]] = exprImpl.PartialResultMap2[A, B, C](fa, fb, f, failFast) + def product[A: Type, B: Type]( + fa: Expr[partial.Result[A]], + fb: Expr[partial.Result[B]], + failFast: Expr[Boolean] + ): Expr[partial.Result[(A, B)]] = exprImpl.PartialResultProduct[A, B](fa, fb, failFast) + } + + object PathElement { + object Accessor { + def apply(targetName: Expr[String]): Expr[partial.PathElement.Accessor] = + exprImpl.PathElementAccessor(targetName) + } + object Index { + def apply(index: Expr[Int]): Expr[partial.PathElement.Index] = exprImpl.PathElementIndex(index) + } + object MapKey { + def apply(key: Expr[Any]): Expr[partial.PathElement.MapKey] = exprImpl.PathElementMapKey(key) + } + object MapValue { + def apply(key: Expr[Any]): Expr[partial.PathElement.MapValue] = exprImpl.PathElementMapValue(key) + } + } + } + implicit class ExprOps[T](private val expr: Expr[T]) { + + def asInstanceOf[S: Type]: Expr[S] = exprImpl.AsInstanceOf[T, S](expr) + } + + // Platform specific implementations gathered in a few variables to not pollute scope + + protected def typeImpl: TypeDefinitionsImpl + protected trait TypeDefinitionsImpl { + + def Any: Type[Any] + def Int: Type[Int] + def Unit: Type[Unit] + + def Function1[From: Type, To: Type]: Type[From => To] + def Array[T: Type]: Type[Array[T]] + def Option[T: Type]: Type[Option[T]] + def Either[L: Type, R: Type]: Type[Either[L, R]] + + def Transformer[From: Type, To: Type]: Type[Transformer[From, To]] + def PartialTransformer[From: Type, To: Type]: Type[PartialTransformer[From, To]] + def Patcher[T: Type, Patch: Type]: Type[Patcher[T, Patch]] + + def PartialResult[T: Type]: Type[partial.Result[T]] + def PartialResultValue[T: Type]: Type[partial.Result.Value[T]] + def PartialResultErrors: Type[partial.Result.Errors] + + def PreferTotalTransformer: Type[io.scalaland.chimney.dsl.PreferTotalTransformer.type] + def PreferPartialTransformer: Type[io.scalaland.chimney.dsl.PreferPartialTransformer.type] + + def TransformerFlagsDefault: Type[internal.TransformerFlags.Default] + // def TransformerFlagsEnable[]: Type[internal.TransformerFlags.Default] + def TransformerFlagsDefaultValues: Type[internal.TransformerFlags.DefaultValues] + def TransformerFlagsBeanGetters: Type[internal.TransformerFlags.BeanGetters] + def TransformerFlagsBeanSetters: Type[internal.TransformerFlags.BeanSetters] + def TransformerFlagsMethodAccessors: Type[internal.TransformerFlags.MethodAccessors] + def TransformerFlagsOptionDefaultsToNone: Type[internal.TransformerFlags.OptionDefaultsToNone] + def TransformerFlagsImplicitConflictResolution[R <: ImplicitTransformerPreference: Type] + : Type[internal.TransformerFlags.ImplicitConflictResolution[R]] + + def isSubtypeOf[S, T](S: Type[S], T: Type[T]): Boolean + def isSameAs[S, T](S: Type[S], T: Type[T]): Boolean + } + protected def exprImpl: ExprDefinitionsImpl + protected trait ExprDefinitionsImpl { + + def Unit: Expr[Unit] + + def Array[A: Type](args: Expr[A]*): Expr[Array[A]] + + def Option[A: Type](value: Expr[A]): Expr[Option[A]] + def OptionEmpty[A: Type]: Expr[Option[A]] + def OptionApply[A: Type]: Expr[A => Option[A]] + def None: Expr[scala.None.type] + + def Left[L: Type, R: Type](value: Expr[L]): Expr[Left[L, R]] + def Right[L: Type, R: Type](value: Expr[R]): Expr[Right[L, R]] + + def PartialResultValue[T: Type](value: Expr[T]): Expr[partial.Result.Value[T]] + def PartialResultErrorsMerge( + errors1: Expr[partial.Result.Errors], + errors2: Expr[partial.Result.Errors] + ): Expr[partial.Result.Errors] + def PartialResultErrorsMergeResultNullable[T: Type]( + errorsNullable: Expr[partial.Result.Errors], + result: Expr[partial.Result[T]] + ): Expr[partial.Result.Errors] + def PartialResultEmpty[T: Type]: Expr[partial.Result[T]] + def PartialResultFunction[S: Type, T: Type](f: Expr[S => T]): Expr[S => partial.Result[T]] + def PartialResultTraverse[M: Type, A: Type, B: Type]( + it: Expr[Iterator[A]], + f: Expr[A => partial.Result[B]], + failFast: Expr[Boolean] + ): Expr[partial.Result[M]] + def PartialResultSequence[M: Type, A: Type]( + it: Expr[Iterator[partial.Result[A]]], + failFast: Expr[Boolean] + ): Expr[partial.Result[M]] + def PartialResultMap2[A: Type, B: Type, C: Type]( + fa: Expr[partial.Result[A]], + fb: Expr[partial.Result[B]], + f: Expr[(A, B) => C], + failFast: Expr[Boolean] + ): Expr[partial.Result[C]] + def PartialResultProduct[A: Type, B: Type]( + fa: Expr[partial.Result[A]], + fb: Expr[partial.Result[B]], + failFast: Expr[Boolean] + ): Expr[partial.Result[(A, B)]] + + def PathElementAccessor(targetName: Expr[String]): Expr[partial.PathElement.Accessor] + def PathElementIndex(index: Expr[Int]): Expr[partial.PathElement.Index] + def PathElementMapKey(index: Expr[Any]): Expr[partial.PathElement.MapKey] + def PathElementMapValue(index: Expr[Any]): Expr[partial.PathElement.MapValue] + + def AsInstanceOf[T, S: Type](expr: Expr[T]): Expr[S] + } +} diff --git a/chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/ResultDefinitions.scala b/chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/ResultDefinitions.scala new file mode 100644 index 000000000..42e9b49ee --- /dev/null +++ b/chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/ResultDefinitions.scala @@ -0,0 +1,324 @@ +package io.scalaland.chimney.internal.compiletime + +import scala.annotation.{nowarn, tailrec} +import scala.collection.compat.* + +@nowarn("msg=The outer reference in this type test cannot be checked at run time.") +private[compiletime] trait ResultDefinitions { this: Definitions => + + sealed protected trait DerivationError extends Product with Serializable + protected object DerivationError { + + // TODO: expand as needed + final case class MacroException(throwable: Throwable) extends DerivationError + final case class NotYetImplemented(functionality: String) extends DerivationError + } + + final protected case class DerivationErrors(head: DerivationError, tail: Vector[DerivationError]) { + + def ++(errors: DerivationErrors): DerivationErrors = + DerivationErrors(head, tail ++ Vector(errors.head) ++ errors.tail) + } + protected object DerivationErrors { + + def apply(error: DerivationError, errors: DerivationError*): DerivationErrors = + apply(error, errors.toVector) + + def fromException(throwable: Throwable): DerivationErrors = + apply(DerivationError.MacroException(throwable), Vector.empty) + } + + /** Lazily evaluated log entry */ + final class LogEntry(val nesting: Int, thunk: => String) { + lazy val message: String = thunk + } + + /** Contains everything we would like to access in our computations without resorting to globals. + * + * Possible usages: + * - access to config + * - appending logs and diagnostics + * - storing cache for intermediate results + */ + final case class Context( + config: TransformerConfig = TransformerConfig(), + logNesting: Int = 0, + logs: Vector[LogEntry] = Vector.empty + ) { + + def appendLog(msg: => String): Context = copy(logs = logs :+ new LogEntry(logNesting, msg)) + def increaseLogNesting: Context = copy(logNesting = logNesting + 1) + def decreaseLogNesting: Context = copy(logNesting = logNesting - 1) + + // TODO: rethink if necessary + def merge(ctx: Context): Context = copy( + config = config, // TODO? configs should be the same - consider logging warning if differ + logNesting = logNesting, // TODO? log nesting should be the same - consider logging warning if differ + logs = (logs ++ ctx.logs).distinct + ) + } + + /** Representations of a ongoing computation. + * + * Features: + * - stack-safe + * - handles errors + * - catches exceptions + * - provides sequential and parallel combinators + * - threads context through computations + * + * Intended to simplify how we express our logic during the derivation without long types and boilerplate. + */ + sealed protected trait DerivationResult[+A] { + + import DerivationResult.* + + // monadic operations with sequential semantics (the first fail breaks the circuit) + + final def transformWith[B](onSuccess: A => DerivationResult[B])( + onFailure: DerivationErrors => DerivationResult[B] + ): DerivationResult[B] = + TransformWith(this, onSuccess, onFailure) + + final def flatMap[B](f: A => DerivationResult[B]): DerivationResult[B] = transformWith(f)(fail) + final def map[B](f: A => B): DerivationResult[B] = flatMap(f andThen pure) + + final def flatTap[B](f: A => DerivationResult[B]): DerivationResult[A] = flatMap(a => f(a).as(a)) + final def tap[B](f: A => B): DerivationResult[A] = flatTap(a => pure(a)) + + final def recoverWith[A1 >: A](f: DerivationErrors => DerivationResult[A1]): DerivationResult[A1] = + transformWith[A1](pure)(f(_)) + final def recover[A1 >: A](f: DerivationErrors => A1): DerivationResult[A1] = recoverWith(f andThen pure) + + final def map2[B, C](result: => DerivationResult[B])(f: (A, B) => C): DerivationResult[C] = + flatMap(a => result.map(f(a, _))) + + final def as[B](value: B): DerivationResult[B] = map(_ => value) + final def void: DerivationResult[Unit] = as(()) + + final def >>[B](result: => DerivationResult[B]): DerivationResult[B] = flatMap(_ => result) + final def <<[B](result: => DerivationResult[B]): DerivationResult[A] = flatMap(a => result.as(a)) + + // applicative operations with parallel semantics (both branches are evaluated and then their results aggregated) + + final def parMap2[B, C](result: => DerivationResult[B])(f: (A, B) => C): DerivationResult[C] = context.flatMap { + originalContext => + // TODO: thread context without restarting + + var contextA: Context = originalContext + val rewindContext = UpdateContext(ctx => { contextA = ctx; ctx }).flatMap(_ => result) + val mergeContexts = UpdateContext(contextB => contextA.merge(contextB)) + + transformWith[C] { valueA => + rewindContext.transformWith { valueB => + mergeContexts.as(f(valueA, valueB)) + } { errorB => + mergeContexts >> fail(errorB) + } + } { errorA => + rewindContext.transformWith[C] { _ => + mergeContexts >> fail(errorA) + } { errorB => + mergeContexts >> fail(errorA ++ errorB) + } + } + } + final def parTuple[B](result: => DerivationResult[B]): DerivationResult[(A, B)] = parMap2(result)(_ -> _) + + // evaluated until first success, if none succeed errors aggregate + + final def orElse[A1 >: A](result: => DerivationResult[A1]): DerivationResult[A1] = context.flatMap { context1 => + recoverWith { error => + val updateContext = UpdateContext(context2 => context1.merge(context2)) + result.transformWith { success => + updateContext.as(success) + } { error2 => + updateContext >> fail(error ++ error2) + } + } + } + } + protected object DerivationResult { + + type Value[O] = Either[DerivationErrors, O] + + final private case class Pure[A](value: Value[A]) extends DerivationResult[A] + final private case class TransformWith[A, B]( + result: DerivationResult[A], + next: A => DerivationResult[B], + fail: DerivationErrors => DerivationResult[B] + ) extends DerivationResult[B] + final private case class UpdateContext(update: Context => Context) extends DerivationResult[Context] + + def apply[A](thunk: => A): DerivationResult[A] = unit.map(_ => thunk) + def defer[A](thunk: => DerivationResult[A]): DerivationResult[A] = unit.flatMap(_ => thunk) + + def pure[A](value: A): DerivationResult[A] = Pure(Right(value)) + def fail[A](error: DerivationErrors): DerivationResult[A] = Pure(Left(error)) + + def context: DerivationResult[Context] = UpdateContext(identity) + def config: DerivationResult[TransformerConfig] = context.map(_.config) + def log(msg: String): DerivationResult[Unit] = UpdateContext(_.appendLog(msg)).void + // TODO: rethink + def nestLogs[A](result: DerivationResult[A]): DerivationResult[A] = + UpdateContext(_.increaseLogNesting) >> result << UpdateContext(_.decreaseLogNesting) + + val unit: DerivationResult[Unit] = Pure(Right(())) + + def fromException[T](error: Throwable): DerivationResult[T] = fail(DerivationErrors.fromException(error)) + def notYetImplemented[T](functionality: String): DerivationResult[T] = fail( + DerivationErrors(DerivationError.NotYetImplemented(functionality)) + ) + + type FactoryOf[Coll[+_], O] = Factory[O, Coll[O]] + + // monadic operations with sequential semantics (the first fail breaks the circuit) + + def traverse[C[+A] <: IterableOnce[A], I, O: FactoryOf[C, *]]( + coll: C[I] + )(f: I => DerivationResult[O]): DerivationResult[C[O]] = + coll.iterator + .foldLeft(pure(implicitly[FactoryOf[C, O]].newBuilder)) { (br, i) => + br.map2(f(i))(_ += _) + } + .map(_.result()) + def sequence[C[+A] <: IterableOnce[A], B: FactoryOf[C, *]](coll: C[DerivationResult[B]]): DerivationResult[C[B]] = + traverse(coll)(identity) + + // applicative operations with parallel semantics (both branches are evaluated and then their results aggregated) + + def parTraverse[C[+A] <: IterableOnce[A], I, O: FactoryOf[C, *]]( + coll: C[I] + )(f: I => DerivationResult[O]): DerivationResult[C[O]] = + coll.iterator + .foldLeft(pure(implicitly[FactoryOf[C, O]].newBuilder)) { (br, i) => + br.parMap2(f(i))(_ += _) + } + .map(_.result()) + def parSequence[C[+A] <: IterableOnce[A], B: FactoryOf[C, *]]( + coll: C[DerivationResult[B]] + ): DerivationResult[C[B]] = + parTraverse(coll)(identity) + + // evaluated until first success, if none succeed errors aggregate + + def firstOf[A](head: DerivationResult[A], tail: DerivationResult[A]*): DerivationResult[A] = + tail.foldLeft(head)(_.orElse(_)) + + // here be dragons + + final def unsafeRun[A](context: Context, result: DerivationResult[A]): (Context, Value[A]) = + Stack.eval(context, result) + + final def unsafeRunExpr[A](context: Context, result: DerivationResult[Expr[A]]): Expr[A] = { + val (computedContext, value) = unsafeRun(context, result) + reportOrReturn(computedContext, value) + } + + /** Trampoline utility. + * + * all chain of monadic operations can be though of as something like: + * + * {{{ + * A => F[B] andThen B => F[C] andThen C => F[D] andThen D => F[E] andThen ... + * }}} + * + * where operations would be grouped together like e.g. + * + * {{{ + * A => F[B] + * andThen + * ( + * ( + * B => F[C] + * andThen + * C => F[D] + * ) + * andThen + * D => F[E] + * ) + * ... + * }}} + * + * Monadic laws guarantee us that only global order of operations is important, not how we group it inside + * parenthesis, so we can rearrange them for our convenience. + * + * `Stack` is used exactly for that, to rewrite this chain of operations so that the head will change from e.g. + * + * {{{ + * ( + * A => F[B] + * andThen + * B => F[C] + * ) + * andThen + * C > F[D] + * ... + * }}} + * + * to + * + * {{{ + * A => F[B] + * andThen + * ( + * B => F[C] + * andThen + * C => F[D]) + * ) + * ... + * }}} + * + * which would make it possible to evaluate the function at the head. By rewriting until evaluation is possible, + * and then evaluating in a `loop`, we are able to execute the whole `Result` with stack safety. + */ + sealed private trait Stack[-I, O] + private object Stack { + + private object Ignored + private val ignore = Right(Ignored) + + final private case class Rewrite[I, M, O](result: DerivationResult[M], tail: Stack[M, O])(implicit + ev: I =:= Ignored.type + ) extends Stack[I, O] + final private case class Advance[I, M, O]( + next: I => DerivationResult[M], + fail: DerivationErrors => DerivationResult[M], + tail: Stack[M, O] + ) extends Stack[I, O] + final private case class Return[I, O](cast: I <:< O) extends Stack[I, O] + + @tailrec + private def loop[I, O](context: Context, current: Value[I], s: Stack[I, O]): (Context, Value[O]) = s match { + case Rewrite(Pure(next), stack) => + loop(context, next, stack) + case Rewrite(TransformWith(result, onSuccess, onFailure), stack) => + loop(context, ignore, Rewrite(result, Advance(onSuccess, onFailure, stack))) + case Rewrite(UpdateContext(update), stack) => + val (newContext, next) = current match { + case Left(error) => context -> Left(error) + case Right(_) => + try { + val newContext = update(context) + newContext -> Right(newContext) + } catch { + case error: Throwable => context -> Left(DerivationErrors.fromException(error)) + } + } + loop(newContext, next, stack) + case Advance(onSuccess, onFailure, stack) => + val result = + try current.fold(onFailure, onSuccess) + catch { case err: Throwable => fail(DerivationErrors.fromException(err)) } + loop(context, ignore, Rewrite(result, stack)) + case Return(cast) => + context -> current.map(cast) + } + + def eval[A](context: Context, result: DerivationResult[A]): (Context, Value[A]) = + loop(context, ignore, Rewrite(result, Return(implicitly[A <:< A]))) + } + } + + protected def reportOrReturn[A](context: Context, value: DerivationResult.Value[A]): A +} diff --git a/chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/derivation/DerivationDefinitions.scala b/chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/derivation/DerivationDefinitions.scala new file mode 100644 index 000000000..58d9d700f --- /dev/null +++ b/chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/derivation/DerivationDefinitions.scala @@ -0,0 +1,91 @@ +package io.scalaland.chimney.internal.compiletime.derivation + +import io.scalaland.chimney.internal.compiletime.Definitions +import io.scalaland.chimney.{partial, PartialTransformer, Transformer} + +import scala.annotation.nowarn + +@nowarn("msg=The outer reference in this type test cannot be checked at run time.") +private[derivation] trait DerivationDefinitions { this: Definitions => + + protected type Id[A] = A + + // TODO: sth like that to replave Inputs or whatever we call it + + sealed protected trait DerivedExpr[T, P] extends Product with Serializable { + type From + + def isTotal: Boolean + def isPartial: Boolean + + def From: Type[From] + def src: Expr[From] + + def transformExpr[T2, P2]( + transformTotal: (Type[From], Expr[From], Expr[T]) => DerivationResult[Expr[T2]] + )( + transformPartial: (Type[From], Expr[From], Expr[Boolean], Expr[P]) => DerivationResult[Expr[P2]] + ): DerivationResult[DerivedExpr[T2, P2]] + + def toEither: Either[Expr[T], Expr[P]] + } + protected object DerivedExpr { + + def emptyTotal[From: Type]( + src: Expr[From] + ): DerivedExpr[Unit, Unit] = DerivedTotalExpr(Type[From], src, Expr.Unit) + + def emptyPartial[From: Type]( + src: Expr[From], + failFast: Expr[Boolean] + ): DerivedExpr[Unit, Unit] = DerivedPartialExpr(Type[From], src, failFast, Expr.Unit) + + final case class DerivedTotalExpr[From0, T, P]( + From: Type[From0], + src: Expr[From0], + expr: Expr[T] + ) extends DerivedExpr[T, P] { + + type From = From0 + def isTotal = true + def isPartial = false + + override def transformExpr[T2, P2]( + transformTotal: (Type[From0], Expr[From0], Expr[T]) => DerivationResult[Expr[T2]] + )( + transformPartial: (Type[From0], Expr[From0], Expr[Boolean], Expr[P]) => DerivationResult[Expr[P2]] + ): DerivationResult[DerivedExpr[T2, P2]] = transformTotal(From, src, expr).map(expr2 => copy(expr = expr2)) + + override def toEither: Either[Expr[T], Expr[P]] = Left(expr) + } + + final case class DerivedPartialExpr[From0, T, P]( + From: Type[From0], + src: Expr[From0], + failFast: Expr[Boolean], + expr: Expr[P] + ) extends DerivedExpr[T, P] { + + type From = From0 + def isTotal = false + def isPartial = true + + override def transformExpr[T2, P2]( + transformTotal: (Type[From0], Expr[From0], Expr[T]) => DerivationResult[Expr[T2]] + )( + transformPartial: (Type[From0], Expr[From0], Expr[Boolean], Expr[P]) => DerivationResult[Expr[P2]] + ): DerivationResult[DerivedExpr[T2, P2]] = + transformPartial(From, src, failFast, expr).map(expr2 => copy(expr = expr2)) + + override def toEither: Either[Expr[T], Expr[P]] = Right(expr) + } + } + + protected def instantiateTotalTransformer[From: Type, To: Type]( + f: Expr[From] => DerivationResult[Expr[To]] + ): DerivationResult[Expr[Transformer[From, To]]] + + protected def instantiatePartialTransformer[From: Type, To: Type]( + f: (Expr[From], Expr[Boolean]) => DerivationResult[Expr[partial.Result[To]]] + ): DerivationResult[Expr[PartialTransformer[From, To]]] +} diff --git a/chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/derivation/DerivationGateway.scala b/chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/derivation/DerivationGateway.scala new file mode 100644 index 000000000..cc31e67df --- /dev/null +++ b/chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/derivation/DerivationGateway.scala @@ -0,0 +1,58 @@ +package io.scalaland.chimney.internal.compiletime.derivation + +import io.scalaland.chimney.{PartialTransformer, Transformer} +import io.scalaland.chimney.internal +import io.scalaland.chimney.internal.compiletime.Definitions +import io.scalaland.chimney.partial + +// TODO +private[compiletime] trait DerivationGateway { this: Definitions & DerivationDefinitions => + + final protected def deriveTotalTransformer[From: Type, To: Type]: DerivationResult[Expr[Transformer[From, To]]] = + instantiateTotalTransformer[From, To] { (src: Expr[From]) => + deriveTransformerBody[From, To](DerivedExpr.emptyTotal(src)) + .map(_.toEither) + .flatMap { + case Left(total) => DerivationResult.pure(total) + case Right(_) => DerivationResult.fromException(new AssertionError("Expected total transformer expr")) + } + } + + final protected def deriveTotalTransformerUnsafe[ + From: Type, + To: Type, + Cfg <: internal.TransformerCfg: Type, + InstanceFlags <: internal.TransformerFlags: Type, + SharedFlags <: internal.TransformerFlags: Type + ]: Expr[Transformer[From, To]] = DerivationResult.unsafeRunExpr( + Context(config = readConfig[Cfg, InstanceFlags, SharedFlags]), + deriveTotalTransformer[From, To] + ) + + final protected def derivePartialTransformer[From: Type, To: Type] + : DerivationResult[Expr[PartialTransformer[From, To]]] = + instantiatePartialTransformer[From, To] { (src: Expr[From], failFast: Expr[Boolean]) => + deriveTransformerBody[From, To](DerivedExpr.emptyPartial(src, failFast)) + .map(_.toEither) + .flatMap { + case Left(_) => DerivationResult.fromException(new AssertionError("Expected partial transformer expr")) + case Right(partial) => DerivationResult.pure(partial) + } + } + + final protected def derivePartialTransformerUnsafe[ + From: Type, + To: Type, + Cfg <: internal.TransformerCfg: Type, + InstanceFlags <: internal.TransformerFlags: Type, + SharedFlags <: internal.TransformerFlags: Type + ]: Expr[PartialTransformer[From, To]] = DerivationResult.unsafeRunExpr( + Context(config = readConfig[Cfg, InstanceFlags, SharedFlags]), + derivePartialTransformer[From, To] + ) + + private def deriveTransformerBody[From: Type, To: Type]( + inputs: DerivedExpr[Unit, Unit] + ): DerivationResult[DerivedExpr[To, partial.Result[To]]] = + DerivationResult.notYetImplemented("Actual derivation") +} diff --git a/chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/dsl/DslDefinitions.scala b/chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/dsl/DslDefinitions.scala new file mode 100644 index 000000000..dbf4085af --- /dev/null +++ b/chimney/src/main/scala/io/scalaland/chimney/internal/compiletime/dsl/DslDefinitions.scala @@ -0,0 +1,5 @@ +package io.scalaland.chimney.internal.compiletime.dsl + +import io.scalaland.chimney.internal.compiletime.Definitions + +private[dsl] trait DslDefinitions { this: Definitions => }