Skip to content

Flakiness in macro success #1279

Closed
Closed
@hughsimpson

Description

@hughsimpson

Using:
scala 2.13.16
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core" % "2.35.3"
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "2.35.3" % Provided
(and previous versions of jsoniter-scala going back quite some time -- I don't think this is in anyway a recently-introduced issue)

When compiling the code generated by the tapir openapi generator (example output), I will occasionally hit an error looking as follows:

  [error] /home/runner/work/myapp/target/scala-2.13/src_managed/main/sbt-openapi-codegen/TapirGeneratedEndpointsJsonSerdes.scala:12:186: Cannot evaluate a parameter of the 'make' macro call for type 'sttp.tapir.generated.myapp.TapirGeneratedEndpoints.Payload'. It should not depend on code from the same compilation module where the 'make' macro is called. Use a separated submodule of the project to compile all such dependencies before their usage for generation of codecs. Cause:
  [error] java.lang.AssertionError: assertion failed: com package com <none>
  [error]   implicit lazy val payloadJsonCodec: com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec[Payload] = com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker.make(com.github.plokhotnyuk.jsoniter_scala.macros.CodecMakerConfig.withAllowRecursiveTypes(true).withTransientEmpty(false).withTransientDefault(false).withRequireCollectionFields(true))

I have seen this happen on a relatively minimal file:

  implicit def seqCodec[T: com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec]: com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec[List[T]] =
    com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker.make[List[T]]
  implicit def optionCodec[T: com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec]: com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec[Option[T]] =
    com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker.make[Option[T]]

  implicit lazy val payloadJsonCodec: com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec[Payload] = com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker.make(com.github.plokhotnyuk.jsoniter_scala.macros.CodecMakerConfig.withAllowRecursiveTypes(true).withTransientEmpty(false).withTransientDefault(false).withRequireCollectionFields(true))

  implicit lazy val otherModelJsonCodec: com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec[OtherModel] = com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker.make(com.github.plokhotnyuk.jsoniter_scala.macros.CodecMakerConfig.withAllowRecursiveTypes(true).withTransientEmpty(false).withTransientDefault(false).withRequireCollectionFields(true))
  implicit lazy val innerBodySchemaJsonCodec: com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec[InnerBodySchema] = com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker.make(com.github.plokhotnyuk.jsoniter_scala.macros.CodecMakerConfig.withAllowRecursiveTypes(true).withTransientEmpty(false).withTransientDefault(false).withRequireCollectionFields(true))

  implicit lazy val anotherModelJsonCodec: com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec[AnotherModel] = com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker.make(com.github.plokhotnyuk.jsoniter_scala.macros.CodecMakerConfig.withAllowRecursiveTypes(true).withTransientEmpty(false).withTransientDefault(false).withRequireCollectionFields(true).withDiscriminatorFieldName(scala.None).withDiscriminatorFieldName(scala.None))

  implicit lazy val anotherModel2JsonCodec: com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec[AnotherModel2] = com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker.make(com.github.plokhotnyuk.jsoniter_scala.macros.CodecMakerConfig.withAllowRecursiveTypes(true).withTransientEmpty(false).withTransientDefault(false).withRequireCollectionFields(true).withDiscriminatorFieldName(scala.None).withDiscriminatorFieldName(scala.None))
  implicit lazy val innerBodyCodec: com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec[InnerBody] = new com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec[InnerBody] {
    def decodeValue(in: com.github.plokhotnyuk.jsoniter_scala.core.JsonReader, default: InnerBody): InnerBody = {
      List(
        innerBodySchemaJsonCodec)
        .foldLeft(Option.empty[InnerBody]) {
          case (Some(v), _) => Some(v)
          case (None, next) =>
            in.setMark()
            scala.util.Try(next.asInstanceOf[com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec[InnerBody]].decodeValue(in, default))
              .fold(_ => { in.rollbackToMark(); None }, Some(_))
        }.getOrElse(throw new RuntimeException("Unable to decode json to untagged ADT type InnerBody"))
    }
    def encodeValue(x: InnerBody, out: com.github.plokhotnyuk.jsoniter_scala.core.JsonWriter): Unit = x match {
      case x: InnerBodySchema => InnerBodySchemaJsonCodec.encodeValue(x, out)
    }

    def nullValue: InnerBody = InnerBodySchemaJsonCodec.nullValue
  }
  implicit lazy val messageJsonCodec: com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec[Message] = com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker.make(com.github.plokhotnyuk.jsoniter_scala.macros.CodecMakerConfig.withAllowRecursiveTypes(true).withTransientEmpty(false).withTransientDefault(false).withRequireCollectionFields(true))

The relevant models seem to be

  case class Payload (
    body: InnerBody,
    some: String,
    other: String,
    params: Seq[String]
  )

  sealed trait InnerBody

  case class InnerBodySchema (
    has: String,
    some: Int,
    more: Seq[DateTime],
    params: String 
  ) extends InnerBody

although I haven't been able to produce a minimal version that definitely triggers this yet, I'm afraid - in part because it happens so rarely. I can't even get the failure locally on the exact file that's caused it before. It seems to happen more often when building in CI docker for some mysterious reason 😅

Anyway I'm not sure if this is really a jsoniter-scala bug or a scala bug or what, but thought I'd raise it here in case @plokhotnyuk had any ideas.

Perhaps there might be some workaround we could do in the tapir codegen to ameliorate this flakiness?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions