Skip to content
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

implement requireSourceFieldsUsedExcept #608

Closed
Show file tree
Hide file tree
Changes from 8 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
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,18 @@ final class TransformerInto[From, To, Overrides <: TransformerOverrides, Flags <
): TransformerInto[From, To, ? <: TransformerOverrides, Flags] =
macro TransformerIntoMacros.withConstructorImpl[From, To, Overrides, Flags]

/** Require that all fields of the source object except fields mentioned in `selectorFrom` are used in the
* transformation. and fail compilation otherwise.
*
* @param selectorFrom
* exception fields that are not required to be used in the transformation
* @return
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* @return
*   [[io.scalaland.chimney.dsl.TransformerInto]]
*
* @since 1.5.0

def requireSourceFieldsUsedExcept[T, U](
selectorFrom: From => Any*
): TransformerInto[From, To, ? <: TransformerOverrides, Flags] =
macro TransformerIntoMacros.requireSourceFieldsUsedExceptImpl[From, To, Overrides, Flags]

/** Apply configured transformation in-place.
*
* It runs macro that tries to derive instance of `Transformer[From, To]` and immediately apply it to captured
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,17 @@ private[compiletime] trait ChimneyTypesPlatform extends ChimneyTypes { this: Chi
)
}
}
object RequireSourceFieldsExcept extends RequireSourceFieldsExceptModule {
def apply[
FromPathList <: runtime.PathList: Type,
Tail <: runtime.TransformerOverrides: Type
]: Type[runtime.TransformerOverrides.RequireSourceFieldsExcept[FromPathList, Tail]] =
weakTypeTag[runtime.TransformerOverrides.RequireSourceFieldsExcept[FromPathList, Tail]]
def unapply[A](A: Type[A]): Option[(?<[runtime.PathList], ?<[runtime.TransformerOverrides])] =
A.asCtor[runtime.TransformerOverrides.RequireSourceFieldsExcept[?, ?]].map { A0 =>
(A0.param_<[runtime.PathList](0), A0.param_<[runtime.TransformerOverrides](1))
}
}
}

object TransformerFlags extends TransformerFlagsModule {
Expand Down Expand Up @@ -406,6 +417,17 @@ private[compiletime] trait ChimneyTypesPlatform extends ChimneyTypes { this: Chi
}
}

object PathList extends PathListModule {
val Empty: Type[runtime.PathList.Empty] = weakTypeTag[runtime.PathList.Empty]
object List extends ListModule {
def apply[Head <: runtime.Path: Type, Tail <: runtime.PathList: Type]: Type[runtime.PathList.List[Head, Tail]] =
weakTypeTag[runtime.PathList.List[Head, Tail]]
def unapply[A](A: Type[A]): Option[(?<[runtime.Path], ?<[runtime.PathList])] =
A.asCtor[runtime.PathList.List[?, ?]]
.map(A0 => A0.param_<[runtime.Path](0) -> A0.param_<[runtime.PathList](1))
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A list of paths would not be needed if instead this would apply a config, as many times as many paths there is in vararg. This would make less code to maintain in DSL and macros.


object DefaultValue extends DefaultValueModule {
def apply[Value: Type]: Type[integrations.DefaultValue[Value]] =
weakTypeTag[integrations.DefaultValue[Value]]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.scalaland.chimney.internal.compiletime.dsl

import io.scalaland.chimney.dsl.TransformerInto
import io.scalaland.chimney.internal.runtime.{ArgumentLists, Path, TransformerFlags, TransformerOverrides}
import io.scalaland.chimney.internal.runtime.{ArgumentLists, Path, PathList, TransformerFlags, TransformerOverrides}
import io.scalaland.chimney.internal.runtime.TransformerOverrides.*

import scala.annotation.unused
Expand Down Expand Up @@ -96,4 +96,17 @@ class TransformerIntoMacros(val c: whitebox.Context) extends utils.DslMacroUtils
.addOverride(f)
.asInstanceOfExpr[TransformerInto[From, To, Constructor[Args, Path.Root, Overrides], Flags]]
}.applyFromBody(f)

def requireSourceFieldsUsedExceptImpl[
From: WeakTypeTag,
To: WeakTypeTag,
Overrides <: TransformerOverrides: WeakTypeTag,
Flags <: TransformerFlags: WeakTypeTag
](selectorFrom: Tree*): Tree = c.prefix.tree
.asInstanceOfExpr(
new ApplyFieldNamesType {
def apply[FromPathList <: PathList: WeakTypeTag]: c.WeakTypeTag[?] =
weakTypeTag[TransformerInto[From, To, RequireSourceFieldsExcept[FromPathList, Overrides], Flags]]
}.applyFromSelector(selectorFrom)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,99 @@ private[chimney] trait DslMacroUtils {
s"Expected function, instead got: $MAGENTA$t$RESET: $MAGENTA${t.tpe}$RESET"
}

private trait ExistentialPathList {
type Underlying <: runtime.PathList
val Underlying: c.WeakTypeTag[Underlying]
}
private object ExistentialPathList {
def parse(selectors: Seq[Tree]): Either[String, ExistentialPathList] =
selectors
.map(selector => ExistentialPath.parse(selector))
.foldLeft[Either[String, List[ExistentialPath]]](Right(Nil)) {
case (err @ Left(_), _) => err
case (_, Left(error)) => Left(error)
case (Right(acc), Right(path)) => Right(acc :+ path)
}
.map { params =>
new ExistentialPathList {
type Underlying = runtime.PathList
implicit val Underlying: WeakTypeTag[Underlying] = combine(params)
}
}

private def combine(paths: Seq[ExistentialPath]): WeakTypeTag[runtime.PathList] = {
object Combine {
def apply[A <: runtime.Path: WeakTypeTag, Args <: runtime.PathList: WeakTypeTag]
: WeakTypeTag[runtime.PathList.List[A, Args]] =
weakTypeTag[runtime.PathList.List[A, Args]]
}

paths
.foldLeft[WeakTypeTag[? <: runtime.PathList]](weakTypeTag[runtime.PathList.Empty]) { (acc, path) =>
Combine(path.Underlying, acc)
}
.asInstanceOf[WeakTypeTag[runtime.PathList]]
}

// selectors
// .map(ExistentialPath.parse)
//
// extractParams(t).map { params =>
// new ExistentialCtor {
// type Underlying = runtime.ArgumentLists
// implicit val Underlying: WeakTypeTag[runtime.ArgumentLists] = paramsToType(params)
// }
// }
// }
//
// private def paramsToType(paramsLists: List[List[ValDef]]): WeakTypeTag[runtime.ArgumentLists] =
// paramsLists
// .map { paramList =>
// paramList.foldRight[WeakTypeTag[? <: runtime.ArgumentList]](weakTypeTag[runtime.ArgumentList.Empty])(
// constructArgumentListType
// )
// }
// .foldRight[WeakTypeTag[? <: runtime.ArgumentLists]](weakTypeTag[runtime.ArgumentLists.Empty])(
// constructArgumentListsType
// )
// .asInstanceOf[WeakTypeTag[runtime.ArgumentLists]]
//
// private def constructArgumentListsType(
// head: WeakTypeTag[? <: runtime.ArgumentList],
// tail: WeakTypeTag[? <: runtime.ArgumentLists]
// ): WeakTypeTag[? <: runtime.ArgumentLists] = {
// object ApplyParams {
// def apply[Head <: runtime.ArgumentList: WeakTypeTag, Tail <: runtime.ArgumentLists: WeakTypeTag]
// : WeakTypeTag[runtime.ArgumentLists.List[Head, Tail]] =
// weakTypeTag[runtime.ArgumentLists.List[Head, Tail]]
// }
//
// ApplyParams(head, tail)
// }
//
// private def constructArgumentListType(
// t: ValDef,
// args: WeakTypeTag[? <: runtime.ArgumentList]
// ): WeakTypeTag[? <: runtime.ArgumentList] = {
// object ApplyParam {
// def apply[ParamName <: String: WeakTypeTag, ParamType: WeakTypeTag, Args <: runtime.ArgumentList: WeakTypeTag]
// : WeakTypeTag[runtime.ArgumentList.Argument[ParamName, ParamType, Args]] =
// weakTypeTag[runtime.ArgumentList.Argument[ParamName, ParamType, Args]]
// }
//
// ApplyParam(
// c.WeakTypeTag(c.internal.constantType(Constant(t.name.decodedName.toString))),
// c.WeakTypeTag(t.tpt.tpe),
// args
// )
// }
//
// import Console.*
//
// private def invalidConstructor(t: Tree): String =
// s"Expected function, instead got: $MAGENTA$t$RESET: $MAGENTA${t.tpe}$RESET"
}

// If we try to do:
//
// implicit val toPath = fieldName.Underlying
Expand Down Expand Up @@ -304,6 +397,16 @@ private[chimney] trait DslMacroUtils {
}
}

protected trait ApplyFieldNamesType {
def apply[A <: runtime.PathList: WeakTypeTag]: WeakTypeTag[?]

final def applyFromSelector(t: Seq[Tree]): WeakTypeTag[?] =
apply(extractSelectorAsType(t).Underlying)

private def extractSelectorAsType(t: Seq[Tree]): ExistentialPathList =
ExistentialPathList.parse(t).fold(error => c.abort(c.enclosingPosition, error), path => path)
}

/** Workaround for Java Enums, see [[io.scalaland.chimney.internal.runtime.RefinedJavaEnum]]. */
protected trait ApplyFixedCoproductType {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,18 @@ final class TransformerInto[From, To, Overrides <: TransformerOverrides, Flags <
)(using IsFunction.Of[Ctor, To]): TransformerInto[From, To, ? <: TransformerOverrides, Flags] =
${ TransformerIntoMacros.withConstructorImpl('this, 'f) }

/** Require that all fields of the source object except fields mentioned in `selectorFrom` are used in the
* transformation. and fail compilation otherwise.
*
* @param selectorFrom
* exception fields that are not required to be used in the transformation
* @return
*/
transparent inline def requireSourceFieldsUsedExcept(
inline selectorFrom: From => Any*
): TransformerInto[From, To, ? <: TransformerOverrides, Flags] =
${ TransformerIntoMacros.requireSourceFieldsUsedExceptImpl('this, 'selectorFrom) }

/** Apply configured transformation in-place.
*
* It runs macro that tries to derive instance of `Transformer[From, To]` and immediately apply it to captured
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,25 @@ private[compiletime] trait ChimneyTypesPlatform extends ChimneyTypes { this: Chi
case _ => scala.None
}
}

object RequireSourceFieldsExcept extends RequireSourceFieldsExceptModule {
def apply[
FromPathList <: runtime.PathList: Type,
Tail <: runtime.TransformerOverrides: Type
]: Type[runtime.TransformerOverrides.RequireSourceFieldsExcept[FromPathList, Tail]] =
quoted.Type.of[runtime.TransformerOverrides.RequireSourceFieldsExcept[FromPathList, Tail]]
def unapply[A](tpe: Type[A]): Option[(?<[runtime.PathList], ?<[runtime.TransformerOverrides])] =
tpe match {
case '[runtime.TransformerOverrides.RequireSourceFieldsExcept[fromPath, cfg]] =>
Some(
(
Type[fromPath].as_?<[runtime.PathList],
Type[cfg].as_?<[runtime.TransformerOverrides]
)
)
case _ => scala.None
}
}
}

object TransformerFlags extends TransformerFlagsModule {
Expand Down Expand Up @@ -405,6 +424,19 @@ private[compiletime] trait ChimneyTypesPlatform extends ChimneyTypes { this: Chi
}
}

object PathList extends PathListModule {
val Empty: Type[runtime.PathList.Empty] = quoted.Type.of[runtime.PathList.Empty]
object List extends ListModule {
def apply[Head <: runtime.Path: Type, Tail <: runtime.PathList: Type]: Type[runtime.PathList.List[Head, Tail]] =
quoted.Type.of[runtime.PathList.List[Head, Tail]]
def unapply[A](tpe: Type[A]): Option[(?<[runtime.Path], ?<[runtime.PathList])] = tpe match {
case '[runtime.PathList.List[head, tail]] =>
Some((Type[head].as_?<[runtime.Path], Type[tail].as_?<[runtime.PathList]))
case _ => scala.None
}
}
}

object DefaultValue extends DefaultValueModule {
def apply[Value: Type]: Type[integrations.DefaultValue[Value]] =
quoted.Type.of[integrations.DefaultValue[Value]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import io.scalaland.chimney.internal.compiletime.dsl.utils.DslMacroUtils
import io.scalaland.chimney.internal.runtime.{
ArgumentLists,
Path,
PathList,
TransformerFlags,
TransformerOverrides,
WithRuntimeDataStore
Expand Down Expand Up @@ -143,4 +144,22 @@ object TransformerIntoMacros {
.asInstanceOf[TransformerInto[From, To, Constructor[args, Path.Root, Overrides], Flags]]
}
}(f)

def requireSourceFieldsUsedExceptImpl[
From: Type,
To: Type,
Overrides <: TransformerOverrides: Type,
Flags <: TransformerFlags: Type,
Ctor: Type
](
ti: Expr[TransformerInto[From, To, Overrides, Flags]],
selectorFrom: Expr[Seq[From => Any]]
)(using Quotes): Expr[TransformerInto[From, To, ? <: TransformerOverrides, Flags]] =
DslMacroUtils().applyRequireSourceFieldsExceptType {
[args <: PathList] =>
(_: Type[args]) ?=>
'{
$ti.asInstanceOf[TransformerInto[From, To, RequireSourceFieldsExcept[args, Overrides], Flags]]
}
}(selectorFrom)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.scalaland.chimney.internal.compiletime.dsl.utils

import io.scalaland.chimney.internal.runtime
import io.scalaland.chimney.internal.runtime.PathList

import scala.annotation.nowarn
import scala.quoted.*
Expand Down Expand Up @@ -284,6 +285,44 @@ private[chimney] class DslMacroUtils()(using quotes: Quotes) {
s"Expected function, instead got: ${t.show(using Printer.TreeAnsiCode)}: ${t.asInstanceOf[Term].tpe.show(using Printer.TypeReprAnsiCode)}"
}

private trait ExistentialPathList {
type Underlying <: runtime.PathList
implicit val Underlying: Type[Underlying]
}
private object ExistentialPathList {
def parse(t: Expr[Seq[?]]): Either[String, ExistentialPathList] =
(t match {
case Varargs(selectors: Seq[Expr[Any]]) =>
selectors
.map(selector => ExistentialPath.parse(selector.asTerm))
.foldLeft[Either[String, List[ExistentialPath]]](Right(Nil)) {
case (err @ Left(_), _) => err
case (Right(acc), Left(error)) => Left(error)
case (Right(acc), Right(path)) => Right(acc :+ path)
}
}).map { params =>
val value1: Type[PathList] = combine(params)

new ExistentialPathList {
type Underlying = runtime.PathList
implicit val Underlying: Type[Underlying] = value1
}
}

private def combine(paths: Seq[ExistentialPath]): Type[runtime.PathList] = {
object Combine {
def apply[A <: runtime.Path: Type, Args <: runtime.PathList: Type]: Type[runtime.PathList.List[A, Args]] =
Type.of[runtime.PathList.List[A, Args]]
}

paths
.foldLeft[Type[? <: runtime.PathList]](Type.of[runtime.PathList.Empty]) { (acc, path) =>
Combine(path.Underlying, acc)
}
.asInstanceOf[Type[runtime.PathList]]
}
}

def applyFieldNameType[Out](f: [A <: runtime.Path] => Type[A] ?=> Out)(selector: Expr[?]): Out =
ExistentialPath
.parse(selector.asTerm)
Expand All @@ -307,4 +346,12 @@ private[chimney] class DslMacroUtils()(using quotes: Quotes) {
case Right(ctorType) => f(using ctorType.Underlying)
case Left(error) => report.errorAndAbort(error, Position.ofMacroExpansion)
}

def applyRequireSourceFieldsExceptType[Out](
f: [A <: runtime.PathList] => Type[A] ?=> Out
)(fieldSelectors: Expr[Seq[?]]): Out =
ExistentialPathList.parse(fieldSelectors) match {
case Right(pathList) => f(using pathList.Underlying)
case Left(error) => report.errorAndAbort(error, Position.ofMacroExpansion)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,14 @@ private[compiletime] trait ChimneyTypes { this: ChimneyDefinitions =>
runtime.TransformerOverrides,
runtime.TransformerOverrides.RenamedTo
] { this: RenamedTo.type => }

val RequireSourceFieldsExcept: RequireSourceFieldsExceptModule
trait RequireSourceFieldsExceptModule
extends Type.Ctor2UpperBounded[
runtime.PathList,
runtime.TransformerOverrides,
runtime.TransformerOverrides.RequireSourceFieldsExcept
] { this: RequireSourceFieldsExcept.type => }
}

val TransformerFlags: TransformerFlagsModule
Expand Down Expand Up @@ -289,6 +297,23 @@ private[compiletime] trait ChimneyTypes { this: ChimneyDefinitions =>
] { this: EveryMapValue.type => }
}

val PathList: PathListModule
trait PathListModule {
this: PathList.type =>
val Empty: Type[runtime.PathList.Empty]

val List: ListModule

trait ListModule
extends Type.Ctor2UpperBounded[
runtime.Path,
runtime.PathList,
runtime.PathList.List
] {
this: List.type =>
}
}

val DefaultValue: DefaultValueModule
trait DefaultValueModule extends Type.Ctor1[integrations.DefaultValue] { this: DefaultValue.type => }

Expand Down
Loading
Loading