Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.arainko.ducktape.internal

import io.github.arainko.ducktape.*
import io.github.arainko.ducktape.internal.Configuration.*

private[ducktape] object ConfigInstructionRefiner {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ private[ducktape] object Context {
transparent inline def current(using ctx: Context): ctx.type = ctx

case class PossiblyFallible[G[+x]](
wrapperType: WrapperType.Wrapped[G],
wrapperType: WrapperType[G],
transformationSite: TransformationSite,
summoner: Summoner.PossiblyFallible[G],
mode: TransformationMode[G]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ private[ducktape] object FallibleTransformations {
configs: Expr[Seq[Field.Fallible[F, A, B] | Case.Fallible[F, A, B]]]
)(using Quotes): Expr[F[B]] = {
given Context.PossiblyFallible[F](
WrapperType.Wrapped(Type.of[F]),
WrapperType.create[F],
TransformationSite.fromStringExpr(transformationSite),
Summoner.PossiblyFallible[F],
TransformationMode.create(F)
Expand Down Expand Up @@ -50,7 +50,7 @@ private[ducktape] object FallibleTransformations {
configs: Expr[Seq[Field.Fallible[F, A, Args] | Case.Fallible[F, A, Args]]]
)(using Quotes): Expr[F[B]] = {
given Context.PossiblyFallible[F](
WrapperType.Wrapped(Type.of[F]),
WrapperType.create[F],
TransformationSite.fromStringExpr(transformationSite),
Summoner.PossiblyFallible[F],
TransformationMode.create(F)
Expand Down Expand Up @@ -92,7 +92,7 @@ private[ducktape] object FallibleTransformations {
import quotes.reflect.*

given Context.PossiblyFallible[F](
WrapperType.Wrapped(Type.of[F]),
WrapperType.create[F],
TransformationSite.Transformation,
Summoner.PossiblyFallible[F],
TransformationMode.create(F)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,13 @@ private[ducktape] object Plan {
) extends Plan[Nothing, Nothing]

case class BetweenFallibleNonFallible[+E <: Erroneous](
source: Structure.Wrappped[?],
source: Structure.Wrapped[?],
dest: Structure,
plan: Plan[E, Nothing]
) extends Plan[E, Fallible]

case class BetweenFallibles[+E <: Erroneous](
source: Structure.Wrappped[?],
source: Structure.Wrapped[?],
dest: Structure,
mode: TransformationMode.FailFast[?],
plan: Plan[E, Fallible]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,22 @@ private[ducktape] object Planner {
recurse(source, paramStruct)
)

// Wrapped(WrapperType.Optional) is isomorphic to Optional
// scalafmt: { maxColumn = 150 }
case (source @ Wrapped(_, WrapperType.Optional, _, srcUnderlying)) -> (dest @ Wrapped(_, WrapperType.Optional, _, destUnderlying)) =>
Plan.BetweenOptions(
Structure.Optional.fromWrapped(source),
Structure.Optional.fromWrapped(dest),
recurse(srcUnderlying, destUnderlying)
)

case source -> (dest @ Wrapped(_, WrapperType.Optional, _, underlying)) =>
Plan.BetweenNonOptionOption(
source,
Structure.Optional.fromWrapped(dest),
recurse(source, underlying)
)

case (source @ Collection(_, _, srcParamStruct)) -> (dest @ Collection(_, _, destParamStruct)) =>
Plan.BetweenCollections(
source,
Expand Down Expand Up @@ -246,8 +262,7 @@ private[ducktape] object Planner {
boundary[Plan.Error | plan.type]:
var owner = Symbol.spliceOwner
while (!owner.isNoSymbol) {
if owner == transformerSymbol then
boundary.break(Plan.Error.from(plan, ErrorMessage.LoopingTransformerDetected, None))
if owner == transformerSymbol then boundary.break(Plan.Error.from(plan, ErrorMessage.LoopingTransformerDetected, None))
owner = owner.maybeOwner
}
plan
Expand All @@ -260,7 +275,7 @@ private[ducktape] object Planner {
structs: (Structure, Structure)
)(using Quotes, Depth, Context.Of[F]): Option[Plan[Erroneous, F]] =
PartialFunction.condOpt(Context.current *: structs) {
case (ctx: Context.PossiblyFallible[f], source @ Wrappped(tpe, path, underlying), dest) =>
case (ctx: Context.PossiblyFallible[f], source @ Wrapped(tpe, _, path, underlying), dest) =>
// needed for the recurse call to return Plan[Erroneous, Nothing]
given Context.Total = ctx.toTotal
val plan = Plan.BetweenFallibleNonFallible(
Expand All @@ -283,7 +298,7 @@ private[ducktape] object Planner {
PartialFunction.condOpt(Context.current *: structs) {
case (
ctx @ Context.PossiblyFallible(_, _, _, mode: TransformationMode.FailFast[f]),
source @ Wrappped(tpe, path, underlying),
source @ Wrapped(tpe, _, path, underlying),
dest
) =>
ctx.reifyPlan[F] {
Expand All @@ -296,13 +311,8 @@ private[ducktape] object Planner {
}

case (
ctx @ Context.PossiblyFallible(
WrapperType.Wrapped(given Type[f]),
_,
_,
TransformationMode.Accumulating(mode, Some(localMode))
),
source @ Wrappped(tpe, path, underlying),
ctx @ Context.PossiblyFallible(_, _, _, TransformationMode.Accumulating(mode, Some(localMode))),
source @ Wrapped(tpe, _, path, underlying),
dest
) =>
ctx.reifyPlan[F] {
Expand All @@ -313,7 +323,6 @@ private[ducktape] object Planner {
recurse(underlying, dest)
)
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ private[ducktape] object Structure {

case class Optional(tpe: Type[? <: Option[?]], path: Path, paramStruct: Structure) extends Structure

object Optional {
def fromWrapped(wrapped: Wrapped[Option]): Optional =
Optional(wrapped.tpe, wrapped.path, wrapped.underlying)
}

case class Collection(tpe: Type[? <: Iterable[?]], path: Path, paramStruct: Structure) extends Structure

case class Singleton(tpe: Type[?], path: Path, name: String, value: Expr[Any]) extends Structure
Expand All @@ -60,7 +65,7 @@ private[ducktape] object Structure {

case class ValueClass(tpe: Type[? <: AnyVal], path: Path, paramTpe: Type[?], paramFieldName: String) extends Structure

case class Wrappped[F[+x]](tpe: Type[? <: F[Any]], path: Path, underlying: Structure) extends Structure
case class Wrapped[F[+x]](tpe: Type[? <: F[Any]], wrapper: WrapperType[F], path: Path, underlying: Structure) extends Structure

case class Lazy private (tpe: Type[?], path: Path, private val deferredStruct: () => Structure) extends Structure {
lazy val struct: Structure = deferredStruct()
Expand Down Expand Up @@ -95,10 +100,11 @@ private[ducktape] object Structure {
case tpe @ '[Nothing] =>
Structure.Ordinary(tpe, path)

case WrapperType(wrapper: WrapperType.Wrapped[f], '[underlying]) =>
@unused given Type[f] = wrapper.wrapperTpe
Structure.Wrappped(
case WrapperType(wrapper: WrapperType[f], '[underlying]) =>
@unused given Type[f] = wrapper.wrapper
Structure.Wrapped(
Type.of[f[underlying]],
wrapper,
path,
Structure.of[underlying](path.appended(Path.Segment.Element(Type.of[underlying])))
)
Expand All @@ -117,7 +123,7 @@ private[ducktape] object Structure {

case tpe @ '[Any *: scala.Tuple] if !tpe.repr.isTupleN => // let plain tuples be caught later on
val elements =
tupleTypeElements(tpe.repr.dealias).zipWithIndex.map { (tpe, idx) =>
tupleTypeElements(tpe).zipWithIndex.map { (tpe, idx) =>
tpe.asType match {
case '[tpe] => Lazy.of[tpe](path.appended(Path.Segment.TupleElement(Type.of[tpe], idx)))
}
Expand Down Expand Up @@ -155,7 +161,7 @@ private[ducktape] object Structure {
}
} if tpe.repr.isTupleN =>
val structures =
tupleTypeElements(TypeRepr.of[types]).zipWithIndex
tupleTypeElements(Type.of[types]).zipWithIndex
.map((tpe, idx) =>
tpe.asType match {
case '[tpe] => Lazy.of[tpe](path.appended(Path.Segment.TupleElement(Type.of[tpe], idx)))
Expand All @@ -172,7 +178,7 @@ private[ducktape] object Structure {
}
} =>
val structures =
tupleTypeElements(TypeRepr.of[types])
tupleTypeElements(Type.of[types])
.zip(constStringTuple(TypeRepr.of[labels]))
.map((tpe, name) =>
name -> (tpe.asType match {
Expand All @@ -189,7 +195,7 @@ private[ducktape] object Structure {
}
} =>
val structures =
tupleTypeElements(TypeRepr.of[types])
tupleTypeElements(Type.of[types])
.zip(constStringTuple(TypeRepr.of[labels]))
.map((tpe, name) =>
name -> (tpe.asType match { case '[tpe] => Lazy.of[tpe](path.appended(Path.Segment.Case(Type.of[tpe]))) })
Expand All @@ -209,21 +215,27 @@ private[ducktape] object Structure {

private def constantString[Const <: String: Type](using Quotes) = Type.valueOfConstant[Const].get

private def tupleTypeElements(using Quotes)(tp: quotes.reflect.TypeRepr): List[quotes.reflect.TypeRepr] = {
import quotes.reflect.*
private def tupleTypeElements(tpe: Type[?])(using Quotes): List[quotes.reflect.TypeRepr] = {
@tailrec def loop(using Quotes)(curr: Type[?], acc: List[quotes.reflect.TypeRepr]): List[quotes.reflect.TypeRepr] = {
import quotes.reflect.*

@tailrec def loop(curr: TypeRepr, acc: List[TypeRepr]): List[TypeRepr] =
curr match {
case AppliedType(pairTpe, head :: tail :: Nil) =>
loop(tail, head :: acc)
case _ =>
case '[head *: tail] =>
loop(Type.of[tail], TypeRepr.of[head] :: acc)
case '[EmptyTuple] =>
acc
case other =>
report.errorAndAbort(
s"Unexpected type (${other.repr.show}) encountered when extracting tuple type elems. This is a bug in ducktape."
)
}
loop(tp, Nil).reverse
}

loop(tpe, Nil).reverse
}

private def constStringTuple(using Quotes)(tp: quotes.reflect.TypeRepr): List[String] = {
import quotes.reflect.*
tupleTypeElements(tp).map { case ConstantType(StringConstant(l)) => l }
tupleTypeElements(tp.asType).map { case ConstantType(StringConstant(l)) => l }
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,58 @@
package io.github.arainko.ducktape.internal

import io.github.arainko.ducktape.internal.Debug.AST

import scala.annotation.unused
import scala.quoted.*

private[ducktape] sealed trait WrapperType {
def unapply(tpe: Type[?])(using Quotes): Option[(WrapperType, Type[?])]
private[ducktape] sealed trait WrapperType[F[+x]] {
def wrapper(using Quotes): Type[F]

def unapply(tpe: Type[?])(using Quotes): Option[(WrapperType[F], Type[?])]
}

private[ducktape] object WrapperType {
def create[F[+x]: Type](using Quotes): WrapperType[F] = {
import quotes.reflect.*

Type.of[F[Any]] match {
case '[Option[a]] =>
Optional.asInstanceOf[WrapperType[F]]
case other =>
Wrapped(Type.of[F])
}
}

given Debug[WrapperType[?]] with {
def astify(self: WrapperType[?])(using Quotes): AST =
import quotes.reflect.*
self match
case Optional => Debug.AST.Text(s"WrapperType[Option]")
case Wrapped(wrapperTpe) => Debug.AST.Text(s"WrapperType[${wrapperTpe.repr.show(using Printer.TypeReprShortCode)}]")
}

def unapply(using Quotes, Context)(tpe: Type[?]) =
Context.current match {
case ctx: Context.PossiblyFallible[?] => ctx.wrapperType.unapply(tpe)
case Context.Total(_) => None
}

case object Absent extends WrapperType {
override def unapply(tpe: Type[? <: AnyKind])(using Quotes): Option[(WrapperType, Type[?])] = None
case object Optional extends WrapperType[Option] {

def wrapper(using Quotes): Type[Option] = Type.of[Option]

override def unapply(tpe: Type[? <: AnyKind])(using Quotes): Option[(WrapperType[Option], Type[?])] = {
tpe match {
case '[Option[underlying]] => Some(this -> Type.of[underlying])
case _ => None
}
}
}

final case class Wrapped[F[+x]](wrapperTpe: Type[F]) extends WrapperType {
override def unapply(tpe: Type[? <: AnyKind])(using Quotes): Option[(WrapperType, Type[?])] = {
final case class Wrapped[F[+x]] private[WrapperType] (wrapperTpe: Type[F]) extends WrapperType[F] {
def wrapper(using Quotes): Type[F] = wrapperTpe

override def unapply(tpe: Type[? <: AnyKind])(using Quotes): Option[(WrapperType[F], Type[?])] = {
@unused given Type[F] = wrapperTpe
tpe match
case '[F[underlying]] => Some(this -> Type.of[underlying])
Expand Down
Loading