Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make-macro behaves unexpectedly with jsoniter-scala #1206

Open
domdorn opened this issue Sep 27, 2023 · 2 comments
Open

make-macro behaves unexpectedly with jsoniter-scala #1206

domdorn opened this issue Sep 27, 2023 · 2 comments

Comments

@domdorn
Copy link
Contributor

domdorn commented Sep 27, 2023

    object SolutionType extends Subtype[Int] {
      override def assertion =
        assert(Assertion.greaterThanOrEqualTo(0) && lessThanOrEqualTo(2))

    }

not working at runtime:

    implicit val solutionTypeCodec: JsonValueCodec[SolutionType.Type] = new JsonValueCodec[CPLEX.SolutionType.Type] {
      override def decodeValue(in: JsonReader, default: CPLEX.SolutionType.Type): CPLEX.SolutionType.Type =
        {
          SolutionType.make(in.readInt()) match {
            case ZValidation.Failure(log, errors) => in.decodeError(errors.mkString(","))
            case ZValidation.Success(log, value)  => value: CPLEX.SolutionType.Type
          }
        }

      override def encodeValue(x: CPLEX.SolutionType.Type, out: JsonWriter): Unit =
        out.writeVal(SolutionType.unwrap(x))

      override def nullValue: CPLEX.SolutionType.Type = null.asInstanceOf[CPLEX.SolutionType.Type]
    }

also not working at runtime (type hint):

    implicit val solutionTypeCodec: JsonValueCodec[SolutionType.Type] = new JsonValueCodec[CPLEX.SolutionType.Type] {
      override def decodeValue(in: JsonReader, default: CPLEX.SolutionType.Type): CPLEX.SolutionType.Type =
        {
          SolutionType.make(in.readInt(): Int) match {
            case ZValidation.Failure(log, errors) => in.decodeError(errors.mkString(","))
            case ZValidation.Success(log, value)  => value: CPLEX.SolutionType.Type
          }
        }

      override def encodeValue(x: CPLEX.SolutionType.Type, out: JsonWriter): Unit =
        out.writeVal(SolutionType.unwrap(x))

      override def nullValue: CPLEX.SolutionType.Type = null.asInstanceOf[CPLEX.SolutionType.Type]
    }

Working at runtime (explicit variable declaration before macro call:

    implicit val solutionTypeCodec: JsonValueCodec[SolutionType.Type] = new JsonValueCodec[CPLEX.SolutionType.Type] {
      override def decodeValue(in: JsonReader, default: CPLEX.SolutionType.Type): CPLEX.SolutionType.Type =
        {
          val i = in.readInt()
          SolutionType.make(i) match {
            case ZValidation.Failure(log, errors) => in.decodeError(errors.mkString(","))
            case ZValidation.Success(log, value)  => value: CPLEX.SolutionType.Type
          }
        }

      override def encodeValue(x: CPLEX.SolutionType.Type, out: JsonWriter): Unit =
        out.writeVal(SolutionType.unwrap(x))

      override def nullValue: CPLEX.SolutionType.Type = null.asInstanceOf[CPLEX.SolutionType.Type]
    }

Testcase:

object SolverConfigJsonSpec extends ZIOSpecDefault {

  import com.github.plokhotnyuk.jsoniter_scala.core._
  override val spec = suite("SolverConfigJsonSpec")(
    suite("CPLEXOptions")(
      suite("SolutionType")(
        test("should correctly serialize") {
          case class SolutionTypeTest(in: SolutionType.Type)
          implicit val codec: JsonValueCodec[SolutionTypeTest] = JsonCodecMaker.make[SolutionTypeTest]

          val obj = SolutionTypeTest(SolutionType(2))
          val actualJson = writeToString(obj)
          println(actualJson)
          val readObject = readFromString[SolutionTypeTest](actualJson)

          assertTrue(obj == readObject)
        }
      )))
     }
     ```
      
@domdorn
Copy link
Contributor Author

domdorn commented Sep 27, 2023

//cc @plokhotnyuk do you see something obvious here? in.readInt() is typed Int, so the macro call SolutionType.make should already know that its an Int ?

If I don't do the allocation (e.g. the first or second codec), I'll get

    + SolutionType
      - should correctly serialize
        Exception in thread "zio-fiber-162" com.github.plokhotnyuk.jsoniter_scala.core.JsonReaderException: illegal number, offset: 0x00000007, buf:
        +----------+-------------------------------------------------+------------------+
        |          |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f | 0123456789abcdef |
        +----------+-------------------------------------------------+------------------+
        | 00000000 | 7b 22 69 6e 22 3a 32 7d                         | {"in":2}         |
        +----------+-------------------------------------------------+------------------+
        	at xxx.SolverConfigJsonSpec.spec(SolverConfigJsonSpec.scala:54)

@domdorn
Copy link
Contributor Author

domdorn commented Sep 27, 2023

the make method is defined as this:

  def make(value: A): Validation[String, Type] = macro zio.prelude.Macros.make_impl[A, Type]

which continues like this:

  def make_impl[A, T](value: c.Expr[A]): c.Tree = {
    val expr = value

    val result = q"_root_.zio.prelude.Newtype.unsafeWrap(${c.prefix})($expr)"

    q"""
_root_.zio.prelude.Validation.fromEitherNonEmptyChunk {
  ${c.prefix}.assertion.run($value)
    .left.map(e => _root_.zio.NonEmptyChunk.fromCons(e.toNel($value.toString)))
}.as($result)
"""

  }

is it possible that the expression in.readInt() is passed as a whole and thus evaluated multiple times instead of being eagerly evaluated?

@domdorn domdorn changed the title make-macro fails with jsoniter-scala make-macro behaves unexpectedly with jsoniter-scala Sep 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant