plokhotnyuk / jsoniter-scala

Scala macros for compile-time generation of safe and ultra-fast JSON codecs + circe booster
MIT License
745 stars 99 forks source link

Better derivation error when the constructor is private #1198

Open MateuszKubuszok opened 1 month ago

MateuszKubuszok commented 1 month ago

I was testing the behavior of Circe and Jsoniter by deriving the codecs for Out case class defined like this:

class NewType private (val value: Int)
object NewType {
  def unsafe(value: Int): NewType = new NewType(value)
}
final case class In1(int: NewType)
final case class In2(i11: In1, i12: In1)
final case class In3(i21: In2, i22: In2, i23: In2)
final case class In4(i31: In3, i32: In3, i33: In3, i34: In3)
final case class In5(i41: In4, i42: In4, i43: In4, i44: In4, i45: In4)
final case class Out(i1:  In5, i2:  In5, i3:  In5, i4:  In5, i5:  In5, i6:  In5)

I wanted to compare error messages and how good they are.

I noticed that Jsoniter simply throws exception in the macro:

Scala 2

[error] /Users/dev/Workspaces/GitHub/derivation-benchmarks/jsoniter-scala-semi/src/main/scala/example/JsoniterScalaSemi.scala:10:24: constructor NewType in class NewType cannot be accessed in <$anon: com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec[example.model1.Out]> from <$anon: com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec[example.model1.Out]>
[error]     JsonCodecMaker.make(CodecMakerConfig.withAllowRecursiveTypes(true))
[error]                        ^
[error] one error found

Scala 3

[error] -- [E173] Reference Error: /Users/dev/Workspaces/GitHub/derivation-benchmarks/jsoniter-scala-semi/src/main/scala/example/JsoniterScalaSemi.scala:10:23
[error] 10 |    JsonCodecMaker.make(CodecMakerConfig.withAllowRecursiveTypes(true))
[error]    |    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[error]    |constructor NewType cannot be accessed as a member of example.model1.NewType from module class JsoniterScalaSemi$.
[error] one error found

I believe that the error message could be improved.

plokhotnyuk commented 1 month ago

@MateuszKubuszok Thanks for your feedback and using of jsoniter-scala in your presentation!

What your expectations for better error messages in this case?

MateuszKubuszok commented 1 month ago

In my presentation I want to show (among others) that a recursive derivation inside a macro:

when comparing with automatic and even semi-automatic derivation (based on Shapeless/Mirrors/Magnolia). So something better than exception, even "cannot automatically handle the type NewType, please provide implicit JsonValueCodec[NewType]" or something similar would be good.

plokhotnyuk commented 1 month ago

So something better than exception, even "cannot automatically handle the type NewType, please provide implicit JsonValueCodec[NewType]" or something similar would be good.

I don't know how detect that error at time of macro generation.

I mean that usage can happen externally (and fail) or internally in the case object (and compile successfully), so just detection of the private flag in the primary constructor attribute will not help:

class NewType private (val value: Int)
object NewType {
  def unsafe(value: Int): NewType = new NewType(value)
  implicit val codec: JsonValueCodec[NewType] = JsonCodecMaker.make
}
MateuszKubuszok commented 1 month ago

The easy way would be to assume that OOTB macros would:

For Scala 2, there is isPublic method, for Scala 3 (in my macros) I was checking for absence of Flags.Private, Flags.Protected and emptiness of sym.privateWithin and sym.protectedWithin.

The hard way would use Scala 2's:

as well as Scala 3's corresponding flags and methods and detect if constructor is visoble or not.