Skip to content

Commit

Permalink
fine tuning either and option
Browse files Browse the repository at this point in the history
  • Loading branch information
Greg Zoller committed Mar 31, 2024
1 parent db8a60d commit 3b9a37f
Show file tree
Hide file tree
Showing 14 changed files with 263 additions and 215 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ lazy val root = project
Test / parallelExecution := false,
scalafmtOnCompile := !isCI,
libraryDependencies ++= Seq(
"co.blocke" %% "scala-reflection" % "2.0.3",
"co.blocke" %% "scala-reflection" % "fixOptionUnit_ea63ce", //"2.0.3",
"org.apache.commons" % "commons-text" % "1.11.0",
"io.github.kitlangton" %% "neotype" % "0.0.9",
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core" % "2.24.5-SNAPSHOT",
Expand Down
93 changes: 55 additions & 38 deletions src/main/scala/co.blocke.scalajack/json/JsonCodecMaker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import reading.JsonSource
import scala.jdk.CollectionConverters.*
import scala.quoted.*
import scala.reflect.ClassTag
import scala.annotation.switch
import scala.annotation.{switch, tailrec}
import scala.collection.Factory
import scala.util.{Failure, Success, Try}
import dotty.tools.dotc.ast.Trees.EmptyTree
Expand Down Expand Up @@ -176,7 +176,6 @@ object JsonCodecMaker:
$prefix
$out.burpNull()
}
case TryPolicy.NO_WRITE => '{ () }
case TryPolicy.ERR_MSG_STRING =>
'{
$prefix
Expand All @@ -193,13 +192,6 @@ object JsonCodecMaker:
t.rightRef.refType match
case '[rt] =>
cfg.eitherLeftHandling match
case EitherLeftPolicy.NO_WRITE =>
'{
if $tin == null then $out.burpNull()
$tin match
case Left(_) => ()
case Right(v) => ${ _maybeWrite[rt](prefix, '{ v.asInstanceOf[rt] }, t.rightRef.asInstanceOf[RTypeRef[rt]], out, cfg) }
}
case EitherLeftPolicy.AS_NULL =>
'{
if $tin == null then $out.burpNull()
Expand Down Expand Up @@ -231,10 +223,12 @@ object JsonCodecMaker:
'{
if $tin == null then $out.burpNull()
$tin match
case Left(v) => ${ _maybeWrite[lt](prefix, '{ v.asInstanceOf[lt] }, t.leftRef.asInstanceOf[RTypeRef[lt]], out, cfg) }
case Right(v) => ${ _maybeWrite[rt](prefix, '{ v.asInstanceOf[rt] }, t.rightRef.asInstanceOf[RTypeRef[rt]], out, cfg) }
case Left(v) =>
${ _maybeWrite[lt](prefix, '{ v.asInstanceOf[lt] }, t.leftRef.asInstanceOf[RTypeRef[lt]], out, cfg) }
case Right(v) =>
${ _maybeWrite[rt](prefix, '{ v.asInstanceOf[rt] }, t.rightRef.asInstanceOf[RTypeRef[rt]], out, cfg) }
}
case t: LeftRightRef[?] if !cfg.noneAsNull && t.lrkind != LRKind.EITHER && (t.leftRef.isInstanceOf[OptionRef[_]] || t.rightRef.isInstanceOf[OptionRef[_]]) =>
case t: LeftRightRef[?] if t.lrkind != LRKind.EITHER =>
t.refType match
case '[e] =>
t.rightRef.refType match
Expand All @@ -243,14 +237,14 @@ object JsonCodecMaker:
case '[lt] =>
val tin = aE.asExprOf[e]
'{
if $tin == None then ()
if $tin == null then $out.burpNull()
else
$out.mark()
scala.util.Try {
${ _maybeWrite[rt](prefix, '{ $tin.asInstanceOf[rt] }, t.rightRef.asInstanceOf[RTypeRef[rt]], out, cfg) }
} match
case scala.util.Success(_) => ()
case scala.util.Failure(_) =>
case scala.util.Failure(f) =>
$out.revert()
${ _maybeWrite[lt](prefix, '{ $tin.asInstanceOf[lt] }, t.leftRef.asInstanceOf[RTypeRef[lt]], out, cfg) }
}
Expand Down Expand Up @@ -616,15 +610,6 @@ object JsonCodecMaker:
case Right(v) =>
${ genWriteVal[rt]('{ v.asInstanceOf[rt] }, t.rightRef.asInstanceOf[RTypeRef[rt]], out, inTuple = inTuple) }
}
case EitherLeftPolicy.NO_WRITE =>
'{
if $tin == null then $out.burpNull()
else
$tin match
case Left(v) => ()
case Right(v) =>
${ genWriteVal[rt]('{ v.asInstanceOf[rt] }, t.rightRef.asInstanceOf[RTypeRef[rt]], out, inTuple = inTuple) }
}
case EitherLeftPolicy.ERR_MSG_STRING =>
'{
if $tin == null then $out.burpNull()
Expand Down Expand Up @@ -671,7 +656,6 @@ object JsonCodecMaker:
cfg.tryFailureHandling match
case _ if inTuple => '{ $out.burpNull() }
case TryPolicy.AS_NULL => '{ $out.burpNull() }
case TryPolicy.NO_WRITE => '{ () }
case TryPolicy.ERR_MSG_STRING => '{ $out.value("Try Failure with msg: " + v.getMessage()) }
case TryPolicy.THROW_EXCEPTION => '{ throw v }
}
Expand Down Expand Up @@ -860,6 +844,16 @@ object JsonCodecMaker:

// ---------------------------------------------------------------------------------------------

def lrHasOptionChild(lr: LeftRightRef[?]): String =
lr.rightRef match
case t: OptionRef[?] => "r"
case t: LeftRightRef[?] => "r" + lrHasOptionChild(t)
case _ =>
lr.leftRef match
case t: OptionRef[?] => "l"
case t: LeftRightRef[?] => "l" + lrHasOptionChild(t)
case _ => ""

def genDecFnBody[T: Type](r: RTypeRef[?], in: Expr[JsonSource])(using Quotes): Expr[Unit] =
import quotes.reflect.*

Expand Down Expand Up @@ -905,11 +899,31 @@ object JsonCodecMaker:
// if dvMembers.isEmpty then (ValDef(sym, Some(oneField.fieldRef.unitVal.asTerm)), caseDef, fieldSymRef)
if dvMembers.isEmpty then
// no default... required? Not if Option/Optional, or a collection
oneField.fieldRef match {
case _: OptionRef[?] => // not required
case _ => required = required | math.pow(2, oneField.index).toInt // required
val unitVal = oneField.fieldRef match {
case _: OptionRef[?] =>
oneField.fieldRef.unitVal.asTerm // not required
case r: LeftRightRef[?] if r.lrkind == LRKind.EITHER => // maybe required
val optionRecipe = lrHasOptionChild(r)
if optionRecipe.length == 0 then
required = required | math.pow(2, oneField.index).toInt // required
oneField.fieldRef.unitVal.asTerm
else
val recipeE = Expr(optionRecipe)
'{
$recipeE.foldRight(None: Any)((c, acc) => if c == 'r' then Right(acc) else Left(acc)).asInstanceOf[f]
}.asTerm
case r: LeftRightRef[?] => // maybe required
val optionRecipe = lrHasOptionChild(r)
if optionRecipe.length == 0 then // no Option children -> required
required = required | math.pow(2, oneField.index).toInt // required
oneField.fieldRef.unitVal.asTerm
else // at least one Option child -> optional
'{ None }.asTerm
case _ =>
required = required | math.pow(2, oneField.index).toInt // required
oneField.fieldRef.unitVal.asTerm
}
(ValDef(sym, Some(oneField.fieldRef.unitVal.asTerm)), caseDef, fieldSymRef)
(ValDef(sym, Some(unitVal)), caseDef, fieldSymRef)
else
val methodSymbol = dvMembers.head
val dvSelectNoTArgs = Ref(companionModule).select(methodSymbol)
Expand Down Expand Up @@ -1010,14 +1024,14 @@ object JsonCodecMaker:
'{
$in.expectString() match
case null =>
$in.retract()
$in.retract()
$in.retract()
$in.retract()
$in.backspace()
$in.backspace()
$in.backspace()
$in.backspace()
throw JsonParseError("Char value cannot be null", $in)
case "" =>
$in.retract()
$in.retract()
$in.backspace()
$in.backspace()
throw JsonParseError("Char value expected but empty string found in json", $in)
case c => c.charAt(0)
}.asExprOf[T]
Expand Down Expand Up @@ -1086,8 +1100,8 @@ object JsonCodecMaker:
val c = $in.expectString()
if c == null then null
else if c.length == 0 then
$in.retract()
$in.retract()
$in.backspace()
$in.backspace()
throw JsonParseError("Character value expected but empty string found in json", $in)
else java.lang.Character.valueOf(c.charAt(0))
}.asExprOf[T]
Expand Down Expand Up @@ -1257,10 +1271,11 @@ object JsonCodecMaker:
case Success(rval) =>
Right(rval)
case Failure(f) =>
$in.revertToMark()
scala.util.Try(${ genReadVal[l](t.leftRef.asInstanceOf[RTypeRef[l]], in, inTuple) }) match
case Success(lval) => Left(lval)
case Failure(_) =>
$in.retract()
$in.backspace()
throw JsonParseError("Failed to read either side of Either type", $in)
}.asExprOf[T]

Expand All @@ -1271,13 +1286,15 @@ object JsonCodecMaker:
t.rightRef.refType match
case '[r] =>
'{
$in.mark()
scala.util.Try(${ genReadVal[l](t.leftRef.asInstanceOf[RTypeRef[l]], in, true) }) match
case Success(lval) => lval
case Failure(f) =>
$in.revertToMark()
scala.util.Try(${ genReadVal[r](t.rightRef.asInstanceOf[RTypeRef[r]], in, true) }) match
case Success(rval) => rval
case Failure(_) =>
$in.retract()
$in.backspace()
throw JsonParseError("Failed to read either side of Union type", $in)
}.asExprOf[T]
/*
Expand Down
36 changes: 8 additions & 28 deletions src/main/scala/co.blocke.scalajack/json/JsonConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@ import scala.quoted.*

class JsonConfig private[scalajack] (
val noneAsNull: Boolean,
// val forbidNullsInInput: Boolean = false,
val tryFailureHandling: TryPolicy,
val eitherLeftHandling: EitherLeftPolicy,
// val undefinedFieldHandling: UndefinedValueOption = UndefinedValueOption.THROW_EXCEPTION,
// val _allowQuotedPrimitives: Boolean,
val writeNonConstructorFields: Boolean,
// --------------------------
val typeHintLabel: String,
Expand All @@ -31,7 +29,6 @@ class JsonConfig private[scalajack] (
def withEnumsAsIds(asIds: Option[List[String]]): JsonConfig = copy(enumsAsIds = asIds)
def suppressEscapedStrings(): JsonConfig = copy(_suppressEscapedStrings = true)
def suppressTypeHints(): JsonConfig = copy(_suppressTypeHints = true)
// def allowQuotedPrimitives(): JsonConfig = copy(_allowQuotedPrimitives = true)

private[this] def copy(
noneAsNull: Boolean = noneAsNull,
Expand All @@ -43,12 +40,10 @@ class JsonConfig private[scalajack] (
enumsAsIds: Option[List[String]] = enumsAsIds,
_suppressEscapedStrings: Boolean = _suppressEscapedStrings,
_suppressTypeHints: Boolean = _suppressTypeHints
// _allowQuotedPrimitives: Boolean = _allowQuotedPrimitives
): JsonConfig = new JsonConfig(
noneAsNull,
tryFailureHandling,
eitherLeftHandling,
// _allowQuotedPrimitives,
writeNonConstructorFields,
typeHintLabel,
typeHintPolicy,
Expand All @@ -58,10 +53,10 @@ class JsonConfig private[scalajack] (
)

enum TryPolicy:
case AS_NULL, NO_WRITE, ERR_MSG_STRING, THROW_EXCEPTION
case AS_NULL, ERR_MSG_STRING, THROW_EXCEPTION

enum EitherLeftPolicy:
case AS_VALUE, AS_NULL, NO_WRITE, ERR_MSG_STRING, THROW_EXCEPTION
case AS_VALUE, AS_NULL, ERR_MSG_STRING, THROW_EXCEPTION

// enum UndefinedValueOption:
// case AS_NULL, AS_SYMBOL, THROW_EXCEPTION
Expand All @@ -72,9 +67,8 @@ enum TypeHintPolicy:
object JsonConfig
extends JsonConfig(
noneAsNull = false,
tryFailureHandling = TryPolicy.NO_WRITE,
eitherLeftHandling = EitherLeftPolicy.NO_WRITE,
// _allowQuotedPrimitives = false writeNonConstructorFields = true,
tryFailureHandling = TryPolicy.AS_NULL,
eitherLeftHandling = EitherLeftPolicy.AS_VALUE,
writeNonConstructorFields = false,
typeHintLabel = "_hint",
typeHintPolicy = TypeHintPolicy.SIMPLE_CLASSNAME,
Expand All @@ -94,7 +88,6 @@ object JsonConfig
.withTypeHintLabel(${ Expr(x.typeHintLabel) })
.withTypeHintPolicy(${ Expr(x.typeHintPolicy) })
.withEnumsAsIds(${ Expr(x.enumsAsIds) })
// .allowQuotedPrimitives(${ Expr(x._allowQuotedPrimitives) })
val jc2 = ${
if x.noneAsNull then '{ jc.withNoneAsNull() }
else '{ jc }
Expand Down Expand Up @@ -124,12 +117,9 @@ object JsonConfig
case '{
JsonConfig(
$noneAsNullE,
// $forbitNullsInInputE,
$tryFailureHandlerE,
$eitherLeftHandlerE,
// $allowQuotedPrimitivesE,
// $undefinedFieldHandlingE,
// $permissivePrimitivesE,
$writeNonConstructorFieldsE,
$typeHintLabelE,
$typeHintPolicyE,
Expand All @@ -142,14 +132,9 @@ object JsonConfig
Some(
JsonConfig(
extract("noneAsNull", noneAsNullE),
// extract("forbitNullsInInput", forbitNullsInInputE),
extract("tryFailureHandler", tryFailureHandlerE),
extract("eitherLeftHandler", eitherLeftHandlerE),
// extract("_allowQuotedPrimitives", allowQuotedPrimtiviesE),
// extract("undefinedFieldHandling", undefinedFieldHandlingE),
// extract("permissivePrimitives", permissivePrimitivesE),
extract("writeNonConstructorFields", writeNonConstructorFieldsE),
// extract2[String]("typeHintLabel", x)
extract("typeHintLabel", typeHintLabelE),
extract("typeHintPolicy", typeHintPolicyE),
extract("enumsAsIds", enumsAsIdsE),
Expand All @@ -162,11 +147,10 @@ object JsonConfig
println("ERROR: " + x.getMessage)
None
}
case '{ JsonConfig } => Some(JsonConfig)
case '{ ($x: JsonConfig).withNoneAsNull() } => Some(x.valueOrAbort.withNoneAsNull())
case '{ ($x: JsonConfig).withTryFailureHandling($v) } => Some(x.valueOrAbort.withTryFailureHandling(v.valueOrAbort))
case '{ ($x: JsonConfig).withEitherLeftHandling($v) } => Some(x.valueOrAbort.withEitherLeftHandling(v.valueOrAbort))
// case '{ ($x: JsonConfig).allowQuotedPrimitives() } => Some(x.valueOrAbort.allowQuotedPrimities())
case '{ JsonConfig } => Some(JsonConfig)
case '{ ($x: JsonConfig).withNoneAsNull() } => Some(x.valueOrAbort.withNoneAsNull())
case '{ ($x: JsonConfig).withTryFailureHandling($v) } => Some(x.valueOrAbort.withTryFailureHandling(v.valueOrAbort))
case '{ ($x: JsonConfig).withEitherLeftHandling($v) } => Some(x.valueOrAbort.withEitherLeftHandling(v.valueOrAbort))
case '{ ($x: JsonConfig).withWriteNonConstructorFields($v) } => Some(x.valueOrAbort.withWriteNonConstructorFields(v.valueOrAbort))
case '{ ($x: JsonConfig).withTypeHintLabel($v) } => Some(x.valueOrAbort.withTypeHintLabel(v.valueOrAbort))
case '{ ($x: JsonConfig).withTypeHintPolicy($v) } => Some(x.valueOrAbort.withTypeHintPolicy(v.valueOrAbort))
Expand All @@ -180,7 +164,6 @@ object JsonConfig
x match
case TryPolicy.AS_NULL => '{ TryPolicy.AS_NULL }
case TryPolicy.ERR_MSG_STRING => '{ TryPolicy.ERR_MSG_STRING }
case TryPolicy.NO_WRITE => '{ TryPolicy.NO_WRITE }
case TryPolicy.THROW_EXCEPTION => '{ TryPolicy.THROW_EXCEPTION }
}

Expand All @@ -190,7 +173,6 @@ object JsonConfig
case EitherLeftPolicy.AS_VALUE => '{ EitherLeftPolicy.AS_VALUE }
case EitherLeftPolicy.AS_NULL => '{ EitherLeftPolicy.AS_NULL }
case EitherLeftPolicy.ERR_MSG_STRING => '{ EitherLeftPolicy.ERR_MSG_STRING }
case EitherLeftPolicy.NO_WRITE => '{ EitherLeftPolicy.NO_WRITE }
case EitherLeftPolicy.THROW_EXCEPTION => '{ EitherLeftPolicy.THROW_EXCEPTION }
}

Expand All @@ -207,7 +189,6 @@ object JsonConfig
import quotes.reflect.*
x match
case '{ TryPolicy.AS_NULL } => Some(TryPolicy.AS_NULL)
case '{ TryPolicy.NO_WRITE } => Some(TryPolicy.NO_WRITE)
case '{ TryPolicy.ERR_MSG_STRING } => Some(TryPolicy.ERR_MSG_STRING)
case '{ TryPolicy.THROW_EXCEPTION } => Some(TryPolicy.THROW_EXCEPTION)
}
Expand All @@ -218,7 +199,6 @@ object JsonConfig
x match
case '{ EitherLeftPolicy.AS_VALUE } => Some(EitherLeftPolicy.AS_VALUE)
case '{ EitherLeftPolicy.AS_NULL } => Some(EitherLeftPolicy.AS_NULL)
case '{ EitherLeftPolicy.NO_WRITE } => Some(EitherLeftPolicy.NO_WRITE)
case '{ EitherLeftPolicy.ERR_MSG_STRING } => Some(EitherLeftPolicy.ERR_MSG_STRING)
case '{ EitherLeftPolicy.THROW_EXCEPTION } => Some(EitherLeftPolicy.THROW_EXCEPTION)
}
Expand Down
Loading

0 comments on commit 3b9a37f

Please sign in to comment.