From 3e11cc4a7b2a1822241f0a8aa4df1ede4edff267 Mon Sep 17 00:00:00 2001 From: arainko Date: Tue, 13 Sep 2022 20:37:46 +0200 Subject: [PATCH 1/8] initial work, argSelector broken --- .../arainko/ducktape/ArgBuilderConfig.scala | 38 +++---- .../github/arainko/ducktape/Transformer.scala | 2 +- .../ducktape/builder/AppliedViaBuilder.scala | 11 +- .../builder/DefinitionViaBuilder.scala | 46 ++++---- .../function/FunctionArgSelector.scala | 13 +++ .../ducktape/function/FunctionArguments.scala | 3 +- .../internal/macros/FunctionMacros.scala | 19 ++-- .../macros/ProductTransformerMacros.scala | 16 +-- .../modules/ConfigurationModule.scala | 45 ++++---- .../internal/modules/FieldModule.scala | 1 + .../internal/modules/SelectorModule.scala | 10 +- .../builder/AppliedViaBuilderSuite.scala | 83 +++++++------- .../builder/DefinitionViaBuilderSuite.scala | 102 +++++++++--------- 13 files changed, 208 insertions(+), 181 deletions(-) create mode 100644 ducktape/src/main/scala/io/github/arainko/ducktape/function/FunctionArgSelector.scala diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/ArgBuilderConfig.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/ArgBuilderConfig.scala index 22d25b4c..acc2b0e3 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/ArgBuilderConfig.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/ArgBuilderConfig.scala @@ -4,35 +4,35 @@ import io.github.arainko.ducktape.function.FunctionArguments import scala.deriving.Mirror import scala.annotation.implicitNotFound -opaque type ArgBuilderConfig[Source, Dest, NamedArgs <: Tuple] = Unit +opaque type ArgBuilderConfig[Source, Dest, ArgSelector <: FunctionArguments[_]] = Unit object ArgBuilderConfig { - private[ducktape] def instance[Source, Dest, NamedArgs <: Tuple]: ArgBuilderConfig[Source, Dest, NamedArgs] = () + private[ducktape] def instance[Source, Dest, ArgSelector <: FunctionArguments[_]]: ArgBuilderConfig[Source, Dest, ArgSelector] = () } object Arg { - def const[Source, Dest, ArgType, ActualType, NamedArgs <: Tuple]( - selector: FunctionArguments[NamedArgs] => ArgType, + def const[Source, Dest, ArgType, ActualType, ArgSelector <: FunctionArguments[_]]( + selector: ArgSelector => ArgType, const: ActualType )(using @implicitNotFound("Arg.const is only supported for product types but ${Source} is not a product type.") ev: Mirror.ProductOf[Source] - ): ArgBuilderConfig[Source, Dest, NamedArgs] = ArgBuilderConfig.instance + ): ArgBuilderConfig[Source, Dest, ArgSelector] = ArgBuilderConfig.instance - def computed[Source, Dest, ArgType, ActualType, NamedArgs <: Tuple]( - selector: FunctionArguments[NamedArgs] => ArgType, - f: Source => ActualType - )(using - @implicitNotFound("Arg.computed is only supported for product types but ${Source} is not a product type.") - ev: Mirror.ProductOf[Source] - ): ArgBuilderConfig[Source, Dest, NamedArgs] = ArgBuilderConfig.instance + // def computed[Source, Dest, ArgType, ActualType, ArgSelector <: FunctionArguments[_]]( + // selector: ArgSelector => ArgType, + // f: Source => ActualType + // )(using + // @implicitNotFound("Arg.computed is only supported for product types but ${Source} is not a product type.") + // ev: Mirror.ProductOf[Source] + // ): ArgBuilderConfig[Source, Dest, ArgSelector] = ArgBuilderConfig.instance - def renamed[Source, Dest, ArgType, FieldType, NamedArgs <: Tuple]( - destSelector: FunctionArguments[NamedArgs] => ArgType, - sourceSelector: Source => FieldType - )(using - @implicitNotFound("Arg.renamed is only supported for product types but ${Source} is not a product type.") - ev: Mirror.ProductOf[Source] - ): ArgBuilderConfig[Source, Dest, NamedArgs] = ArgBuilderConfig.instance + // def renamed[Source, Dest, ArgType, FieldType, ArgSelector <: FunctionArguments[_]]( + // destSelector: ArgSelector => ArgType, + // sourceSelector: Source => FieldType + // )(using + // @implicitNotFound("Arg.renamed is only supported for product types but ${Source} is not a product type.") + // ev: Mirror.ProductOf[Source] + // ): ArgBuilderConfig[Source, Dest, ArgSelector] = ArgBuilderConfig.instance } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/Transformer.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/Transformer.scala index 57a1a8e4..586970b1 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/Transformer.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/Transformer.scala @@ -18,7 +18,7 @@ object Transformer { def define[Source, Dest]: DefinitionBuilder[Source, Dest] = DefinitionBuilder[Source, Dest] - def defineVia[A]: DefinitionViaBuilder.PartiallyApplied[A] = DefinitionViaBuilder.create[A] + // def defineVia[A]: DefinitionViaBuilder.PartiallyApplied[A] = DefinitionViaBuilder.create[A] sealed trait Identity[Source] extends Transformer[Source, Source] diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/builder/AppliedViaBuilder.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/builder/AppliedViaBuilder.scala index f632a2e2..0db41d82 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/builder/AppliedViaBuilder.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/builder/AppliedViaBuilder.scala @@ -3,21 +3,20 @@ package io.github.arainko.ducktape.builder import io.github.arainko.ducktape.* import io.github.arainko.ducktape.internal.macros.* import scala.deriving.Mirror -import io.github.arainko.ducktape.function.FunctionMirror -import io.github.arainko.ducktape.function.NamedArgument +import io.github.arainko.ducktape.function.* import scala.compiletime.* -sealed abstract class AppliedViaBuilder[Source, Dest, Func, NamedArguments <: Tuple](source: Source, function: Func) { +sealed abstract class AppliedViaBuilder[Source, Dest, Func, NamedArguments <: Tuple, ArgSelector <: FunctionArguments[?]](source: Source, function: Func) { inline def transform( - inline config: ArgBuilderConfig[Source, Dest, NamedArguments]* + inline config: ArgBuilderConfig[Source, Dest, ArgSelector]* )(using Source: Mirror.ProductOf[Source]): Dest = - ProductTransformerMacros.viaConfigured[Source, Dest, Func, NamedArguments](source, function, config*) + ProductTransformerMacros.viaConfigured[Source, Dest, Func, NamedArguments, ArgSelector](source, function, config*) } object AppliedViaBuilder { transparent inline def create[Source, Func](source: Source, inline func: Func)(using Func: FunctionMirror[Func]) = { - val builder = new AppliedViaBuilder[Source, Func.Return, Func, Nothing](source, func) {} + val builder = new AppliedViaBuilder[Source, Func.Return, Func, Nothing, Nothing](source, func) {} FunctionMacros.namedArguments(func, builder) } } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/builder/DefinitionViaBuilder.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/builder/DefinitionViaBuilder.scala index b7857b3c..98d89e86 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/builder/DefinitionViaBuilder.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/builder/DefinitionViaBuilder.scala @@ -1,29 +1,29 @@ -package io.github.arainko.ducktape.builder +// package io.github.arainko.ducktape.builder -import io.github.arainko.ducktape.* -import io.github.arainko.ducktape.internal.macros.* -import scala.deriving.* -import io.github.arainko.ducktape.function.FunctionMirror +// import io.github.arainko.ducktape.* +// import io.github.arainko.ducktape.internal.macros.* +// import scala.deriving.* +// import io.github.arainko.ducktape.function.FunctionMirror -sealed abstract class DefinitionViaBuilder[Source, Dest, Func, NamedArguments <: Tuple](function: Func) { +// sealed abstract class DefinitionViaBuilder[Source, Dest, Func, NamedArguments <: Tuple](function: Func) { - inline def build( - inline config: ArgBuilderConfig[Source, Dest, NamedArguments]* - )(using Mirror.ProductOf[Source]): Transformer[Source, Dest] = from => - ProductTransformerMacros.viaConfigured[Source, Dest, Func, NamedArguments](from, function, config*) -} +// inline def build( +// inline config: ArgBuilderConfig[Source, Dest, NamedArguments]* +// )(using Mirror.ProductOf[Source]): Transformer[Source, Dest] = from => +// ProductTransformerMacros.viaConfigured[Source, Dest, Func, NamedArguments](from, function, config*) +// } -object DefinitionViaBuilder { - def create[Source]: PartiallyApplied[Source] = () +// object DefinitionViaBuilder { +// def create[Source]: PartiallyApplied[Source] = () - opaque type PartiallyApplied[Source] = Unit +// opaque type PartiallyApplied[Source] = Unit - object PartiallyApplied { - extension [Source](partial: PartiallyApplied[Source]) { - transparent inline def apply[Func](inline func: Func)(using Func: FunctionMirror[Func]) = { - val builder = new DefinitionViaBuilder[Source, Func.Return, Func, Nothing](func) {} - FunctionMacros.namedArguments(func, builder) - } - } - } -} +// object PartiallyApplied { +// extension [Source](partial: PartiallyApplied[Source]) { +// transparent inline def apply[Func](inline func: Func)(using Func: FunctionMirror[Func]) = { +// val builder = new DefinitionViaBuilder[Source, Func.Return, Func, Nothing](func) {} +// FunctionMacros.namedArguments(func, builder) +// } +// } +// } +// } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/function/FunctionArgSelector.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/function/FunctionArgSelector.scala new file mode 100644 index 00000000..49bc33b3 --- /dev/null +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/function/FunctionArgSelector.scala @@ -0,0 +1,13 @@ +package io.github.arainko.ducktape.function + +abstract class FunctionArgSelector[NamedArgs <: Tuple] extends Selectable { + def selectDynamic(value: String): NamedArgument.FindByName[value.type, NamedArgs] +} + +@main def main = { + val selector: FunctionArgSelector[NamedArgument["name", Int] *: EmptyTuple] { + val name: Int + } = ??? + + val asd = selector.name +} diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/function/FunctionArguments.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/function/FunctionArguments.scala index a65775ba..c75750e5 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/function/FunctionArguments.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/function/FunctionArguments.scala @@ -1,9 +1,8 @@ package io.github.arainko.ducktape.function import scala.annotation.implicitNotFound -import scala.language.dynamics import scala.util.NotGiven -sealed trait FunctionArguments[NamedArgs <: Tuple] extends Dynamic { +sealed trait FunctionArguments[NamedArgs <: Tuple] extends Selectable { def selectDynamic(value: String): NamedArgument.FindByName[value.type, NamedArgs] } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/FunctionMacros.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/FunctionMacros.scala index 47caa604..1f80879f 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/FunctionMacros.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/FunctionMacros.scala @@ -28,13 +28,19 @@ class FunctionMacros(using val quotes: Quotes) extends Module, SelectorModule, F case other => report.errorAndAbort(s"FunctionMirrors can only be created for functions. Got ${other.show} instead.") } - def namedArguments[Func: Type, F[_ <: Tuple]: Type](function: Expr[Func], initial: Expr[F[Nothing]]) = + def namedArguments[Func: Type, F[_ <: Tuple, _ <: FunctionArguments[?]]: Type](function: Expr[Func], initial: Expr[F[Nothing, Nothing]]) = function.asTerm match { case func @ FunctionLambda(vals, body) => val namedArg = TypeRepr.of[NamedArgument] + val wrapper = TypeRepr.of[F] val args = vals.map(valdef => namedArg.appliedTo(ConstantType(StringConstant(valdef.name)) :: valdef.tpt.tpe :: Nil)) - tupleify(args).asType match { - case '[IsTuple[namedArgs]] => '{ $initial.asInstanceOf[F[namedArgs]] } + val argTuple = tupleify(args) + val functionArguments = TypeRepr.of[FunctionArguments].appliedTo(argTuple) + val refinedFunctionArgs = + vals.foldLeft(functionArguments)((tpe, valDef) => Refinement(tpe, valDef.name, valDef.tpt.tpe)) + // val appliedArgs = argT + tupleify(args).asType -> refinedFunctionArgs.asType match { + case ('[IsTuple[namedArgs]], '[IsFuncArgs[args]]) => '{ $initial.asInstanceOf[F[namedArgs, args]] } } case other => report.errorAndAbort(s"Failed to extract named arguments from ${other.show}") @@ -50,17 +56,18 @@ class FunctionMacros(using val quotes: Quotes) extends Module, SelectorModule, F private[ducktape] object FunctionMacros { private type IsTuple[A <: Tuple] = A + private type IsFuncArgs[A <: FunctionArguments[?]] = A transparent inline def createMirror[F]: FunctionMirror[F] = ${ createMirrorMacro[F] } def createMirrorMacro[Func: Type](using Quotes): Expr[FunctionMirror[Func]] = FunctionMacros().createMirror[Func] - transparent inline def namedArguments[Func, F[_ <: Tuple]]( + transparent inline def namedArguments[Func, F[_ <: Tuple, _ <: FunctionArguments[?]]]( inline function: Func, - initial: F[Nothing] + initial: F[Nothing, Nothing] )(using FunctionMirror[Func]) = ${ namedArgumentsMacro[Func, F]('function, 'initial) } - def namedArgumentsMacro[Func: Type, F[_ <: Tuple]: Type](function: Expr[Func], initial: Expr[F[Nothing]])(using Quotes) = + def namedArgumentsMacro[Func: Type, F[_ <: Tuple, _ <: FunctionArguments[?]]: Type](function: Expr[Func], initial: Expr[F[Nothing, Nothing]])(using Quotes) = FunctionMacros().namedArguments[Func, F](function, initial) } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/ProductTransformerMacros.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/ProductTransformerMacros.scala index f42658d9..b8407498 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/ProductTransformerMacros.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/ProductTransformerMacros.scala @@ -31,10 +31,10 @@ private[ducktape] class ProductTransformerMacros(using val quotes: Quotes) case other => report.errorAndAbort(s"'via' is only supported on eta-expanded methods!") } - def viaConfigured[Source: Type, Dest: Type, Func: Type, NamedArgs <: Tuple: Type]( + def viaConfigured[Source: Type, Dest: Type, Func: Type, NamedArgs <: Tuple: Type, ArgSelector <: FunctionArguments[?]: Type]( sourceValue: Expr[Source], function: Expr[Func], - config: Expr[Seq[ArgBuilderConfig[Source, Dest, NamedArgs]]], + config: Expr[Seq[ArgBuilderConfig[Source, Dest, ArgSelector]]], Source: Expr[Mirror.ProductOf[Source]] ): Expr[Dest] = { given Fields.Source = Fields.Source.fromMirror(Source) @@ -200,20 +200,20 @@ private[ducktape] object ProductTransformerMacros { )(using Quotes) = ProductTransformerMacros().via(source, function, Func, Source) - inline def viaConfigured[Source, Dest, Func, NamedArgs <: Tuple]( + inline def viaConfigured[Source, Dest, Func, NamedArgs <: Tuple, ArgSelector <: FunctionArguments[?]]( source: Source, inline function: Func, - inline config: ArgBuilderConfig[Source, Dest, NamedArgs]* + inline config: ArgBuilderConfig[Source, Dest, ArgSelector]* )(using Source: Mirror.ProductOf[Source]): Dest = - ${ viaConfiguredMacro('source, 'function, 'config, 'Source) } + ${ viaConfiguredMacro[Source, Dest, Func, NamedArgs, ArgSelector]('source, 'function, 'config, 'Source) } - def viaConfiguredMacro[Source: Type, Dest: Type, Func: Type, NamedArgs <: Tuple: Type]( + def viaConfiguredMacro[Source: Type, Dest: Type, Func: Type, NamedArgs <: Tuple: Type, ArgSelector <: FunctionArguments[?]: Type]( sourceValue: Expr[Source], function: Expr[Func], - config: Expr[Seq[ArgBuilderConfig[Source, Dest, NamedArgs]]], + config: Expr[Seq[ArgBuilderConfig[Source, Dest, ArgSelector]]], A: Expr[Mirror.ProductOf[Source]] )(using Quotes) = - ProductTransformerMacros().viaConfigured(sourceValue, function, config, A) + ProductTransformerMacros().viaConfigured[Source, Dest, Func, NamedArgs, ArgSelector](sourceValue, function, config, A) inline def transformConfigured[Source, Dest](sourceValue: Source, inline config: BuilderConfig[Source, Dest]*)(using Source: Mirror.ProductOf[Source], diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/ConfigurationModule.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/ConfigurationModule.scala index d8501acb..06f04d11 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/ConfigurationModule.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/ConfigurationModule.scala @@ -1,6 +1,7 @@ package io.github.arainko.ducktape.internal.modules import io.github.arainko.ducktape.{ Case => CaseConfig, Field => FieldConfig, _ } +import io.github.arainko.ducktape.function.FunctionArguments import scala.quoted.* @@ -36,8 +37,8 @@ private[internal] trait ConfigurationModule { self: Module & SelectorModule & Mi .map((_, fieldConfigs) => fieldConfigs.last) // keep the last applied field config only .toList - def materializeArgConfig[Source, Dest, NamedArguments <: Tuple]( - config: Expr[Seq[ArgBuilderConfig[Source, Dest, NamedArguments]]] + def materializeArgConfig[Source, Dest, ArgSelector <: FunctionArguments[?]]( + config: Expr[Seq[ArgBuilderConfig[Source, Dest, ArgSelector]]] )(using Fields.Source, Fields.Dest): List[Product] = Varargs .unapply(config) @@ -110,34 +111,34 @@ private[internal] trait ConfigurationModule { self: Module & SelectorModule & Mi * * TODO: See if it works properly, if not we may need to go back ot the old encoding with the evidence and bad error messages */ - private def materializeSingleArgConfig[Source, Dest, NamedArguments <: Tuple]( - config: Expr[ArgBuilderConfig[Source, Dest, NamedArguments]] + private def materializeSingleArgConfig[Source, Dest, ArgSelector <: FunctionArguments[?]]( + config: Expr[ArgBuilderConfig[Source, Dest, ArgSelector]] )(using Fields.Source, Fields.Dest): Product = config match { case '{ - type namedArgs <: Tuple + type namedArgs <: FunctionArguments[?] Arg.const[source, dest, argType, actualType, `namedArgs`]($selector, $const)(using $ev1) } => - val argName = Selectors.argName(Fields.dest, selector) + val argName = Selectors.argName(Fields.dest, selector.asInstanceOf[Expr[FunctionArguments[?] => Any]]) verifyArgSelectorTypes(argName, const, TypeRepr.of[argType], TypeRepr.of[actualType]) Product.Const(argName, const) - case '{ - type namedArgs <: Tuple - Arg.computed[source, dest, argType, actualType, `namedArgs`]($selector, $function)(using $ev1) - } => - val argName = Selectors.argName(Fields.dest, selector) - verifyArgSelectorTypes(argName, function, TypeRepr.of[argType], TypeRepr.of[actualType]) - Product.Computed(argName, function.asInstanceOf[Expr[Any => Any]]) - - case '{ - type namedArgs <: Tuple - Arg.renamed[source, dest, argType, fieldType, `namedArgs`]($destSelector, $sourceSelector)(using $ev1) - } => - val argName = Selectors.argName(Fields.dest, destSelector) - val fieldName = Selectors.fieldName(Fields.source, sourceSelector) - verifyArgSelectorTypes(argName, sourceSelector, TypeRepr.of[argType], TypeRepr.of[fieldType]) - Product.Renamed(argName, fieldName) + // case '{ + // type namedArgs <: Tuple + // Arg.computed[source, dest, argType, actualType, `namedArgs`]($selector, $function)(using $ev1) + // } => + // val argName = Selectors.argName(Fields.dest, selector) + // verifyArgSelectorTypes(argName, function, TypeRepr.of[argType], TypeRepr.of[actualType]) + // Product.Computed(argName, function.asInstanceOf[Expr[Any => Any]]) + + // case '{ + // type namedArgs <: Tuple + // Arg.renamed[source, dest, argType, fieldType, `namedArgs`]($destSelector, $sourceSelector)(using $ev1) + // } => + // val argName = Selectors.argName(Fields.dest, destSelector) + // val fieldName = Selectors.fieldName(Fields.source, sourceSelector) + // verifyArgSelectorTypes(argName, sourceSelector, TypeRepr.of[argType], TypeRepr.of[fieldType]) + // Product.Renamed(argName, fieldName) case other => abort(Failure.UnsupportedConfig(other, Failure.ConfigType.Arg)) } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/FieldModule.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/FieldModule.scala index 7b3c4fbe..f972ae4e 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/FieldModule.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/FieldModule.scala @@ -50,6 +50,7 @@ private[internal] trait FieldModule { self: Module & MirrorModule => val name = Type.valueOfConstant[name].getOrElse(report.errorAndAbort("Not a constant named arg name")) val field = Field(name, TypeRepr.of[tpe]) field :: listOfFields[tail] + case other => report.errorAndAbort(s"Failed here with ${Type.show[NamedArgs]}") } apply(listOfFields[NamedArgs]) } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/SelectorModule.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/SelectorModule.scala index badee2ba..e2223066 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/SelectorModule.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/SelectorModule.scala @@ -20,9 +20,9 @@ private[internal] trait SelectorModule { self: Module & MirrorModule & FieldModu abort(Failure.InvalidFieldSelector(other, TypeRepr.of[From], Suggestion.fromFields(validFields))) } - def argName[NamedArgs <: Tuple: Type, ArgType: Type]( + def argName[ArgType: Type]( validArgs: Fields, - selector: Expr[FunctionArguments[NamedArgs] => ArgType] + selector: Expr[FunctionArguments[?] => ArgType] ): String = selector.asTerm match { case ArgSelector(argumentName) if validArgs.containsFieldWithName(argumentName) => @@ -30,6 +30,7 @@ private[internal] trait SelectorModule { self: Module & MirrorModule & FieldModu case ArgSelector(argumentName) => abort(Failure.InvalidArgSelector.NotFound(selector, argumentName, Suggestion.fromFields(validArgs))) case other => + report.errorAndAbort(other.show) abort(Failure.InvalidArgSelector.NotAnArgSelector(selector, Suggestion.fromFields(validArgs))) } @@ -61,7 +62,10 @@ private[internal] trait SelectorModule { self: Module & MirrorModule & FieldModu private object DynamicSelector { def unapply(arg: Term): Option[String] = PartialFunction.condOpt(arg.asExpr) { - case '{ ($args: FunctionArguments[namedArgs]).selectDynamic($selectedArg) } => selectedArg.valueOrAbort + case '{ + type argSelector <: FunctionArguments[?] + ($args: `argSelector`).selectDynamic($selectedArg).asInstanceOf[argTpe] + } => selectedArg.valueOrAbort } } } diff --git a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala index c92e2430..3a9e3807 100644 --- a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala +++ b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala @@ -3,6 +3,9 @@ package io.github.arainko.ducktapetest.builder import io.github.arainko.ducktapetest.DucktapeSuite import io.github.arainko.ducktapetest.builder.AppliedViaBuilderSuite.* import io.github.arainko.ducktape.* +import io.github.arainko.ducktape.function.NamedArgument +import io.github.arainko.ducktape.function.FunctionArguments +import io.github.arainko.ducktape.builder.AppliedViaBuilder class AppliedViaBuilderSuite extends DucktapeSuite { private val testClass = TestClass("str", 1) @@ -17,60 +20,60 @@ class AppliedViaBuilderSuite extends DucktapeSuite { .intoVia(method) .transform(Arg.const(_.additionalArg, List("const"))) - assertEquals(actual, expected) + // assertEquals(actual, expected) } - test("Arg.computed properly applies a function to an argument") { - def method(str: String, int: Int, additionalArg: List[String]) = TestClassWithAdditionalList(int, str, additionalArg) + // test("Arg.computed properly applies a function to an argument") { + // def method(str: String, int: Int, additionalArg: List[String]) = TestClassWithAdditionalList(int, str, additionalArg) - val expected = TestClassWithAdditionalList(1, "str", List("str")) + // val expected = TestClassWithAdditionalList(1, "str", List("str")) - val actual = - testClass - .intoVia(method) - .transform(Arg.computed(_.additionalArg, testClass => List(testClass.str))) + // val actual = + // testClass + // .intoVia(method) + // .transform(Arg.computed(_.additionalArg, testClass => List(testClass.str))) - assertEquals(actual, expected) - } + // assertEquals(actual, expected) + // } - test("Arg.renamed properly uses a different field for that argument") { - def method(str: String, int: Int, additionalArg: String) = TestClassWithAdditionalString(int, str, additionalArg) + // test("Arg.renamed properly uses a different field for that argument") { + // def method(str: String, int: Int, additionalArg: String) = TestClassWithAdditionalString(int, str, additionalArg) - val expected = TestClassWithAdditionalString(1, "str", "str") + // val expected = TestClassWithAdditionalString(1, "str", "str") - val actual = - testClass - .intoVia(method) - .transform(Arg.renamed(_.additionalArg, _.str)) + // val actual = + // testClass + // .intoVia(method) + // .transform(Arg.renamed(_.additionalArg, _.str)) - assertEquals(actual, expected) - } + // assertEquals(actual, expected) + // } - test("The last ") { - def method(str: String, int: Int, additionalArg: String) = TestClassWithAdditionalString(int, str, additionalArg) + // test("The last ") { + // def method(str: String, int: Int, additionalArg: String) = TestClassWithAdditionalString(int, str, additionalArg) - val expected = TestClassWithAdditionalString(1, "str", "str") + // val expected = TestClassWithAdditionalString(1, "str", "str") - val actual = - testClass - .intoVia(method) - .transform(Arg.renamed(_.additionalArg, _.str)) + // val actual = + // testClass + // .intoVia(method) + // .transform(Arg.renamed(_.additionalArg, _.str)) - assertEquals(actual, expected) - } + // assertEquals(actual, expected) + // } - test("Builder reports a missing argument") { - assertFailsToCompileWith { - """ - def method(str: String, int: Int, additionalArg: String) = TestClassWithAdditionalString(int, str, additionalArg) - - val actual = - testClass - .intoVia(method) - .transform() - """ - }("No field named 'additionalArg' found in TestClass") - } + // test("Builder reports a missing argument") { + // assertFailsToCompileWith { + // """ + // def method(str: String, int: Int, additionalArg: String) = TestClassWithAdditionalString(int, str, additionalArg) + + // val actual = + // testClass + // .intoVia(method) + // .transform() + // """ + // }("No field named 'additionalArg' found in TestClass") + // } } object AppliedViaBuilderSuite { diff --git a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/DefinitionViaBuilderSuite.scala b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/DefinitionViaBuilderSuite.scala index 450e179e..ea41a379 100644 --- a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/DefinitionViaBuilderSuite.scala +++ b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/DefinitionViaBuilderSuite.scala @@ -1,69 +1,69 @@ -package io.github.arainko.ducktapetest.builder +// package io.github.arainko.ducktapetest.builder -import io.github.arainko.ducktapetest.* -import io.github.arainko.ducktape.* -import io.github.arainko.ducktapetest.builder.DefinitionViaBuilderSuite.* -import munit.* +// import io.github.arainko.ducktapetest.* +// import io.github.arainko.ducktape.* +// import io.github.arainko.ducktapetest.builder.DefinitionViaBuilderSuite.* +// import munit.* -class DefinitionViaBuilderSuite extends DucktapeSuite { - private val testClass = TestClass("str", 1) +// class DefinitionViaBuilderSuite extends DucktapeSuite { +// private val testClass = TestClass("str", 1) - test("Arg.const properly applies a constant to an argument") { - def method(str: String, int: Int, additionalArg: List[String]) = TestClassWithAdditionalList(int, str, additionalArg) +// test("Arg.const properly applies a constant to an argument") { +// def method(str: String, int: Int, additionalArg: List[String]) = TestClassWithAdditionalList(int, str, additionalArg) - val expected = TestClassWithAdditionalList(1, "str", List("const")) +// val expected = TestClassWithAdditionalList(1, "str", List("const")) - val transformer = - Transformer - .defineVia[TestClass](method) - .build(Arg.const(_.additionalArg, List("const"))) +// val transformer = +// Transformer +// .defineVia[TestClass](method) +// .build(Arg.const(_.additionalArg, List("const"))) - assertEquals(transformer.transform(testClass), expected) - } +// assertEquals(transformer.transform(testClass), expected) +// } - test("Arg.computed properly applies a function to an argument") { - def method(str: String, int: Int, additionalArg: List[String]) = TestClassWithAdditionalList(int, str, additionalArg) +// test("Arg.computed properly applies a function to an argument") { +// def method(str: String, int: Int, additionalArg: List[String]) = TestClassWithAdditionalList(int, str, additionalArg) - val expected = TestClassWithAdditionalList(1, "str", List("str")) +// val expected = TestClassWithAdditionalList(1, "str", List("str")) - val transformer = - Transformer - .defineVia[TestClass](method) - .build(Arg.computed(_.additionalArg, testClass => List(testClass.str))) +// val transformer = +// Transformer +// .defineVia[TestClass](method) +// .build(Arg.computed(_.additionalArg, testClass => List(testClass.str))) - assertEquals(transformer.transform(testClass), expected) - } +// assertEquals(transformer.transform(testClass), expected) +// } - test("Arg.renamed properly uses a different field for that argument") { - def method(str: String, int: Int, additionalArg: String) = TestClassWithAdditionalString(int, str, additionalArg) +// test("Arg.renamed properly uses a different field for that argument") { +// def method(str: String, int: Int, additionalArg: String) = TestClassWithAdditionalString(int, str, additionalArg) - val expected = TestClassWithAdditionalString(1, "str", "str") +// val expected = TestClassWithAdditionalString(1, "str", "str") - val transformer = - Transformer - .defineVia[TestClass](method) - .build(Arg.renamed(_.additionalArg, _.str)) +// val transformer = +// Transformer +// .defineVia[TestClass](method) +// .build(Arg.renamed(_.additionalArg, _.str)) - assertEquals(transformer.transform(testClass), expected) - } +// assertEquals(transformer.transform(testClass), expected) +// } - test("Builder reports a missing argument") { - assertFailsToCompileWith { - """ - def method(str: String, int: Int, additionalArg: String) = TestClassWithAdditionalString(int, str, additionalArg) +// test("Builder reports a missing argument") { +// assertFailsToCompileWith { +// """ +// def method(str: String, int: Int, additionalArg: String) = TestClassWithAdditionalString(int, str, additionalArg) - val transformer = - Transformer - .defineVia[TestClass](method) - .build() - """ - }("No field named 'additionalArg' found in TestClass") - } +// val transformer = +// Transformer +// .defineVia[TestClass](method) +// .build() +// """ +// }("No field named 'additionalArg' found in TestClass") +// } -} +// } -object DefinitionViaBuilderSuite { - final case class TestClass(str: String, int: Int) - final case class TestClassWithAdditionalList(int: Int, str: String, additionalArg: List[String]) - final case class TestClassWithAdditionalString(int: Int, str: String, additionalArg: String) -} +// object DefinitionViaBuilderSuite { +// final case class TestClass(str: String, int: Int) +// final case class TestClassWithAdditionalList(int: Int, str: String, additionalArg: List[String]) +// final case class TestClassWithAdditionalString(int: Int, str: String, additionalArg: String) +// } From fda52313bdcea60fa599fc12e1706493699276cd Mon Sep 17 00:00:00 2001 From: arainko Date: Tue, 13 Sep 2022 20:44:09 +0200 Subject: [PATCH 2/8] refined arg selectors! but for `const` only for now --- .../ducktape/internal/modules/ConfigurationModule.scala | 4 ++-- .../arainko/ducktape/internal/modules/SelectorModule.scala | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/ConfigurationModule.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/ConfigurationModule.scala index 06f04d11..78b4576d 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/ConfigurationModule.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/ConfigurationModule.scala @@ -116,8 +116,8 @@ private[internal] trait ConfigurationModule { self: Module & SelectorModule & Mi )(using Fields.Source, Fields.Dest): Product = config match { case '{ - type namedArgs <: FunctionArguments[?] - Arg.const[source, dest, argType, actualType, `namedArgs`]($selector, $const)(using $ev1) + type argSelector <: FunctionArguments[?] + Arg.const[source, dest, argType, actualType, `argSelector`]($selector, $const)(using $ev1) } => val argName = Selectors.argName(Fields.dest, selector.asInstanceOf[Expr[FunctionArguments[?] => Any]]) verifyArgSelectorTypes(argName, const, TypeRepr.of[argType], TypeRepr.of[actualType]) diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/SelectorModule.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/SelectorModule.scala index e2223066..d22eb0bc 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/SelectorModule.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/SelectorModule.scala @@ -30,7 +30,6 @@ private[internal] trait SelectorModule { self: Module & MirrorModule & FieldModu case ArgSelector(argumentName) => abort(Failure.InvalidArgSelector.NotFound(selector, argumentName, Suggestion.fromFields(validArgs))) case other => - report.errorAndAbort(other.show) abort(Failure.InvalidArgSelector.NotAnArgSelector(selector, Suggestion.fromFields(validArgs))) } @@ -64,7 +63,7 @@ private[internal] trait SelectorModule { self: Module & MirrorModule & FieldModu PartialFunction.condOpt(arg.asExpr) { case '{ type argSelector <: FunctionArguments[?] - ($args: `argSelector`).selectDynamic($selectedArg).asInstanceOf[argTpe] + ($args: `argSelector`).selectDynamic($selectedArg).$asInstanceOf$[tpe] } => selectedArg.valueOrAbort } } From 13e6e54879dd1405f325e2a47323e32e7cb4390f Mon Sep 17 00:00:00 2001 From: arainko Date: Tue, 13 Sep 2022 20:49:55 +0200 Subject: [PATCH 3/8] restore test --- .../arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala index 3a9e3807..dd3cc21b 100644 --- a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala +++ b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala @@ -20,7 +20,7 @@ class AppliedViaBuilderSuite extends DucktapeSuite { .intoVia(method) .transform(Arg.const(_.additionalArg, List("const"))) - // assertEquals(actual, expected) + assertEquals(actual, expected) } // test("Arg.computed properly applies a function to an argument") { From 9c91dca9a3965e787ec19d445bd9200bb3c9684f Mon Sep 17 00:00:00 2001 From: arainko Date: Wed, 14 Sep 2022 01:44:08 +0200 Subject: [PATCH 4/8] upgrade to scala 3.2 --- build.sbt | 2 +- .../scala/io/github/arainko/ducktape/ArgBuilderConfig.scala | 4 ++-- .../arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 32ff0e38..4fc5647c 100644 --- a/build.sbt +++ b/build.sbt @@ -13,7 +13,7 @@ ThisBuild / developers := List( url("https://github.com/arainko") ) ) -ThisBuild / scalaVersion := "3.1.3" +ThisBuild / scalaVersion := "3.2.0" name := "ducktape" sonatypeRepository := "https://s01.oss.sonatype.org/service/local" diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/ArgBuilderConfig.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/ArgBuilderConfig.scala index acc2b0e3..63c4a771 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/ArgBuilderConfig.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/ArgBuilderConfig.scala @@ -7,11 +7,11 @@ import scala.annotation.implicitNotFound opaque type ArgBuilderConfig[Source, Dest, ArgSelector <: FunctionArguments[_]] = Unit object ArgBuilderConfig { - private[ducktape] def instance[Source, Dest, ArgSelector <: FunctionArguments[_]]: ArgBuilderConfig[Source, Dest, ArgSelector] = () + private[ducktape] def instance[Source, Dest, ArgSelector <: FunctionArguments[?]]: ArgBuilderConfig[Source, Dest, ArgSelector] = () } object Arg { - def const[Source, Dest, ArgType, ActualType, ArgSelector <: FunctionArguments[_]]( + def const[Source, Dest, ArgType, ActualType, ArgSelector <: FunctionArguments[?]]( selector: ArgSelector => ArgType, const: ActualType )(using diff --git a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala index dd3cc21b..d80c8a98 100644 --- a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala +++ b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala @@ -8,7 +8,7 @@ import io.github.arainko.ducktape.function.FunctionArguments import io.github.arainko.ducktape.builder.AppliedViaBuilder class AppliedViaBuilderSuite extends DucktapeSuite { - private val testClass = TestClass("str", 1) + val testClass = TestClass("str", 1) test("Arg.const properly applies a constant to an argument") { def method(str: String, int: Int, additionalArg: List[String]) = TestClassWithAdditionalList(int, str, additionalArg) From d0501c5bab75f968560228243c344e3b1758c59f Mon Sep 17 00:00:00 2001 From: arainko Date: Sat, 17 Sep 2022 00:51:27 +0200 Subject: [PATCH 5/8] more refinements --- .../ducktape/builder/AppliedViaBuilder.scala | 14 ++++-- .../internal/macros/FunctionMacros.scala | 45 ++++++++++--------- .../macros/ProductTransformerMacros.scala | 14 +++--- .../builder/AppliedViaBuilderSuite.scala | 2 +- 4 files changed, 43 insertions(+), 32 deletions(-) diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/builder/AppliedViaBuilder.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/builder/AppliedViaBuilder.scala index 0db41d82..948cffcc 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/builder/AppliedViaBuilder.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/builder/AppliedViaBuilder.scala @@ -6,17 +6,25 @@ import scala.deriving.Mirror import io.github.arainko.ducktape.function.* import scala.compiletime.* -sealed abstract class AppliedViaBuilder[Source, Dest, Func, NamedArguments <: Tuple, ArgSelector <: FunctionArguments[?]](source: Source, function: Func) { +sealed abstract class AppliedViaBuilder[Source, Dest, Func, ArgSelector <: FunctionArguments[?]]( + source: Source, + function: Func +) { inline def transform( inline config: ArgBuilderConfig[Source, Dest, ArgSelector]* )(using Source: Mirror.ProductOf[Source]): Dest = - ProductTransformerMacros.viaConfigured[Source, Dest, Func, NamedArguments, ArgSelector](source, function, config*) + ProductTransformerMacros.viaConfigured[Source, Dest, Func, ArgSelector](source, function, config*) } object AppliedViaBuilder { + private[AppliedViaBuilder] class Impl[Source, Dest, Func, ArgSelector <: FunctionArguments[?]]( + source: Source, + function: Func + ) extends AppliedViaBuilder[Source, Dest, Func, ArgSelector](source, function) + transparent inline def create[Source, Func](source: Source, inline func: Func)(using Func: FunctionMirror[Func]) = { - val builder = new AppliedViaBuilder[Source, Func.Return, Func, Nothing, Nothing](source, func) {} + val builder = Impl[Source, Func.Return, Func, Nothing](source, func) FunctionMacros.namedArguments(func, builder) } } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/FunctionMacros.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/FunctionMacros.scala index 1f80879f..b0ee510c 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/FunctionMacros.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/FunctionMacros.scala @@ -5,10 +5,15 @@ import io.github.arainko.ducktape.internal.modules.* import scala.quoted.* -class FunctionMacros(using val quotes: Quotes) extends Module, SelectorModule, FieldModule, MirrorModule { +private[ducktape] class FunctionMacros(using val quotes: Quotes) extends Module, SelectorModule, FieldModule, MirrorModule { import FunctionMacros.* import quotes.reflect.* + private val cons = TypeRepr.of[*:] + private val emptyTuple = TypeRepr.of[EmptyTuple] + private val namedArg = TypeRepr.of[NamedArgument] + private val functionArguments = TypeRepr.of[FunctionArguments] + def createMirror[Func: Type]: Expr[FunctionMirror[Func]] = TypeRepr.of[Func] match { case tpe @ AppliedType(_, tpeArgs) if tpe.isFunctionType => @@ -28,34 +33,28 @@ class FunctionMacros(using val quotes: Quotes) extends Module, SelectorModule, F case other => report.errorAndAbort(s"FunctionMirrors can only be created for functions. Got ${other.show} instead.") } - def namedArguments[Func: Type, F[_ <: Tuple, _ <: FunctionArguments[?]]: Type](function: Expr[Func], initial: Expr[F[Nothing, Nothing]]) = + def namedArguments[Func: Type, F[_ <: FunctionArguments[?]]: Type](function: Expr[Func], initial: Expr[F[Nothing]]) = function.asTerm match { - case func @ FunctionLambda(vals, body) => - val namedArg = TypeRepr.of[NamedArgument] - val wrapper = TypeRepr.of[F] - val args = vals.map(valdef => namedArg.appliedTo(ConstantType(StringConstant(valdef.name)) :: valdef.tpt.tpe :: Nil)) - val argTuple = tupleify(args) - val functionArguments = TypeRepr.of[FunctionArguments].appliedTo(argTuple) - val refinedFunctionArgs = - vals.foldLeft(functionArguments)((tpe, valDef) => Refinement(tpe, valDef.name, valDef.tpt.tpe)) - // val appliedArgs = argT - tupleify(args).asType -> refinedFunctionArgs.asType match { - case ('[IsTuple[namedArgs]], '[IsFuncArgs[args]]) => '{ $initial.asInstanceOf[F[namedArgs, args]] } + case func @ FunctionLambda(valDefs, _) => + val args = valDefs.map(valdef => namedArg.appliedTo(ConstantType(StringConstant(valdef.name)) :: valdef.tpt.tpe :: Nil)) + val funcArgs = functionArguments.appliedTo(tupleify(args)) + val refinedFunctionArgs = refine(funcArgs, valDefs) + refinedFunctionArgs.asType match { + case '[IsFuncArgs[args]] => '{ $initial.asInstanceOf[F[args]] } } case other => report.errorAndAbort(s"Failed to extract named arguments from ${other.show}") } - private def tupleify(tpes: List[TypeRepr]) = { - val cons = TypeRepr.of[*:] - val emptyTuple = TypeRepr.of[EmptyTuple] + private def tupleify(tpes: List[TypeRepr]) = tpes.foldRight(emptyTuple)((curr, acc) => cons.appliedTo(curr :: acc :: Nil)) - } + + private def refine(tpe: TypeRepr, valDefs: List[ValDef]) = + valDefs.foldLeft(functionArguments)((tpe, valDef) => Refinement(tpe, valDef.name, valDef.tpt.tpe)) } private[ducktape] object FunctionMacros { - private type IsTuple[A <: Tuple] = A private type IsFuncArgs[A <: FunctionArguments[?]] = A transparent inline def createMirror[F]: FunctionMirror[F] = ${ createMirrorMacro[F] } @@ -63,11 +62,13 @@ private[ducktape] object FunctionMacros { def createMirrorMacro[Func: Type](using Quotes): Expr[FunctionMirror[Func]] = FunctionMacros().createMirror[Func] - transparent inline def namedArguments[Func, F[_ <: Tuple, _ <: FunctionArguments[?]]]( + transparent inline def namedArguments[Func, F[_ <: FunctionArguments[?]]]( inline function: Func, - initial: F[Nothing, Nothing] + initial: F[Nothing] )(using FunctionMirror[Func]) = ${ namedArgumentsMacro[Func, F]('function, 'initial) } - def namedArgumentsMacro[Func: Type, F[_ <: Tuple, _ <: FunctionArguments[?]]: Type](function: Expr[Func], initial: Expr[F[Nothing, Nothing]])(using Quotes) = - FunctionMacros().namedArguments[Func, F](function, initial) + def namedArgumentsMacro[Func: Type, F[_ <: FunctionArguments[?]]: Type]( + function: Expr[Func], + initial: Expr[F[Nothing]] + )(using Quotes) = FunctionMacros().namedArguments[Func, F](function, initial) } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/ProductTransformerMacros.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/ProductTransformerMacros.scala index b8407498..4e7e7f48 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/ProductTransformerMacros.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/ProductTransformerMacros.scala @@ -31,14 +31,16 @@ private[ducktape] class ProductTransformerMacros(using val quotes: Quotes) case other => report.errorAndAbort(s"'via' is only supported on eta-expanded methods!") } - def viaConfigured[Source: Type, Dest: Type, Func: Type, NamedArgs <: Tuple: Type, ArgSelector <: FunctionArguments[?]: Type]( + def viaConfigured[Source: Type, Dest: Type, Func: Type, ArgSelector <: FunctionArguments[?]: Type]( sourceValue: Expr[Source], function: Expr[Func], config: Expr[Seq[ArgBuilderConfig[Source, Dest, ArgSelector]]], Source: Expr[Mirror.ProductOf[Source]] ): Expr[Dest] = { given Fields.Source = Fields.Source.fromMirror(Source) - given Fields.Dest = Fields.Dest.fromNamedArguments[NamedArgs] + given Fields.Dest = Type.of[ArgSelector] match { + case '[FunctionArguments[namedArgs]] => Fields.Dest.fromNamedArguments[namedArgs] + } val materializedConfig = MaterializedConfiguration.materializeArgConfig(config) val nonConfiguredFields = Fields.dest.byName -- materializedConfig.map(_.destFieldName) @@ -200,20 +202,20 @@ private[ducktape] object ProductTransformerMacros { )(using Quotes) = ProductTransformerMacros().via(source, function, Func, Source) - inline def viaConfigured[Source, Dest, Func, NamedArgs <: Tuple, ArgSelector <: FunctionArguments[?]]( + inline def viaConfigured[Source, Dest, Func, ArgSelector <: FunctionArguments[?]]( source: Source, inline function: Func, inline config: ArgBuilderConfig[Source, Dest, ArgSelector]* )(using Source: Mirror.ProductOf[Source]): Dest = - ${ viaConfiguredMacro[Source, Dest, Func, NamedArgs, ArgSelector]('source, 'function, 'config, 'Source) } + ${ viaConfiguredMacro[Source, Dest, Func, ArgSelector]('source, 'function, 'config, 'Source) } - def viaConfiguredMacro[Source: Type, Dest: Type, Func: Type, NamedArgs <: Tuple: Type, ArgSelector <: FunctionArguments[?]: Type]( + def viaConfiguredMacro[Source: Type, Dest: Type, Func: Type, ArgSelector <: FunctionArguments[?]: Type]( sourceValue: Expr[Source], function: Expr[Func], config: Expr[Seq[ArgBuilderConfig[Source, Dest, ArgSelector]]], A: Expr[Mirror.ProductOf[Source]] )(using Quotes) = - ProductTransformerMacros().viaConfigured[Source, Dest, Func, NamedArgs, ArgSelector](sourceValue, function, config, A) + ProductTransformerMacros().viaConfigured[Source, Dest, Func, ArgSelector](sourceValue, function, config, A) inline def transformConfigured[Source, Dest](sourceValue: Source, inline config: BuilderConfig[Source, Dest]*)(using Source: Mirror.ProductOf[Source], diff --git a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala index d80c8a98..dd3cc21b 100644 --- a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala +++ b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala @@ -8,7 +8,7 @@ import io.github.arainko.ducktape.function.FunctionArguments import io.github.arainko.ducktape.builder.AppliedViaBuilder class AppliedViaBuilderSuite extends DucktapeSuite { - val testClass = TestClass("str", 1) + private val testClass = TestClass("str", 1) test("Arg.const properly applies a constant to an argument") { def method(str: String, int: Int, additionalArg: List[String]) = TestClassWithAdditionalList(int, str, additionalArg) From 41e2401ebafe9e89ca3f4319dc4776ab06c16cd4 Mon Sep 17 00:00:00 2001 From: arainko Date: Sat, 17 Sep 2022 23:11:30 +0200 Subject: [PATCH 6/8] even more thigns - but passing along named args seems broken for some reason --- .../internal/macros/FunctionMacros.scala | 1 + .../macros/ProductTransformerMacros.scala | 6 +++--- .../ducktape/internal/modules/FieldModule.scala | 17 +++++++---------- .../builder/AppliedViaBuilderSuite.scala | 4 ++-- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/FunctionMacros.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/FunctionMacros.scala index b0ee510c..3cdfde8f 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/FunctionMacros.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/FunctionMacros.scala @@ -39,6 +39,7 @@ private[ducktape] class FunctionMacros(using val quotes: Quotes) extends Module, val args = valDefs.map(valdef => namedArg.appliedTo(ConstantType(StringConstant(valdef.name)) :: valdef.tpt.tpe :: Nil)) val funcArgs = functionArguments.appliedTo(tupleify(args)) val refinedFunctionArgs = refine(funcArgs, valDefs) + report.errorAndAbort(refinedFunctionArgs.show) refinedFunctionArgs.asType match { case '[IsFuncArgs[args]] => '{ $initial.asInstanceOf[F[args]] } } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/ProductTransformerMacros.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/ProductTransformerMacros.scala index 4e7e7f48..07369e59 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/ProductTransformerMacros.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/ProductTransformerMacros.scala @@ -38,9 +38,9 @@ private[ducktape] class ProductTransformerMacros(using val quotes: Quotes) Source: Expr[Mirror.ProductOf[Source]] ): Expr[Dest] = { given Fields.Source = Fields.Source.fromMirror(Source) - given Fields.Dest = Type.of[ArgSelector] match { - case '[FunctionArguments[namedArgs]] => Fields.Dest.fromNamedArguments[namedArgs] - } + // report.errorAndAbort(TypeRepr.of[ArgSelector].show) + given Fields.Dest = Fields.Dest.fromNamedArguments[ArgSelector] + report.errorAndAbort(Fields.dest.value.map(_.tpe.show).mkString(", ")) val materializedConfig = MaterializedConfiguration.materializeArgConfig(config) val nonConfiguredFields = Fields.dest.byName -- materializedConfig.map(_.destFieldName) diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/FieldModule.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/FieldModule.scala index f972ae4e..446a4f22 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/FieldModule.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/FieldModule.scala @@ -5,6 +5,7 @@ import scala.deriving.* import io.github.arainko.ducktape.Transformer import io.github.arainko.ducktape.function.NamedArgument import scala.compiletime.* +import io.github.arainko.ducktape.function.FunctionArguments private[internal] trait FieldModule { self: Module & MirrorModule => import quotes.reflect.* @@ -42,17 +43,13 @@ private[internal] trait FieldModule { self: Module & MirrorModule => apply(fields) } - final def fromNamedArguments[NamedArgs <: Tuple: Type]: FieldsSubtype = { - def listOfFields[Args <: Tuple: Type]: List[Field] = - Type.of[Args] match { - case '[EmptyTuple] => List.empty - case '[NamedArgument[name, tpe] *: tail] => - val name = Type.valueOfConstant[name].getOrElse(report.errorAndAbort("Not a constant named arg name")) - val field = Field(name, TypeRepr.of[tpe]) - field :: listOfFields[tail] - case other => report.errorAndAbort(s"Failed here with ${Type.show[NamedArgs]}") + final def fromNamedArguments[ArgSelector <: FunctionArguments[?]: Type]: FieldsSubtype = { + val fields = List.unfold(TypeRepr.of[ArgSelector]) { state => + PartialFunction.condOpt(state) { + case Refinement(parent, name, fieldTpe) => Field(name, fieldTpe) -> parent } - apply(listOfFields[NamedArgs]) + } + apply(fields) } final def fromValDefs(valDefs: List[ValDef]): FieldsSubtype = { diff --git a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala index dd3cc21b..bb494b0c 100644 --- a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala +++ b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala @@ -18,9 +18,9 @@ class AppliedViaBuilderSuite extends DucktapeSuite { val actual = testClass .intoVia(method) - .transform(Arg.const(_.additionalArg, List("const"))) + // .transform(Arg.const(_.additionalArg, List("const"))) - assertEquals(actual, expected) + // assertEquals(actual, expected) } // test("Arg.computed properly applies a function to an argument") { From b94e2763c4e2f147dd87b575370a12d62ffc66f2 Mon Sep 17 00:00:00 2001 From: Aleksander Rainko Date: Sun, 18 Sep 2022 01:39:05 +0200 Subject: [PATCH 7/8] workaround 3.2 regression, finalize Selectable arg selectors --- .../arainko/ducktape/ArgBuilderConfig.scala | 44 ++++---- .../arainko/ducktape/BuilderConfig.scala | 2 + .../github/arainko/ducktape/Transformer.scala | 2 +- .../ducktape/builder/AppliedViaBuilder.scala | 7 +- .../builder/DefinitionViaBuilder.scala | 51 +++++---- .../function/FunctionArgSelector.scala | 13 --- .../ducktape/function/FunctionArguments.scala | 4 +- .../ducktape/function/NamedArgument.scala | 12 --- .../internal/macros/DebugMacros.scala | 3 - .../internal/macros/FunctionMacros.scala | 18 +--- .../macros/ProductTransformerMacros.scala | 10 +- .../modules/ConfigurationModule.scala | 55 ++++------ .../internal/modules/FieldModule.scala | 5 +- .../internal/modules/MirrorModule.scala | 2 +- .../ducktape/internal/modules/Module.scala | 13 --- .../internal/modules/SelectorModule.scala | 6 +- .../builder/AppliedViaBuilderSuite.scala | 83 +++++++------- .../builder/DefinitionViaBuilderSuite.scala | 102 +++++++++--------- 18 files changed, 188 insertions(+), 244 deletions(-) delete mode 100644 ducktape/src/main/scala/io/github/arainko/ducktape/function/FunctionArgSelector.scala delete mode 100644 ducktape/src/main/scala/io/github/arainko/ducktape/function/NamedArgument.scala diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/ArgBuilderConfig.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/ArgBuilderConfig.scala index 63c4a771..a0b1253e 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/ArgBuilderConfig.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/ArgBuilderConfig.scala @@ -3,36 +3,42 @@ package io.github.arainko.ducktape import io.github.arainko.ducktape.function.FunctionArguments import scala.deriving.Mirror import scala.annotation.implicitNotFound +import scala.annotation.compileTimeOnly -opaque type ArgBuilderConfig[Source, Dest, ArgSelector <: FunctionArguments[_]] = Unit +opaque type ArgBuilderConfig[Source, Dest, ArgSelector <: FunctionArguments] = Unit object ArgBuilderConfig { - private[ducktape] def instance[Source, Dest, ArgSelector <: FunctionArguments[?]]: ArgBuilderConfig[Source, Dest, ArgSelector] = () + private[ducktape] def instance[Source, Dest, ArgSelector <: FunctionArguments]: ArgBuilderConfig[Source, Dest, ArgSelector] = () } +//TODO: Slap a @compileTimeOnly on all things here object Arg { - def const[Source, Dest, ArgType, ActualType, ArgSelector <: FunctionArguments[?]]( + + def const[Source, Dest, ArgType, ActualType, ArgSelector <: FunctionArguments]( selector: ArgSelector => ArgType, const: ActualType )(using @implicitNotFound("Arg.const is only supported for product types but ${Source} is not a product type.") - ev: Mirror.ProductOf[Source] + ev1: Mirror.ProductOf[Source], + ev2: ActualType <:< ArgType ): ArgBuilderConfig[Source, Dest, ArgSelector] = ArgBuilderConfig.instance - // def computed[Source, Dest, ArgType, ActualType, ArgSelector <: FunctionArguments[_]]( - // selector: ArgSelector => ArgType, - // f: Source => ActualType - // )(using - // @implicitNotFound("Arg.computed is only supported for product types but ${Source} is not a product type.") - // ev: Mirror.ProductOf[Source] - // ): ArgBuilderConfig[Source, Dest, ArgSelector] = ArgBuilderConfig.instance - - // def renamed[Source, Dest, ArgType, FieldType, ArgSelector <: FunctionArguments[_]]( - // destSelector: ArgSelector => ArgType, - // sourceSelector: Source => FieldType - // )(using - // @implicitNotFound("Arg.renamed is only supported for product types but ${Source} is not a product type.") - // ev: Mirror.ProductOf[Source] - // ): ArgBuilderConfig[Source, Dest, ArgSelector] = ArgBuilderConfig.instance + def computed[Source, Dest, ArgType, ActualType, ArgSelector <: FunctionArguments]( + selector: ArgSelector => ArgType, + f: Source => ActualType + )(using + @implicitNotFound("Arg.computed is only supported for product types but ${Source} is not a product type.") + ev1: Mirror.ProductOf[Source], + ev2: ActualType <:< ArgType + ): ArgBuilderConfig[Source, Dest, ArgSelector] = ArgBuilderConfig.instance + + def renamed[Source, Dest, ArgType, FieldType, ArgSelector <: FunctionArguments]( + destSelector: ArgSelector => ArgType, + sourceSelector: Source => FieldType, + )(using + @implicitNotFound("Arg.renamed is only supported for product types but ${Source} is not a product type.") + ev1: Mirror.ProductOf[Source], + ev2: FieldType <:< ArgType + ): ArgBuilderConfig[Source, Dest, ArgSelector] = ArgBuilderConfig.instance } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/BuilderConfig.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/BuilderConfig.scala index 815c7af8..8278b025 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/BuilderConfig.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/BuilderConfig.scala @@ -10,6 +10,7 @@ object BuilderConfig { private[ducktape] def instance[Source, Dest]: BuilderConfig[Source, Dest] = () } +//TODO: Slap a @compileTimeOnly on all things here object Field { def const[Source, Dest, FieldType, ActualType](selector: Dest => FieldType, value: ActualType)(using ev1: ActualType <:< FieldType, @@ -39,6 +40,7 @@ object Field { ): BuilderConfig[Source, Dest] = BuilderConfig.instance } +//TODO: Slap a @compileTimeOnly on all things here object Case { def const[SourceSubtype]: Case.Const[SourceSubtype] = Const.instance diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/Transformer.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/Transformer.scala index 586970b1..57a1a8e4 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/Transformer.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/Transformer.scala @@ -18,7 +18,7 @@ object Transformer { def define[Source, Dest]: DefinitionBuilder[Source, Dest] = DefinitionBuilder[Source, Dest] - // def defineVia[A]: DefinitionViaBuilder.PartiallyApplied[A] = DefinitionViaBuilder.create[A] + def defineVia[A]: DefinitionViaBuilder.PartiallyApplied[A] = DefinitionViaBuilder.create[A] sealed trait Identity[Source] extends Transformer[Source, Source] diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/builder/AppliedViaBuilder.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/builder/AppliedViaBuilder.scala index 948cffcc..aa64f5d2 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/builder/AppliedViaBuilder.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/builder/AppliedViaBuilder.scala @@ -6,7 +6,7 @@ import scala.deriving.Mirror import io.github.arainko.ducktape.function.* import scala.compiletime.* -sealed abstract class AppliedViaBuilder[Source, Dest, Func, ArgSelector <: FunctionArguments[?]]( +sealed abstract class AppliedViaBuilder[Source, Dest, Func, ArgSelector <: FunctionArguments]( source: Source, function: Func ) { @@ -18,13 +18,14 @@ sealed abstract class AppliedViaBuilder[Source, Dest, Func, ArgSelector <: Funct } object AppliedViaBuilder { - private[AppliedViaBuilder] class Impl[Source, Dest, Func, ArgSelector <: FunctionArguments[?]]( + private[AppliedViaBuilder] class Impl[Source, Dest, Func, ArgSelector <: FunctionArguments]( source: Source, function: Func ) extends AppliedViaBuilder[Source, Dest, Func, ArgSelector](source, function) transparent inline def create[Source, Func](source: Source, inline func: Func)(using Func: FunctionMirror[Func]) = { - val builder = Impl[Source, Func.Return, Func, Nothing](source, func) + // widen the type to not infer `AppliedViaBuilder.Impl`, we're in a transparent inline method after all + val builder: AppliedViaBuilder[Source, Func.Return, Func, Nothing] = Impl(source, func) FunctionMacros.namedArguments(func, builder) } } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/builder/DefinitionViaBuilder.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/builder/DefinitionViaBuilder.scala index 98d89e86..4a55ed6d 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/builder/DefinitionViaBuilder.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/builder/DefinitionViaBuilder.scala @@ -1,29 +1,34 @@ -// package io.github.arainko.ducktape.builder +package io.github.arainko.ducktape.builder -// import io.github.arainko.ducktape.* -// import io.github.arainko.ducktape.internal.macros.* -// import scala.deriving.* -// import io.github.arainko.ducktape.function.FunctionMirror +import io.github.arainko.ducktape.* +import io.github.arainko.ducktape.internal.macros.* +import scala.deriving.* +import io.github.arainko.ducktape.function.* -// sealed abstract class DefinitionViaBuilder[Source, Dest, Func, NamedArguments <: Tuple](function: Func) { +sealed abstract class DefinitionViaBuilder[Source, Dest, Func, ArgSelector <: FunctionArguments](function: Func) { -// inline def build( -// inline config: ArgBuilderConfig[Source, Dest, NamedArguments]* -// )(using Mirror.ProductOf[Source]): Transformer[Source, Dest] = from => -// ProductTransformerMacros.viaConfigured[Source, Dest, Func, NamedArguments](from, function, config*) -// } + inline def build( + inline config: ArgBuilderConfig[Source, Dest, ArgSelector]* + )(using Mirror.ProductOf[Source]): Transformer[Source, Dest] = from => + ProductTransformerMacros.viaConfigured[Source, Dest, Func, ArgSelector](from, function, config*) +} -// object DefinitionViaBuilder { -// def create[Source]: PartiallyApplied[Source] = () +object DefinitionViaBuilder { + private[DefinitionViaBuilder] class Impl[Source, Dest, Func, ArgSelector <: FunctionArguments]( + function: Func + ) extends DefinitionViaBuilder[Source, Dest, Func, ArgSelector](function) -// opaque type PartiallyApplied[Source] = Unit + def create[Source]: PartiallyApplied[Source] = () -// object PartiallyApplied { -// extension [Source](partial: PartiallyApplied[Source]) { -// transparent inline def apply[Func](inline func: Func)(using Func: FunctionMirror[Func]) = { -// val builder = new DefinitionViaBuilder[Source, Func.Return, Func, Nothing](func) {} -// FunctionMacros.namedArguments(func, builder) -// } -// } -// } -// } + opaque type PartiallyApplied[Source] = Unit + + object PartiallyApplied { + extension [Source](partial: PartiallyApplied[Source]) { + transparent inline def apply[Func](inline func: Func)(using Func: FunctionMirror[Func]) = { + // widen the type to not infer `DefinitionViaBuilder.Impl`, we're in a transparent inline method after all + val builder: DefinitionViaBuilder[Source, Func.Return, Func, Nothing] = Impl(func) + FunctionMacros.namedArguments(func, builder) + } + } + } +} diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/function/FunctionArgSelector.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/function/FunctionArgSelector.scala deleted file mode 100644 index 49bc33b3..00000000 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/function/FunctionArgSelector.scala +++ /dev/null @@ -1,13 +0,0 @@ -package io.github.arainko.ducktape.function - -abstract class FunctionArgSelector[NamedArgs <: Tuple] extends Selectable { - def selectDynamic(value: String): NamedArgument.FindByName[value.type, NamedArgs] -} - -@main def main = { - val selector: FunctionArgSelector[NamedArgument["name", Int] *: EmptyTuple] { - val name: Int - } = ??? - - val asd = selector.name -} diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/function/FunctionArguments.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/function/FunctionArguments.scala index c75750e5..0ca124a7 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/function/FunctionArguments.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/function/FunctionArguments.scala @@ -3,6 +3,6 @@ package io.github.arainko.ducktape.function import scala.annotation.implicitNotFound import scala.util.NotGiven -sealed trait FunctionArguments[NamedArgs <: Tuple] extends Selectable { - def selectDynamic(value: String): NamedArgument.FindByName[value.type, NamedArgs] +sealed trait FunctionArguments extends Selectable { + def selectDynamic(value: String): Nothing } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/function/NamedArgument.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/function/NamedArgument.scala deleted file mode 100644 index 59af9605..00000000 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/function/NamedArgument.scala +++ /dev/null @@ -1,12 +0,0 @@ -package io.github.arainko.ducktape.function - -sealed trait NamedArgument[Name <: String, Type] - -object NamedArgument { - type FindByName[Name <: String, NamedArgs <: Tuple] = - NamedArgs match { - case EmptyTuple => Nothing - case NamedArgument[Name, tpe] *: _ => tpe - case head *: tail => FindByName[Name, tail] - } -} diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/DebugMacros.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/DebugMacros.scala index d6721e94..b2a76f41 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/DebugMacros.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/DebugMacros.scala @@ -17,11 +17,8 @@ object DebugMacros { def codeCompiletimeMacro[A: Type](value: Expr[A])(using Quotes) = { import quotes.reflect.* - val struct = Printer.TreeShortCode.show(value.asTerm) - report.info(struct) - value } } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/FunctionMacros.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/FunctionMacros.scala index 3cdfde8f..47b69f9a 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/FunctionMacros.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/FunctionMacros.scala @@ -11,7 +11,6 @@ private[ducktape] class FunctionMacros(using val quotes: Quotes) extends Module, private val cons = TypeRepr.of[*:] private val emptyTuple = TypeRepr.of[EmptyTuple] - private val namedArg = TypeRepr.of[NamedArgument] private val functionArguments = TypeRepr.of[FunctionArguments] def createMirror[Func: Type]: Expr[FunctionMirror[Func]] = @@ -33,42 +32,35 @@ private[ducktape] class FunctionMacros(using val quotes: Quotes) extends Module, case other => report.errorAndAbort(s"FunctionMirrors can only be created for functions. Got ${other.show} instead.") } - def namedArguments[Func: Type, F[_ <: FunctionArguments[?]]: Type](function: Expr[Func], initial: Expr[F[Nothing]]) = + def namedArguments[Func: Type, F[_ <: FunctionArguments]: Type](function: Expr[Func], initial: Expr[F[Nothing]]) = function.asTerm match { case func @ FunctionLambda(valDefs, _) => - val args = valDefs.map(valdef => namedArg.appliedTo(ConstantType(StringConstant(valdef.name)) :: valdef.tpt.tpe :: Nil)) - val funcArgs = functionArguments.appliedTo(tupleify(args)) - val refinedFunctionArgs = refine(funcArgs, valDefs) - report.errorAndAbort(refinedFunctionArgs.show) - refinedFunctionArgs.asType match { + refine(functionArguments, valDefs).asType match { case '[IsFuncArgs[args]] => '{ $initial.asInstanceOf[F[args]] } } case other => report.errorAndAbort(s"Failed to extract named arguments from ${other.show}") } - private def tupleify(tpes: List[TypeRepr]) = - tpes.foldRight(emptyTuple)((curr, acc) => cons.appliedTo(curr :: acc :: Nil)) - private def refine(tpe: TypeRepr, valDefs: List[ValDef]) = valDefs.foldLeft(functionArguments)((tpe, valDef) => Refinement(tpe, valDef.name, valDef.tpt.tpe)) } private[ducktape] object FunctionMacros { - private type IsFuncArgs[A <: FunctionArguments[?]] = A + private type IsFuncArgs[A <: FunctionArguments] = A transparent inline def createMirror[F]: FunctionMirror[F] = ${ createMirrorMacro[F] } def createMirrorMacro[Func: Type](using Quotes): Expr[FunctionMirror[Func]] = FunctionMacros().createMirror[Func] - transparent inline def namedArguments[Func, F[_ <: FunctionArguments[?]]]( + transparent inline def namedArguments[Func, F[_ <: FunctionArguments]]( inline function: Func, initial: F[Nothing] )(using FunctionMirror[Func]) = ${ namedArgumentsMacro[Func, F]('function, 'initial) } - def namedArgumentsMacro[Func: Type, F[_ <: FunctionArguments[?]]: Type]( + def namedArgumentsMacro[Func: Type, F[_ <: FunctionArguments]: Type]( function: Expr[Func], initial: Expr[F[Nothing]] )(using Quotes) = FunctionMacros().namedArguments[Func, F](function, initial) diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/ProductTransformerMacros.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/ProductTransformerMacros.scala index 07369e59..60f991db 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/ProductTransformerMacros.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/ProductTransformerMacros.scala @@ -31,16 +31,14 @@ private[ducktape] class ProductTransformerMacros(using val quotes: Quotes) case other => report.errorAndAbort(s"'via' is only supported on eta-expanded methods!") } - def viaConfigured[Source: Type, Dest: Type, Func: Type, ArgSelector <: FunctionArguments[?]: Type]( + def viaConfigured[Source: Type, Dest: Type, Func: Type, ArgSelector <: FunctionArguments: Type]( sourceValue: Expr[Source], function: Expr[Func], config: Expr[Seq[ArgBuilderConfig[Source, Dest, ArgSelector]]], Source: Expr[Mirror.ProductOf[Source]] ): Expr[Dest] = { given Fields.Source = Fields.Source.fromMirror(Source) - // report.errorAndAbort(TypeRepr.of[ArgSelector].show) - given Fields.Dest = Fields.Dest.fromNamedArguments[ArgSelector] - report.errorAndAbort(Fields.dest.value.map(_.tpe.show).mkString(", ")) + given Fields.Dest = Fields.Dest.fromFunctionArguments[ArgSelector] val materializedConfig = MaterializedConfiguration.materializeArgConfig(config) val nonConfiguredFields = Fields.dest.byName -- materializedConfig.map(_.destFieldName) @@ -202,14 +200,14 @@ private[ducktape] object ProductTransformerMacros { )(using Quotes) = ProductTransformerMacros().via(source, function, Func, Source) - inline def viaConfigured[Source, Dest, Func, ArgSelector <: FunctionArguments[?]]( + inline def viaConfigured[Source, Dest, Func, ArgSelector <: FunctionArguments]( source: Source, inline function: Func, inline config: ArgBuilderConfig[Source, Dest, ArgSelector]* )(using Source: Mirror.ProductOf[Source]): Dest = ${ viaConfiguredMacro[Source, Dest, Func, ArgSelector]('source, 'function, 'config, 'Source) } - def viaConfiguredMacro[Source: Type, Dest: Type, Func: Type, ArgSelector <: FunctionArguments[?]: Type]( + def viaConfiguredMacro[Source: Type, Dest: Type, Func: Type, ArgSelector <: FunctionArguments: Type]( sourceValue: Expr[Source], function: Expr[Func], config: Expr[Seq[ArgBuilderConfig[Source, Dest, ArgSelector]]], diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/ConfigurationModule.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/ConfigurationModule.scala index 78b4576d..0291e728 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/ConfigurationModule.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/ConfigurationModule.scala @@ -37,7 +37,7 @@ private[internal] trait ConfigurationModule { self: Module & SelectorModule & Mi .map((_, fieldConfigs) => fieldConfigs.last) // keep the last applied field config only .toList - def materializeArgConfig[Source, Dest, ArgSelector <: FunctionArguments[?]]( + def materializeArgConfig[Source, Dest, ArgSelector <: FunctionArguments]( config: Expr[Seq[ArgBuilderConfig[Source, Dest, ArgSelector]]] )(using Fields.Source, Fields.Dest): List[Product] = Varargs @@ -105,51 +105,34 @@ private[internal] trait ConfigurationModule { self: Module & SelectorModule & Mi case other => abort(Failure.UnsupportedConfig(other, Failure.ConfigType.Case)) } - /** - * We check arg types here because if an arg is not found `FunctionArguments.FindByName` returns a Nothing - * which trips up evidence summoning which in turn only tells us that an arg was Nothing (with no suggestions etc.) - * - * TODO: See if it works properly, if not we may need to go back ot the old encoding with the evidence and bad error messages - */ - private def materializeSingleArgConfig[Source, Dest, ArgSelector <: FunctionArguments[?]]( + private def materializeSingleArgConfig[Source, Dest, ArgSelector <: FunctionArguments]( config: Expr[ArgBuilderConfig[Source, Dest, ArgSelector]] )(using Fields.Source, Fields.Dest): Product = config match { case '{ - type argSelector <: FunctionArguments[?] - Arg.const[source, dest, argType, actualType, `argSelector`]($selector, $const)(using $ev1) + type argSelector <: FunctionArguments + Arg.const[source, dest, argType, actualType, `argSelector`]($selector, $const)(using $ev1, $ev2) } => - val argName = Selectors.argName(Fields.dest, selector.asInstanceOf[Expr[FunctionArguments[?] => Any]]) - verifyArgSelectorTypes(argName, const, TypeRepr.of[argType], TypeRepr.of[actualType]) + val argName = Selectors.argName(Fields.dest, selector) Product.Const(argName, const) - // case '{ - // type namedArgs <: Tuple - // Arg.computed[source, dest, argType, actualType, `namedArgs`]($selector, $function)(using $ev1) - // } => - // val argName = Selectors.argName(Fields.dest, selector) - // verifyArgSelectorTypes(argName, function, TypeRepr.of[argType], TypeRepr.of[actualType]) - // Product.Computed(argName, function.asInstanceOf[Expr[Any => Any]]) - - // case '{ - // type namedArgs <: Tuple - // Arg.renamed[source, dest, argType, fieldType, `namedArgs`]($destSelector, $sourceSelector)(using $ev1) - // } => - // val argName = Selectors.argName(Fields.dest, destSelector) - // val fieldName = Selectors.fieldName(Fields.source, sourceSelector) - // verifyArgSelectorTypes(argName, sourceSelector, TypeRepr.of[argType], TypeRepr.of[fieldType]) - // Product.Renamed(argName, fieldName) + case '{ + type argSelector <: FunctionArguments + Arg.computed[source, dest, argType, actualType, `argSelector`]($selector, $function)(using $ev1, $ev2) + } => + val argName = Selectors.argName(Fields.dest, selector) + Product.Computed(argName, function.asInstanceOf[Expr[Any => Any]]) + + case '{ + type argSelector <: FunctionArguments + Arg.renamed[source, dest, argType, fieldType, `argSelector`]($destSelector, $sourceSelector)(using $ev1, $ev2) + } => + val argName = Selectors.argName(Fields.dest, destSelector) + val fieldName = Selectors.fieldName(Fields.source, sourceSelector) + Product.Renamed(argName, fieldName) case other => abort(Failure.UnsupportedConfig(other, Failure.ConfigType.Arg)) } - private def verifyArgSelectorTypes( - argName: String, - mismatchedValue: Expr[Any], - expected: TypeRepr, - actual: TypeRepr - ) = if (!(actual <:< expected)) - abort(Failure.InvalidArgSelector.TypeMismatch(argName, expected, actual, mismatchedValue)) - } } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/FieldModule.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/FieldModule.scala index 446a4f22..6326b7d6 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/FieldModule.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/FieldModule.scala @@ -3,7 +3,6 @@ package io.github.arainko.ducktape.internal.modules import scala.quoted.* import scala.deriving.* import io.github.arainko.ducktape.Transformer -import io.github.arainko.ducktape.function.NamedArgument import scala.compiletime.* import io.github.arainko.ducktape.function.FunctionArguments @@ -43,13 +42,13 @@ private[internal] trait FieldModule { self: Module & MirrorModule => apply(fields) } - final def fromNamedArguments[ArgSelector <: FunctionArguments[?]: Type]: FieldsSubtype = { + final def fromFunctionArguments[ArgSelector <: FunctionArguments: Type]: FieldsSubtype = { val fields = List.unfold(TypeRepr.of[ArgSelector]) { state => PartialFunction.condOpt(state) { case Refinement(parent, name, fieldTpe) => Field(name, fieldTpe) -> parent } } - apply(fields) + apply(fields.reverse) } final def fromValDefs(valDefs: List[ValDef]): FieldsSubtype = { diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/MirrorModule.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/MirrorModule.scala index 5f54abbd..ef08eb0d 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/MirrorModule.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/MirrorModule.scala @@ -31,7 +31,7 @@ private[internal] trait MirrorModule { self: Module => mirroredElemLabels <- findMemberType(mirrorTpe, "MirroredElemLabels") } yield { val elemTypes = tupleTypeElements(mirroredElemTypes) - val ConstantType(StringConstant(label)) = mirroredLabel + val ConstantType(StringConstant(label)) = mirroredLabel: @unchecked val elemLabels = tupleTypeElements(mirroredElemLabels).map { case ConstantType(StringConstant(l)) => l } MaterializedMirror(mirroredType, mirroredMonoType, elemTypes, label, elemLabels) } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/Module.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/Module.scala index 14ddc428..0e394d75 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/Module.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/Module.scala @@ -84,7 +84,6 @@ private[internal] trait Module { override def position: Position = this match { case NotFound(selector, _, _) => selector.asTerm.pos - case TypeMismatch(_, _, _, mismatchedValue) => mismatchedValue.asTerm.pos case NotAnArgSelector(selector, _) => selector.asTerm.pos } @@ -94,11 +93,6 @@ private[internal] trait Module { |'_.$argName' is not a valid argument selector. |Try one of these: ${Suggestion.renderAll(suggestedArgs)} """.stripMargin - case TypeMismatch(argName, expectedType, actualTpe, _) => - s""" - |Type mistmatch for argument '$argName'. - |Expected ${expectedType.show} but found ${actualTpe.show}. - """.stripMargin case NotAnArgSelector(_, suggestedArgs) => s""" |Not a valid argument selector. @@ -108,13 +102,6 @@ private[internal] trait Module { case NotFound(selector: Expr[Any], argumentName: String, suggestedArgs: List[Suggestion]) - case TypeMismatch( - argumentName: String, - expectedType: TypeRepr, - actualType: TypeRepr, - mismatchedValue: Expr[Any] - ) - case NotAnArgSelector(selector: Expr[Any], suggestedArgs: List[Suggestion]) } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/SelectorModule.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/SelectorModule.scala index d22eb0bc..bd739bee 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/SelectorModule.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/SelectorModule.scala @@ -20,9 +20,9 @@ private[internal] trait SelectorModule { self: Module & MirrorModule & FieldModu abort(Failure.InvalidFieldSelector(other, TypeRepr.of[From], Suggestion.fromFields(validFields))) } - def argName[ArgType: Type]( + def argName[ArgType: Type, ArgSelector <: FunctionArguments]( validArgs: Fields, - selector: Expr[FunctionArguments[?] => ArgType] + selector: Expr[ArgSelector => ArgType] ): String = selector.asTerm match { case ArgSelector(argumentName) if validArgs.containsFieldWithName(argumentName) => @@ -62,7 +62,7 @@ private[internal] trait SelectorModule { self: Module & MirrorModule & FieldModu def unapply(arg: Term): Option[String] = PartialFunction.condOpt(arg.asExpr) { case '{ - type argSelector <: FunctionArguments[?] + type argSelector <: FunctionArguments ($args: `argSelector`).selectDynamic($selectedArg).$asInstanceOf$[tpe] } => selectedArg.valueOrAbort } diff --git a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala index bb494b0c..c016facf 100644 --- a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala +++ b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala @@ -3,7 +3,6 @@ package io.github.arainko.ducktapetest.builder import io.github.arainko.ducktapetest.DucktapeSuite import io.github.arainko.ducktapetest.builder.AppliedViaBuilderSuite.* import io.github.arainko.ducktape.* -import io.github.arainko.ducktape.function.NamedArgument import io.github.arainko.ducktape.function.FunctionArguments import io.github.arainko.ducktape.builder.AppliedViaBuilder @@ -18,62 +17,62 @@ class AppliedViaBuilderSuite extends DucktapeSuite { val actual = testClass .intoVia(method) - // .transform(Arg.const(_.additionalArg, List("const"))) + .transform(Arg.const(_.additionalArg, List("const"))) - // assertEquals(actual, expected) + assertEquals(actual, expected) } - // test("Arg.computed properly applies a function to an argument") { - // def method(str: String, int: Int, additionalArg: List[String]) = TestClassWithAdditionalList(int, str, additionalArg) - - // val expected = TestClassWithAdditionalList(1, "str", List("str")) + test("Arg.computed properly applies a function to an argument") { + def method(str: String, int: Int, additionalArg: List[String]) = TestClassWithAdditionalList(int, str, additionalArg) - // val actual = - // testClass - // .intoVia(method) - // .transform(Arg.computed(_.additionalArg, testClass => List(testClass.str))) + val expected = TestClassWithAdditionalList(1, "str", List("str")) - // assertEquals(actual, expected) - // } + val actual = + testClass + .intoVia(method) + .transform(Arg.computed(_.additionalArg, testClass => List(testClass.str))) - // test("Arg.renamed properly uses a different field for that argument") { - // def method(str: String, int: Int, additionalArg: String) = TestClassWithAdditionalString(int, str, additionalArg) + assertEquals(actual, expected) + } - // val expected = TestClassWithAdditionalString(1, "str", "str") + test("Arg.renamed properly uses a different field for that argument") { + def method(str: String, int: Int, additionalArg: String) = TestClassWithAdditionalString(int, str, additionalArg) - // val actual = - // testClass - // .intoVia(method) - // .transform(Arg.renamed(_.additionalArg, _.str)) + val expected = TestClassWithAdditionalString(1, "str", "str") - // assertEquals(actual, expected) - // } + val actual = + testClass + .intoVia(method) + .transform(Arg.renamed(_.additionalArg, _.str)) - // test("The last ") { - // def method(str: String, int: Int, additionalArg: String) = TestClassWithAdditionalString(int, str, additionalArg) + assertEquals(actual, expected) + } - // val expected = TestClassWithAdditionalString(1, "str", "str") + test("The last ") { + def method(str: String, int: Int, additionalArg: String) = TestClassWithAdditionalString(int, str, additionalArg) - // val actual = - // testClass - // .intoVia(method) - // .transform(Arg.renamed(_.additionalArg, _.str)) + val expected = TestClassWithAdditionalString(1, "str", "str") - // assertEquals(actual, expected) - // } + val actual = + testClass + .intoVia(method) + .transform(Arg.renamed(_.additionalArg, _.str)) - // test("Builder reports a missing argument") { - // assertFailsToCompileWith { - // """ - // def method(str: String, int: Int, additionalArg: String) = TestClassWithAdditionalString(int, str, additionalArg) + assertEquals(actual, expected) + } - // val actual = - // testClass - // .intoVia(method) - // .transform() - // """ - // }("No field named 'additionalArg' found in TestClass") - // } + test("Builder reports a missing argument") { + assertFailsToCompileWith { + """ + def method(str: String, int: Int, additionalArg: String) = TestClassWithAdditionalString(int, str, additionalArg) + + val actual = + testClass + .intoVia(method) + .transform() + """ + }("No field named 'additionalArg' found in TestClass") + } } object AppliedViaBuilderSuite { diff --git a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/DefinitionViaBuilderSuite.scala b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/DefinitionViaBuilderSuite.scala index ea41a379..450e179e 100644 --- a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/DefinitionViaBuilderSuite.scala +++ b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/DefinitionViaBuilderSuite.scala @@ -1,69 +1,69 @@ -// package io.github.arainko.ducktapetest.builder +package io.github.arainko.ducktapetest.builder -// import io.github.arainko.ducktapetest.* -// import io.github.arainko.ducktape.* -// import io.github.arainko.ducktapetest.builder.DefinitionViaBuilderSuite.* -// import munit.* +import io.github.arainko.ducktapetest.* +import io.github.arainko.ducktape.* +import io.github.arainko.ducktapetest.builder.DefinitionViaBuilderSuite.* +import munit.* -// class DefinitionViaBuilderSuite extends DucktapeSuite { -// private val testClass = TestClass("str", 1) +class DefinitionViaBuilderSuite extends DucktapeSuite { + private val testClass = TestClass("str", 1) -// test("Arg.const properly applies a constant to an argument") { -// def method(str: String, int: Int, additionalArg: List[String]) = TestClassWithAdditionalList(int, str, additionalArg) + test("Arg.const properly applies a constant to an argument") { + def method(str: String, int: Int, additionalArg: List[String]) = TestClassWithAdditionalList(int, str, additionalArg) -// val expected = TestClassWithAdditionalList(1, "str", List("const")) + val expected = TestClassWithAdditionalList(1, "str", List("const")) -// val transformer = -// Transformer -// .defineVia[TestClass](method) -// .build(Arg.const(_.additionalArg, List("const"))) + val transformer = + Transformer + .defineVia[TestClass](method) + .build(Arg.const(_.additionalArg, List("const"))) -// assertEquals(transformer.transform(testClass), expected) -// } + assertEquals(transformer.transform(testClass), expected) + } -// test("Arg.computed properly applies a function to an argument") { -// def method(str: String, int: Int, additionalArg: List[String]) = TestClassWithAdditionalList(int, str, additionalArg) + test("Arg.computed properly applies a function to an argument") { + def method(str: String, int: Int, additionalArg: List[String]) = TestClassWithAdditionalList(int, str, additionalArg) -// val expected = TestClassWithAdditionalList(1, "str", List("str")) + val expected = TestClassWithAdditionalList(1, "str", List("str")) -// val transformer = -// Transformer -// .defineVia[TestClass](method) -// .build(Arg.computed(_.additionalArg, testClass => List(testClass.str))) + val transformer = + Transformer + .defineVia[TestClass](method) + .build(Arg.computed(_.additionalArg, testClass => List(testClass.str))) -// assertEquals(transformer.transform(testClass), expected) -// } + assertEquals(transformer.transform(testClass), expected) + } -// test("Arg.renamed properly uses a different field for that argument") { -// def method(str: String, int: Int, additionalArg: String) = TestClassWithAdditionalString(int, str, additionalArg) + test("Arg.renamed properly uses a different field for that argument") { + def method(str: String, int: Int, additionalArg: String) = TestClassWithAdditionalString(int, str, additionalArg) -// val expected = TestClassWithAdditionalString(1, "str", "str") + val expected = TestClassWithAdditionalString(1, "str", "str") -// val transformer = -// Transformer -// .defineVia[TestClass](method) -// .build(Arg.renamed(_.additionalArg, _.str)) + val transformer = + Transformer + .defineVia[TestClass](method) + .build(Arg.renamed(_.additionalArg, _.str)) -// assertEquals(transformer.transform(testClass), expected) -// } + assertEquals(transformer.transform(testClass), expected) + } -// test("Builder reports a missing argument") { -// assertFailsToCompileWith { -// """ -// def method(str: String, int: Int, additionalArg: String) = TestClassWithAdditionalString(int, str, additionalArg) + test("Builder reports a missing argument") { + assertFailsToCompileWith { + """ + def method(str: String, int: Int, additionalArg: String) = TestClassWithAdditionalString(int, str, additionalArg) -// val transformer = -// Transformer -// .defineVia[TestClass](method) -// .build() -// """ -// }("No field named 'additionalArg' found in TestClass") -// } + val transformer = + Transformer + .defineVia[TestClass](method) + .build() + """ + }("No field named 'additionalArg' found in TestClass") + } -// } +} -// object DefinitionViaBuilderSuite { -// final case class TestClass(str: String, int: Int) -// final case class TestClassWithAdditionalList(int: Int, str: String, additionalArg: List[String]) -// final case class TestClassWithAdditionalString(int: Int, str: String, additionalArg: String) -// } +object DefinitionViaBuilderSuite { + final case class TestClass(str: String, int: Int) + final case class TestClassWithAdditionalList(int: Int, str: String, additionalArg: List[String]) + final case class TestClassWithAdditionalString(int: Int, str: String, additionalArg: String) +} From 3c42e0783a60b9da9e19230c2533986c25b2707e Mon Sep 17 00:00:00 2001 From: Aleksander Rainko Date: Sun, 25 Sep 2022 03:13:08 +0200 Subject: [PATCH 8/8] add @compileTimeOnly where applicable, delete `instance` values in BuilderConfig/ArgBuilderConfig, introduce NotQuotedException, add scalafix, hide all the internal classes, improve the 3.2.0 regression workaround --- .scalafix.conf | 10 +++++ README.md | 6 +-- build.sbt | 3 +- .../arainko/ducktape/ArgBuilderConfig.scala | 21 +++++----- .../arainko/ducktape/BuilderConfig.scala | 38 ++++++++++--------- .../github/arainko/ducktape/Transformer.scala | 9 ++--- .../ducktape/builder/AppliedViaBuilder.scala | 16 ++++---- .../builder/DefinitionViaBuilder.scala | 13 +++---- .../ducktape/function/FunctionMirror.scala | 1 + .../internal/NotQuotedException.scala | 9 +++++ .../macros/CoproductTransformerMacros.scala | 3 +- .../internal/macros/DebugMacros.scala | 5 ++- .../macros/ProductTransformerMacros.scala | 5 ++- .../internal/macros/TransformerMacros.scala | 2 +- .../modules/ConfigurationModule.scala | 4 +- .../internal/modules/FieldModule.scala | 7 ++-- .../internal/modules/MirrorModule.scala | 4 +- .../ducktape/internal/modules/Module.scala | 13 +++---- .../internal/modules/SelectorModule.scala | 20 +++++----- .../io/github/arainko/ducktape/syntax.scala | 5 ++- .../ducktapetest/FunctionMirrorSuite.scala | 2 +- .../builder/AppliedBuilderSuite.scala | 4 +- .../builder/AppliedViaBuilderSuite.scala | 19 ++-------- .../builder/DefinitionViaBuilderSuite.scala | 2 +- .../derivation/DerivedTransformerSuite.scala | 4 +- .../ducktapetest/derivation/ViaSuite.scala | 5 ++- project/plugins.sbt | 3 +- 27 files changed, 126 insertions(+), 107 deletions(-) create mode 100644 .scalafix.conf create mode 100644 ducktape/src/main/scala/io/github/arainko/ducktape/internal/NotQuotedException.scala diff --git a/.scalafix.conf b/.scalafix.conf new file mode 100644 index 00000000..d0c3577b --- /dev/null +++ b/.scalafix.conf @@ -0,0 +1,10 @@ +rules = [ + OrganizeImports +] + +OrganizeImports { + coalesceToWildcardImportThreshold = 3 + groupedImports = AggressiveMerge + + removeUnused = false +} diff --git a/README.md b/README.md index b15bf853..45d4dcf4 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ If this project interests you, please drop a 🌟 - these things are worthless b ### Installation ```scala -libraryDependencies += "io.github.arainko" %% "ducktape" % "0.1.0-RC1" +libraryDependencies += "io.github.arainko" %% "ducktape" % "0.1.0-RC2" ``` ### Examples @@ -322,13 +322,13 @@ val definedViaTransformer = Transformer .defineVia[TestClass](method) .build(Arg.const(_.additionalArg, List("const"))) -// definedViaTransformer: Transformer[TestClass, TestClassWithAdditionalList] = repl.MdocSession$MdocApp6$$Lambda$8962/0x0000000802e7e040@61870c88 +// definedViaTransformer: Transformer[TestClass, TestClassWithAdditionalList] = repl.MdocSession$MdocApp6$$Lambda$41195/0x000000010897d840@6ee2238f val definedTransformer = Transformer .define[TestClass, TestClassWithAdditionalList] .build(Field.const(_.additionalArg, List("const"))) -// definedTransformer: Transformer[TestClass, TestClassWithAdditionalList] = repl.MdocSession$MdocApp6$$Lambda$8963/0x0000000802e7e440@2f13a6ac +// definedTransformer: Transformer[TestClass, TestClassWithAdditionalList] = repl.MdocSession$MdocApp6$$Lambda$41196/0x000000010897dc40@3eb47bb1 val transformedVia = definedViaTransformer.transform(testClass) // transformedVia: TestClassWithAdditionalList = TestClassWithAdditionalList( diff --git a/build.sbt b/build.sbt index 4fc5647c..3fed8626 100644 --- a/build.sbt +++ b/build.sbt @@ -14,6 +14,7 @@ ThisBuild / developers := List( ) ) ThisBuild / scalaVersion := "3.2.0" +ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.6.0" name := "ducktape" sonatypeRepository := "https://s01.oss.sonatype.org/service/local" @@ -29,7 +30,7 @@ lazy val ducktape = project .in(file("ducktape")) .settings( - scalacOptions ++= List("-Xcheck-macros", "-no-indent", "-old-syntax"), + scalacOptions ++= List("-Xcheck-macros", "-no-indent", "-old-syntax", "-Xfatal-warnings"), libraryDependencies += "org.scalameta" %% "munit" % "1.0.0-M6" % Test ) diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/ArgBuilderConfig.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/ArgBuilderConfig.scala index a0b1253e..23b87555 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/ArgBuilderConfig.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/ArgBuilderConfig.scala @@ -1,19 +1,16 @@ package io.github.arainko.ducktape import io.github.arainko.ducktape.function.FunctionArguments +import io.github.arainko.ducktape.internal.NotQuotedException + +import scala.annotation.{ compileTimeOnly, implicitNotFound } import scala.deriving.Mirror -import scala.annotation.implicitNotFound -import scala.annotation.compileTimeOnly opaque type ArgBuilderConfig[Source, Dest, ArgSelector <: FunctionArguments] = Unit -object ArgBuilderConfig { - private[ducktape] def instance[Source, Dest, ArgSelector <: FunctionArguments]: ArgBuilderConfig[Source, Dest, ArgSelector] = () -} - -//TODO: Slap a @compileTimeOnly on all things here object Arg { + @compileTimeOnly("'Arg.const' needs to be erased from the AST with a macro.") def const[Source, Dest, ArgType, ActualType, ArgSelector <: FunctionArguments]( selector: ArgSelector => ArgType, const: ActualType @@ -21,8 +18,9 @@ object Arg { @implicitNotFound("Arg.const is only supported for product types but ${Source} is not a product type.") ev1: Mirror.ProductOf[Source], ev2: ActualType <:< ArgType - ): ArgBuilderConfig[Source, Dest, ArgSelector] = ArgBuilderConfig.instance + ): ArgBuilderConfig[Source, Dest, ArgSelector] = throw NotQuotedException("Arg.const") + @compileTimeOnly("'Arg.computed' needs to be erased from the AST with a macro.") def computed[Source, Dest, ArgType, ActualType, ArgSelector <: FunctionArguments]( selector: ArgSelector => ArgType, f: Source => ActualType @@ -30,15 +28,16 @@ object Arg { @implicitNotFound("Arg.computed is only supported for product types but ${Source} is not a product type.") ev1: Mirror.ProductOf[Source], ev2: ActualType <:< ArgType - ): ArgBuilderConfig[Source, Dest, ArgSelector] = ArgBuilderConfig.instance + ): ArgBuilderConfig[Source, Dest, ArgSelector] = throw NotQuotedException("Arg.computed") + @compileTimeOnly("'Arg.renamed' needs to be erased from the AST with a macro.") def renamed[Source, Dest, ArgType, FieldType, ArgSelector <: FunctionArguments]( destSelector: ArgSelector => ArgType, - sourceSelector: Source => FieldType, + sourceSelector: Source => FieldType )(using @implicitNotFound("Arg.renamed is only supported for product types but ${Source} is not a product type.") ev1: Mirror.ProductOf[Source], ev2: FieldType <:< ArgType - ): ArgBuilderConfig[Source, Dest, ArgSelector] = ArgBuilderConfig.instance + ): ArgBuilderConfig[Source, Dest, ArgSelector] = throw NotQuotedException("Arg.renamed") } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/BuilderConfig.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/BuilderConfig.scala index 8278b025..1b21c3d8 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/BuilderConfig.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/BuilderConfig.scala @@ -1,33 +1,34 @@ package io.github.arainko.ducktape +import io.github.arainko.ducktape.internal.NotQuotedException + +import scala.annotation.{ compileTimeOnly, implicitNotFound } import scala.deriving.Mirror import scala.util.NotGiven -import scala.annotation.implicitNotFound opaque type BuilderConfig[Source, Dest] = Unit -object BuilderConfig { - private[ducktape] def instance[Source, Dest]: BuilderConfig[Source, Dest] = () -} - -//TODO: Slap a @compileTimeOnly on all things here object Field { + + @compileTimeOnly("'Field.const' needs to be erased from the AST with a macro.") def const[Source, Dest, FieldType, ActualType](selector: Dest => FieldType, value: ActualType)(using ev1: ActualType <:< FieldType, @implicitNotFound("Field.const is supported for product types only, but ${Source} is not a product type.") ev2: Mirror.ProductOf[Source], @implicitNotFound("Field.const is supported for product types only, but ${Dest} is not a product type.") ev3: Mirror.ProductOf[Dest] - ): BuilderConfig[Source, Dest] = BuilderConfig.instance + ): BuilderConfig[Source, Dest] = throw NotQuotedException("Field.const") + @compileTimeOnly("'Field.computed' needs to be erased from the AST with a macro.") def computed[Source, Dest, FieldType, ActualType](selector: Dest => FieldType, f: Source => ActualType)(using ev1: ActualType <:< FieldType, @implicitNotFound("Field.computed is supported for product types only, but ${Source} is not a product type.") ev2: Mirror.ProductOf[Source], @implicitNotFound("Field.computed is supported for product types only, but ${Dest} is not a product type.") ev3: Mirror.ProductOf[Dest] - ): BuilderConfig[Source, Dest] = BuilderConfig.instance + ): BuilderConfig[Source, Dest] = throw NotQuotedException("Field.computed") + @compileTimeOnly("'Field.renamed' needs to be erased from the AST with a macro.") def renamed[Source, Dest, SourceFieldType, DestFieldType]( destSelector: Dest => DestFieldType, sourceSelector: Source => SourceFieldType @@ -37,44 +38,47 @@ object Field { ev2: Mirror.ProductOf[Source], @implicitNotFound("Field.renamed is supported for product types only, but ${Dest} is not a product type.") ev3: Mirror.ProductOf[Dest] - ): BuilderConfig[Source, Dest] = BuilderConfig.instance + ): BuilderConfig[Source, Dest] = throw NotQuotedException("Field.renamed") } //TODO: Slap a @compileTimeOnly on all things here object Case { - def const[SourceSubtype]: Case.Const[SourceSubtype] = Const.instance - def computed[SourceSubtype]: Case.Computed[SourceSubtype] = Computed.instance + @compileTimeOnly("'Case.const' needs to be erased from the AST with a macro.") + def const[SourceSubtype]: Case.Const[SourceSubtype] = throw NotQuotedException("Case.const") + + @compileTimeOnly("'Case.computed' needs to be erased from the AST with a macro.") + def computed[SourceSubtype]: Case.Computed[SourceSubtype] = throw NotQuotedException("Case.computed") opaque type Computed[SourceSubtype] = Unit object Computed { - private[ducktape] def instance[SourceSubtype]: Computed[SourceSubtype] = () - extension [SourceSubtype](inst: Computed[SourceSubtype]) { + + @compileTimeOnly("'Case.computed' needs to be erased from the AST with a macro.") def apply[Source, Dest](f: SourceSubtype => Dest)(using @implicitNotFound("Case.computed is only supported for coproducts but ${Source} is not a coproduct.") ev1: Mirror.SumOf[Source], ev2: SourceSubtype <:< Source, @implicitNotFound("Case.computed is only supported for subtypes of ${Source}.") ev3: NotGiven[SourceSubtype =:= Source] - ): BuilderConfig[Source, Dest] = BuilderConfig.instance + ): BuilderConfig[Source, Dest] = throw NotQuotedException("Case.computed") } } opaque type Const[SourceSubtype] = Unit object Const { - private[ducktape] def instance[SourceSubtype]: Const[SourceSubtype] = () - extension [SourceSubtype](inst: Const[SourceSubtype]) { + + @compileTimeOnly("'Case.const' needs to be erased from the AST with a macro.") def apply[Source, Dest](const: Dest)(using @implicitNotFound("Case.computed is only supported for coproducts but ${Source} is not a coproduct.") ev1: Mirror.SumOf[Source], ev2: SourceSubtype <:< Source, @implicitNotFound("Case.instance is only supported for subtypes of ${Source}.") ev3: NotGiven[SourceSubtype =:= Source] - ): BuilderConfig[Source, Dest] = BuilderConfig.instance + ): BuilderConfig[Source, Dest] = throw NotQuotedException("Case.const") } } } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/Transformer.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/Transformer.scala index 57a1a8e4..72803b83 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/Transformer.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/Transformer.scala @@ -3,8 +3,7 @@ package io.github.arainko.ducktape import io.github.arainko.ducktape.builder.* import io.github.arainko.ducktape.internal.macros.* -import scala.collection.BuildFrom -import scala.collection.Factory +import scala.collection.{ BuildFrom, Factory } import scala.compiletime.* import scala.deriving.Mirror @@ -20,12 +19,12 @@ object Transformer { def defineVia[A]: DefinitionViaBuilder.PartiallyApplied[A] = DefinitionViaBuilder.create[A] - sealed trait Identity[Source] extends Transformer[Source, Source] - - given [Source]: Identity[Source] = new { + final class Identity[Source] private[Transformer] extends Transformer[Source, Source] { def transform(from: Source): Source = from } + given [Source]: Identity[Source] = Identity[Source] + inline given forProducts[Source, Dest](using Mirror.ProductOf[Source], Mirror.ProductOf[Dest]): Transformer[Source, Dest] = from => ProductTransformerMacros.transform(from) diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/builder/AppliedViaBuilder.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/builder/AppliedViaBuilder.scala index aa64f5d2..498fabfd 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/builder/AppliedViaBuilder.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/builder/AppliedViaBuilder.scala @@ -1,12 +1,13 @@ package io.github.arainko.ducktape.builder import io.github.arainko.ducktape.* -import io.github.arainko.ducktape.internal.macros.* -import scala.deriving.Mirror import io.github.arainko.ducktape.function.* +import io.github.arainko.ducktape.internal.macros.* + import scala.compiletime.* +import scala.deriving.Mirror -sealed abstract class AppliedViaBuilder[Source, Dest, Func, ArgSelector <: FunctionArguments]( +final class AppliedViaBuilder[Source, Dest, Func, ArgSelector <: FunctionArguments] private ( source: Source, function: Func ) { @@ -18,14 +19,13 @@ sealed abstract class AppliedViaBuilder[Source, Dest, Func, ArgSelector <: Funct } object AppliedViaBuilder { - private[AppliedViaBuilder] class Impl[Source, Dest, Func, ArgSelector <: FunctionArguments]( + private def instance[Source, Dest, Func, ArgSelector <: FunctionArguments]( source: Source, function: Func - ) extends AppliedViaBuilder[Source, Dest, Func, ArgSelector](source, function) + ) = AppliedViaBuilder[Source, Dest, Func, ArgSelector](source, function) transparent inline def create[Source, Func](source: Source, inline func: Func)(using Func: FunctionMirror[Func]) = { - // widen the type to not infer `AppliedViaBuilder.Impl`, we're in a transparent inline method after all - val builder: AppliedViaBuilder[Source, Func.Return, Func, Nothing] = Impl(source, func) - FunctionMacros.namedArguments(func, builder) + val builder = instance[Source, Func.Return, Func, Nothing](source, func) + FunctionMacros.namedArguments(func, instance[Source, Func.Return, Func, Nothing](source, func)) } } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/builder/DefinitionViaBuilder.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/builder/DefinitionViaBuilder.scala index 4a55ed6d..fd416d8c 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/builder/DefinitionViaBuilder.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/builder/DefinitionViaBuilder.scala @@ -1,11 +1,12 @@ package io.github.arainko.ducktape.builder import io.github.arainko.ducktape.* +import io.github.arainko.ducktape.function.* import io.github.arainko.ducktape.internal.macros.* + import scala.deriving.* -import io.github.arainko.ducktape.function.* -sealed abstract class DefinitionViaBuilder[Source, Dest, Func, ArgSelector <: FunctionArguments](function: Func) { +final class DefinitionViaBuilder[Source, Dest, Func, ArgSelector <: FunctionArguments] private (function: Func) { inline def build( inline config: ArgBuilderConfig[Source, Dest, ArgSelector]* @@ -14,9 +15,8 @@ sealed abstract class DefinitionViaBuilder[Source, Dest, Func, ArgSelector <: Fu } object DefinitionViaBuilder { - private[DefinitionViaBuilder] class Impl[Source, Dest, Func, ArgSelector <: FunctionArguments]( - function: Func - ) extends DefinitionViaBuilder[Source, Dest, Func, ArgSelector](function) + private def instance[Source, Dest, Func, ArgSelector <: FunctionArguments](function: Func) = + DefinitionViaBuilder[Source, Dest, Func, ArgSelector](function) def create[Source]: PartiallyApplied[Source] = () @@ -25,8 +25,7 @@ object DefinitionViaBuilder { object PartiallyApplied { extension [Source](partial: PartiallyApplied[Source]) { transparent inline def apply[Func](inline func: Func)(using Func: FunctionMirror[Func]) = { - // widen the type to not infer `DefinitionViaBuilder.Impl`, we're in a transparent inline method after all - val builder: DefinitionViaBuilder[Source, Func.Return, Func, Nothing] = Impl(func) + val builder = instance[Source, Func.Return, Func, Nothing](func) FunctionMacros.namedArguments(func, builder) } } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/function/FunctionMirror.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/function/FunctionMirror.scala index 7567c2dd..48bb685f 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/function/FunctionMirror.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/function/FunctionMirror.scala @@ -1,6 +1,7 @@ package io.github.arainko.ducktape.function import io.github.arainko.ducktape.internal.macros.* + import scala.annotation.implicitNotFound @implicitNotFound("FunctionMirrors are only available for function types, but got ${F}") diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/NotQuotedException.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/NotQuotedException.scala new file mode 100644 index 00000000..f98cd271 --- /dev/null +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/NotQuotedException.scala @@ -0,0 +1,9 @@ +package io.github.arainko.ducktape.internal + +private[ducktape] final class NotQuotedException(name: String) + extends Exception( + s""" + |'$name' was not lifted away from the AST with a macro but also skirted past the compiler into the runtime. + |This is not good. + |Please file an issue on the 'ducktape' repository.""".stripMargin + ) diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/CoproductTransformerMacros.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/CoproductTransformerMacros.scala index 99060bb9..e1e9d8ae 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/CoproductTransformerMacros.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/CoproductTransformerMacros.scala @@ -1,9 +1,10 @@ package io.github.arainko.ducktape.internal.macros -import scala.quoted.* import io.github.arainko.ducktape.* import io.github.arainko.ducktape.internal.modules.* + import scala.deriving.* +import scala.quoted.* private[ducktape] class CoproductTransformerMacros(using val quotes: Quotes) extends Module, diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/DebugMacros.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/DebugMacros.scala index b2a76f41..83d781bc 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/DebugMacros.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/DebugMacros.scala @@ -1,9 +1,10 @@ package io.github.arainko.ducktape.internal.macros -import scala.quoted.* import io.github.arainko.ducktape.* -object DebugMacros { +import scala.quoted.* + +private[ducktape] object DebugMacros { inline def structure[A](inline value: A) = ${ structureMacro('value) } def structureMacro[A: Type](value: Expr[A])(using Quotes) = { diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/ProductTransformerMacros.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/ProductTransformerMacros.scala index 60f991db..5c208747 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/ProductTransformerMacros.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/ProductTransformerMacros.scala @@ -1,10 +1,11 @@ package io.github.arainko.ducktape.internal.macros -import scala.quoted.* -import io.github.arainko.ducktape.internal.modules.* import io.github.arainko.ducktape.* import io.github.arainko.ducktape.function.* +import io.github.arainko.ducktape.internal.modules.* + import scala.deriving.* +import scala.quoted.* private[ducktape] class ProductTransformerMacros(using val quotes: Quotes) extends Module, diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/TransformerMacros.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/TransformerMacros.scala index 8f872b78..9a04b87b 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/TransformerMacros.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/macros/TransformerMacros.scala @@ -27,7 +27,7 @@ private[ducktape] final class TransformerMacros(using val quotes: Quotes) extend } -object TransformerMacros { +private[ducktape] object TransformerMacros { inline def transformConfigured[Source, Dest]( sourceValue: Source, diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/ConfigurationModule.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/ConfigurationModule.scala index 0291e728..0a9b7877 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/ConfigurationModule.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/ConfigurationModule.scala @@ -1,11 +1,11 @@ package io.github.arainko.ducktape.internal.modules -import io.github.arainko.ducktape.{ Case => CaseConfig, Field => FieldConfig, _ } import io.github.arainko.ducktape.function.FunctionArguments +import io.github.arainko.ducktape.{ Case => CaseConfig, Field => FieldConfig, _ } import scala.quoted.* -private[internal] trait ConfigurationModule { self: Module & SelectorModule & MirrorModule & FieldModule & CaseModule => +private[ducktape] trait ConfigurationModule { self: Module & SelectorModule & MirrorModule & FieldModule & CaseModule => import quotes.reflect.* sealed trait MaterializedConfiguration diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/FieldModule.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/FieldModule.scala index 6326b7d6..95a6deec 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/FieldModule.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/FieldModule.scala @@ -1,11 +1,12 @@ package io.github.arainko.ducktape.internal.modules -import scala.quoted.* -import scala.deriving.* import io.github.arainko.ducktape.Transformer -import scala.compiletime.* import io.github.arainko.ducktape.function.FunctionArguments +import scala.compiletime.* +import scala.deriving.* +import scala.quoted.* + private[internal] trait FieldModule { self: Module & MirrorModule => import quotes.reflect.* diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/MirrorModule.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/MirrorModule.scala index ef08eb0d..856b7db5 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/MirrorModule.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/MirrorModule.scala @@ -1,12 +1,12 @@ package io.github.arainko.ducktape.internal.modules -import scala.quoted.* import scala.annotation.tailrec import scala.deriving.Mirror +import scala.quoted.* // Lifted from shapeless 3: // https://github.com/typelevel/shapeless-3/blob/main/modules/deriving/src/main/scala/shapeless3/deriving/internals/reflectionutils.scala -private[internal] trait MirrorModule { self: Module => +private[ducktape] trait MirrorModule { self: Module => import quotes.reflect.* final case class MaterializedMirror private ( diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/Module.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/Module.scala index 0e394d75..550ae02b 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/Module.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/Module.scala @@ -1,12 +1,11 @@ package io.github.arainko.ducktape.internal.modules -import scala.quoted.* -import scala.deriving.* -import io.github.arainko.ducktape.Transformer +import io.github.arainko.ducktape.{ Arg, Transformer } + import scala.deriving.* -import io.github.arainko.ducktape.Arg +import scala.quoted.* -private[internal] trait Module { +private[ducktape] trait Module { val quotes: Quotes given Quotes = quotes @@ -83,8 +82,8 @@ private[internal] trait Module { enum InvalidArgSelector extends Failure { override def position: Position = this match { - case NotFound(selector, _, _) => selector.asTerm.pos - case NotAnArgSelector(selector, _) => selector.asTerm.pos + case NotFound(selector, _, _) => selector.asTerm.pos + case NotAnArgSelector(selector, _) => selector.asTerm.pos } final def render = this match { diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/SelectorModule.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/SelectorModule.scala index bd739bee..15bdb817 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/SelectorModule.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/modules/SelectorModule.scala @@ -1,11 +1,12 @@ package io.github.arainko.ducktape.internal.modules -import scala.quoted.* -import scala.deriving.* import io.github.arainko.ducktape.function.* + +import scala.deriving.* +import scala.quoted.* import scala.util.NotGiven -private[internal] trait SelectorModule { self: Module & MirrorModule & FieldModule => +private[ducktape] trait SelectorModule { self: Module & MirrorModule & FieldModule => import quotes.reflect.* object Selectors { @@ -54,17 +55,18 @@ private[internal] trait SelectorModule { self: Module & MirrorModule & FieldModu private object ArgSelector { def unapply(arg: Term): Option[String] = PartialFunction.condOpt(arg) { - case Lambda(_, DynamicSelector(argumentName)) => argumentName + case Lambda(_, FunctionArgumentSelector(argumentName)) => argumentName } } - private object DynamicSelector { + private object FunctionArgumentSelector { def unapply(arg: Term): Option[String] = PartialFunction.condOpt(arg.asExpr) { - case '{ - type argSelector <: FunctionArguments - ($args: `argSelector`).selectDynamic($selectedArg).$asInstanceOf$[tpe] - } => selectedArg.valueOrAbort + case '{ + type argSelector <: FunctionArguments + ($args: `argSelector`).selectDynamic($selectedArg).$asInstanceOf$[tpe] + } => + selectedArg.valueOrAbort } } } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/syntax.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/syntax.scala index f1c032e5..849c2248 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/syntax.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/syntax.scala @@ -1,9 +1,10 @@ package io.github.arainko.ducktape -import io.github.arainko.ducktape.internal.macros.* +import io.github.arainko.ducktape.builder.* import io.github.arainko.ducktape.function.* +import io.github.arainko.ducktape.internal.macros.* + import scala.deriving.Mirror -import io.github.arainko.ducktape.builder.* extension [Source](value: Source) { def into[Dest]: AppliedBuilder[Source, Dest] = AppliedBuilder(value) diff --git a/ducktape/src/test/scala/io/github/arainko/ducktapetest/FunctionMirrorSuite.scala b/ducktape/src/test/scala/io/github/arainko/ducktapetest/FunctionMirrorSuite.scala index 598123e2..f085df7a 100644 --- a/ducktape/src/test/scala/io/github/arainko/ducktapetest/FunctionMirrorSuite.scala +++ b/ducktape/src/test/scala/io/github/arainko/ducktapetest/FunctionMirrorSuite.scala @@ -1,7 +1,7 @@ package io.github.arainko.ducktapetest -import munit.FunSuite import io.github.arainko.ducktape.function.FunctionMirror +import munit.FunSuite class FunctionMirrorSuite extends FunSuite { test("derives FunctionMirror for 0 arg functions") { diff --git a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedBuilderSuite.scala b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedBuilderSuite.scala index 985eb8c0..7a88d379 100644 --- a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedBuilderSuite.scala +++ b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedBuilderSuite.scala @@ -1,7 +1,8 @@ package io.github.arainko.ducktapetest.builder -import io.github.arainko.ducktapetest.DucktapeSuite import io.github.arainko.ducktape.* +import io.github.arainko.ducktapetest.DucktapeSuite + import scala.deriving.Mirror class AppliedBuilderSuite extends DucktapeSuite { @@ -102,6 +103,7 @@ class AppliedBuilderSuite extends DucktapeSuite { } test("Case.computed applies a function to that given subtype") { + def actual(value: NotEnumMoreCases) = value .into[MoreCases] diff --git a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala index c016facf..66c1d364 100644 --- a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala +++ b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/AppliedViaBuilderSuite.scala @@ -1,10 +1,10 @@ package io.github.arainko.ducktapetest.builder -import io.github.arainko.ducktapetest.DucktapeSuite -import io.github.arainko.ducktapetest.builder.AppliedViaBuilderSuite.* import io.github.arainko.ducktape.* -import io.github.arainko.ducktape.function.FunctionArguments import io.github.arainko.ducktape.builder.AppliedViaBuilder +import io.github.arainko.ducktape.function.FunctionArguments +import io.github.arainko.ducktapetest.DucktapeSuite +import io.github.arainko.ducktapetest.builder.AppliedViaBuilderSuite.* class AppliedViaBuilderSuite extends DucktapeSuite { private val testClass = TestClass("str", 1) @@ -48,19 +48,6 @@ class AppliedViaBuilderSuite extends DucktapeSuite { assertEquals(actual, expected) } - test("The last ") { - def method(str: String, int: Int, additionalArg: String) = TestClassWithAdditionalString(int, str, additionalArg) - - val expected = TestClassWithAdditionalString(1, "str", "str") - - val actual = - testClass - .intoVia(method) - .transform(Arg.renamed(_.additionalArg, _.str)) - - assertEquals(actual, expected) - } - test("Builder reports a missing argument") { assertFailsToCompileWith { """ diff --git a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/DefinitionViaBuilderSuite.scala b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/DefinitionViaBuilderSuite.scala index 450e179e..803dd1b0 100644 --- a/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/DefinitionViaBuilderSuite.scala +++ b/ducktape/src/test/scala/io/github/arainko/ducktapetest/builder/DefinitionViaBuilderSuite.scala @@ -1,7 +1,7 @@ package io.github.arainko.ducktapetest.builder -import io.github.arainko.ducktapetest.* import io.github.arainko.ducktape.* +import io.github.arainko.ducktapetest.* import io.github.arainko.ducktapetest.builder.DefinitionViaBuilderSuite.* import munit.* diff --git a/ducktape/src/test/scala/io/github/arainko/ducktapetest/derivation/DerivedTransformerSuite.scala b/ducktape/src/test/scala/io/github/arainko/ducktapetest/derivation/DerivedTransformerSuite.scala index c7f3ea33..b565cb51 100644 --- a/ducktape/src/test/scala/io/github/arainko/ducktapetest/derivation/DerivedTransformerSuite.scala +++ b/ducktape/src/test/scala/io/github/arainko/ducktapetest/derivation/DerivedTransformerSuite.scala @@ -1,13 +1,13 @@ package io.github.arainko.ducktapetest.derivation import io.github.arainko.ducktape.* +import io.github.arainko.ducktape.internal.macros.* +import io.github.arainko.ducktapetest.DucktapeSuite import io.github.arainko.ducktapetest.model.* import munit.FunSuite import scala.compiletime.testing.* -import io.github.arainko.ducktape.internal.macros.* import scala.deriving.Mirror -import io.github.arainko.ducktapetest.DucktapeSuite object DerivedTransformerSuite { // If these are declared inside their tests the compiler crashes 🤔 diff --git a/ducktape/src/test/scala/io/github/arainko/ducktapetest/derivation/ViaSuite.scala b/ducktape/src/test/scala/io/github/arainko/ducktapetest/derivation/ViaSuite.scala index 48839ac9..070eaaaf 100644 --- a/ducktape/src/test/scala/io/github/arainko/ducktapetest/derivation/ViaSuite.scala +++ b/ducktape/src/test/scala/io/github/arainko/ducktapetest/derivation/ViaSuite.scala @@ -1,9 +1,10 @@ package io.github.arainko.ducktapetest.derivation import io.github.arainko.ducktape.* -import scala.compiletime.testing.* -import munit.FunSuite import io.github.arainko.ducktapetest.DucktapeSuite +import munit.FunSuite + +import scala.compiletime.testing.* final case class Input(int: Int, string: String, list: List[Int], option: Option[Int]) diff --git a/project/plugins.sbt b/project/plugins.sbt index 9e3a921b..1b389d91 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,4 @@ addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.9") -addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.3" ) \ No newline at end of file +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.3") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.10.3")