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

Better derivation error when the constructor is private #1198

Open
MateuszKubuszok opened this issue Sep 20, 2024 · 4 comments
Open

Better derivation error when the constructor is private #1198

MateuszKubuszok opened this issue Sep 20, 2024 · 4 comments

Comments

@MateuszKubuszok
Copy link
Contributor

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
Copy link
Owner

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

What your expectations for better error messages in this case?

@MateuszKubuszok
Copy link
Contributor Author

MateuszKubuszok commented Sep 20, 2024

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

  • compiles faster
  • with better and more actionable error messages that "implicit not found"
  • without a degradation in runtime performance (an increase in performance is nice to have)

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
Copy link
Owner

plokhotnyuk commented Sep 20, 2024

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
Copy link
Contributor Author

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

  • find the primaryConstructor
  • check if it is public (and only derive if constructor is always available)

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:

  • sym.isPublic: Boolean
  • sym.isProtected: Boolean
  • sym.isPrivate: Boolean
  • sym.isPrivateWithin: Symbol

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants