Skip to content

Commit

Permalink
workaround 3.2 regression, finalize Selectable arg selectors
Browse files Browse the repository at this point in the history
  • Loading branch information
arainko committed Sep 17, 2022
1 parent 41e2401 commit b94e276
Show file tree
Hide file tree
Showing 18 changed files with 188 additions and 244 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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

}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
) {
Expand All @@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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]] =
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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]]],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))

}
}
Loading

0 comments on commit b94e276

Please sign in to comment.