Skip to content

Commit

Permalink
make Wrapped(WrapperType.Optional) isomorphic to Optional
Browse files Browse the repository at this point in the history
  • Loading branch information
arainko committed Jul 28, 2024
1 parent dc8ef54 commit ff6b08c
Show file tree
Hide file tree
Showing 10 changed files with 177 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.github.arainko.ducktape.internal

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

private[ducktape] object ConfigInstructionRefiner {

Expand All @@ -14,17 +14,3 @@ private[ducktape] object ConfigInstructionRefiner {
case inst: (Instruction.Dynamic | Instruction.Bulk | Instruction.Regional | Instruction.Failed) => inst

}

object Common {
given Transformer.Fallible[Option, Int, String] = a => Some(a.toString)
}

case class Costam1(int: Int)
case class Costam2(int: Option[String])

object test {
import Common.given
Mode.FailFast.option.locally {
Costam1(1).fallibleTo[Costam2]
}
}
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 @@ -6,7 +6,7 @@ import scala.quoted.*
private[ducktape] object Logger {

// Logger Config
private[ducktape] transparent inline given level: Level = Level.Debug
private[ducktape] transparent inline given level: Level = Level.Off
private val output = Output.StdOut
private def filter(msg: String, loc: String) = true
enum Level {
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,20 +100,21 @@ private[ducktape] object Structure {
case tpe @ '[Nothing] =>
Structure.Ordinary(tpe, path)

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])))
)

case tpe @ '[Option[param]] =>
Structure.Optional(tpe, path, Structure.of[param](path.appended(Path.Segment.Element(Type.of[param]))))

case tpe @ '[Iterable[param]] =>
Structure.Collection(tpe, path, Structure.of[param](path.appended(Path.Segment.Element(Type.of[param]))))

case WrapperType(wrapper: WrapperType.Wrapped[f], '[underlying]) =>
@unused given Type[f] = wrapper.wrapperTpe
Structure.Wrappped(
Type.of[f[underlying]],
path,
Structure.of[underlying](path.appended(Path.Segment.Element(Type.of[underlying])))
)

case tpe @ '[AnyVal] if tpe.repr.typeSymbol.flags.is(Flags.Case) =>
val repr = tpe.repr
val param = repr.typeSymbol.caseFields.head
Expand Down Expand Up @@ -216,10 +222,12 @@ private[ducktape] object Structure {
curr match {
case '[head *: tail] =>
loop(Type.of[tail], TypeRepr.of[head] :: acc)
case '[EmptyTuple] =>
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.")
report.errorAndAbort(
s"Unexpected type (${other.repr.show}) encountered when extracting tuple type elems. This is a bug in ducktape."
)
}
}

Expand Down
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,93 @@ package io.github.arainko.ducktape.issues
import io.github.arainko.ducktape.*

class Issue187Suite extends DucktapeSuite {
test("minimization works") {
test("BetweenNonOptionOption works when Mode[Option] is in scope") {

given Transformer.Fallible[Option, Int, String] = a => Some(a.toString)

case class Source(int: Int)
case class Source(int: Int, str: String)
case class Dest(int: Option[String], str: Option[String])

Mode.FailFast.option.locally {
val source = Source(1, "str")
val expected = Dest(Some("1"), Some("str"))

assertTransformsFallible(
source,
Some(expected)
)
assertEachEquals(
source.fallibleVia(Dest.apply),
Transformer.defineVia[Source](Dest.apply).fallible.build().transform(source)
)(Some(expected))
}
}

test("BetweenOptions works when Mode[Option] is in scope") {
given Transformer.Fallible[Option, Int, String] = a => Some(a.toString)

case class Source(int: Option[Int])
case class Dest(int: Option[String])

Mode.FailFast.option.locally {
Source(1).fallibleTo[Dest]
val source = Source(Some(1))
val expected = Dest(Some("1"))

assertTransformsFallible(
source,
Some(expected)
)
assertEachEquals(
source.fallibleVia(Dest.apply),
Transformer.defineVia[Source](Dest.apply).fallible.build().transform(source)
)(Some(expected))

}
}

test("Fallible transformation for an Option works when Mode[Option] is in scope") {
given Transformer.Fallible[Option, Int, String] = a => Some(a.toString)

case class Source(int: Option[Int])
case class Dest(int: String)

Mode.FailFast.option.locally {
val source = Source(Some(1))
val expected = Dest("1")

assertTransformsFallible(
source,
Some(expected)
)
assertEachEquals(
source.fallibleVia(Dest.apply),
Transformer.defineVia[Source](Dest.apply).fallible.build().transform(source)
)(Some(expected))
}
}

test("Option-unwrapping works") {
case class Dest(int1: Int, int2: Int, int3: Int, int4: Int)

Mode.FailFast.option.locally {
val source =
(
Some(1),
Some(2),
Some(3),
Some(4)
)

val expected = Dest(1, 2, 3, 4)

assertTransformsFallible(
source,
Some(expected)
)
assertEachEquals(
source.fallibleVia(Dest.apply),
Transformer.defineVia[source.type](Dest.apply).fallible.build().transform(source)
)(Some(expected))
}
}
}
Loading

0 comments on commit ff6b08c

Please sign in to comment.