sirthias / borer

Efficient CBOR and JSON (de)serialization in Scala
https://sirthias.github.io/borer/
Mozilla Public License 2.0
220 stars 13 forks source link

Exception when compiling with Scala 3 #585

Open OndrejSpanel opened 2 years ago

OndrejSpanel commented 2 years ago

I have a quite large closed source project which I am gradually migrating to Scala 3. I get exception when compiling with Scala 3 with some of my decoders. The exception is:

Exception occurred while executing macro expansion.
java.lang.ClassCastException: class dotty.tools.dotc.ast.Trees$This cannot be cast to class dotty.tools.dotc.ast.Trees$RefTree (dotty.tools.dotc.ast.Trees$This and dotty.tools.dotc.ast.Trees$RefTree are in unnamed module of loader java.net.URLClassLoader @4c15b562)

    implicit val decoder: Decoder[V3] = deriveDecoder

I might be able to create a short repro later, but I am not sure if / when, as Scala 3 is not a priority task for me yet.

sirthias commented 2 years ago

Ok, thank you for this report, @OndrejSpanel. Can you show the V3 type, so that we can maybe construct a failing test?

OndrejSpanel commented 2 years ago

Sure. Actually it is quite simple:

  object V3 {
    implicit val encoder: Encoder[V3] = deriveEncoder
    implicit val decoder: Decoder[V3] = deriveDecoder
  }
  case class V3(x: Double, y: Double, z: Double)

Perhaps you may be interested in what follows, which is:

  implicit val vector3fEncoder: Encoder[Vector3f] = V3.encoder.contramap(v => V3(v.x, v.y, v.z))
  implicit val vector3fDecoder: Decoder[Vector3f] = V3.decoder.map(v => Vector3f(v.x, v.y, v.z))

Vector3f is an external class defined in a Scala library I have no control over.

All of this is defined inside of a trait, which is then derived to create an object used to access the types.

sirthias commented 2 years ago

Ok, thank you. I'll look at this ASAP.

OndrejSpanel commented 2 years ago

defined inside of a trait

This seems to be causing the issue. A small repro is:

import io.bullet.borer._
import io.bullet.borer.derivation.MapBasedCodecs._

trait Types {
  object V3 {
    implicit val encoder: Encoder[V3] = deriveEncoder
    implicit val decoder: Decoder[V3] = deriveDecoder
  }
  case class V3(x: Double, y: Double, z: Double)
}

See also https://scastie.scala-lang.org/SWN4jtpOQf6LxwTuAmZf9g

Exception occurred while executing macro expansion.
java.lang.ClassCastException: class dotty.tools.dotc.ast.Trees$This cannot be cast to class dotty.tools.dotc.ast.Trees$RefTree
sirthias commented 2 years ago

Ah, ok, thank you. Constructs such as these often create problems, because of the extra references to the outer context object that case class (and the V3 objects) carry. I'll look into it...

OndrejSpanel commented 2 years ago

While it would be probably good to have it fixed, knowing the cause (outer class reference) is enough for me to restructure the code so that it no longer shows the issue.

sirthias commented 1 year ago

Just looked at this a bit. It looks like a compiler problem since borer derivation doesn't manually manipulate trees anywhere. Although admittedly a bit complex, the macros stay completely within the "safe space" of quotations and splices.

One thing I noticed: When you move the derivations out of the V3 object underneath the Types trait things compile normally. It looks like this object creates some trouble somewhere.

However, if I move the derivation code out of the trait completely like this:

    trait Types {
      case class V3(x: Double, y: Double, z: Double)
    }
    object Types extends Types

    implicit val encoder: Encoder[Types.V3] = MapBasedCodecs.deriveEncoder
    implicit val decoder: Decoder[Types.V3] = MapBasedCodecs.deriveDecoder

I get another problem:

Exception occurred while executing macro expansion.
java.lang.Exception: Expr cast exception: Types.this.V3.apply(x, `x₂`, `x₃`)
of type: Types.this.V3
did not conform to type: Types.V3

So, wrapper traits definitely cause pain at this point, which is why it's probably better to avoid them altogether.