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

ClassCastException thrown on Scala 3 when transforming sealed traits to Protobuf GeneratedEnum #479

Closed
8 of 10 tasks
saeltz opened this issue Mar 14, 2024 · 15 comments · Fixed by #603
Closed
8 of 10 tasks
Labels
bug Erroneous behavior in existing features

Comments

@saeltz
Copy link
Contributor

saeltz commented Mar 14, 2024

Checklist

Describe the bug

A ClassCastException is thrown when trying to transform a sealed trait into a Protobuf GeneratedEnum.

Reproduction

Run the test at this commit of this repository.

This doesn't necessarily happen at every run but on most of the runs. Repeatedly clean before running the test may yield a different result.

Expected behavior

No exception.

Actual behavior

java.lang.ClassCastException: class io.moia.protos.teleproto.ProtocolBuffersRoundTripTest$Color$Yellow$ cannot be cast to class io.moia.protos.teleproto.ProtocolBuffersRoundTripTest$Color$pink$ (io.moia.protos.teleproto.ProtocolBuffersRoundTripTest$Color$Yellow$ and io.moia.protos.teleproto.ProtocolBuffersRoundTripTest$Color$pink$ are in unnamed module of loader sbt.internal.LayeredClassLoader @7dfaa86d)
[info]   at io.moia.protos.teleproto.ProtocolBuffersRoundTripTest$$anon$2.transform(ProtocolBuffersRoundTripTest.scala:20)
[info]   at io.moia.protos.teleproto.ProtocolBuffersRoundTripTest$$anon$2.transform(ProtocolBuffersRoundTripTest.scala:37)
[info]   at io.moia.protos.teleproto.ProtocolBuffersRoundTripTest$$anon$1.transform$$anonfun$1(ProtocolBuffersRoundTripTest.scala:46)
...

What's especially weird is that the first line of the stack trace is commented out.

Which Chimney version do you use

1.0.0-M1

Which platform do you use

  • JVM
  • Scala.js
  • Scala Native

If you checked JVM

OpenJDK 21.0.2

Additional context

We reproduced that on two MacBooks Pro M1.

If you go to the next commit of the branch I fixed the exception by converting the sealed trait into a Scala 3 enum. I still thought it'd be helpful for you to take a look.

@saeltz saeltz added the bug Erroneous behavior in existing features label Mar 14, 2024
@MateuszKubuszok
Copy link
Member

Thank you for the report!

As I wrote in #480, I don't have much time on my hand so I am not sure when I'll be able to look at the reproduction.

If you wanted to investivate further in the meantime

  • it could be interesting to take a look at the generated code, to see if handwritten version would have the same issue

  • it's very curious , if clean and compile can change the outcome, as it suggest it might be something about Zink and/or classloader. I started observing many issues ever since I updated Scala 3 to 3.3.3 - on 3.3.1 code was compiling fine, but after update I get a lot of broken classpath exceptions, and I have to compile sometimes 3 times or more before it works. I was thinking it might be an artifact of some weird changes to build.sbt I attempted a few days ago, but maybe it's related to the Scala 3 update. I see you have 3.3.3 as well. What would happen if you downgrade to 3.3.1?

@saeltz
Copy link
Contributor Author

saeltz commented Mar 14, 2024

The erroneous derived transformer looks like:

[info]    || {
[info]    ||   final class $anon() extends io.scalaland.chimney.Transformer[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color] {
[info]    ||     def transform(src: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color): io.moia.food.food.Meal.Color = {
[info]    ||       val color: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color = src
[info]    ||       (color: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color @scala.unchecked) match {
[info]    ||         case pink @ io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.pink =>
[info]    ||           val pink: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.pink.type = pink
[info]    ||           {
[info]    ||             (pink: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.pink @scala.unchecked) match {
[info]    ||               case _ =>
[info]    ||                 ()
[info]    ||             }
[info]    ||             ()
[info]    ||           }
[info]    ||           vector.apply(1).asInstanceOf[scala.Function1[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color]].apply(pink.asInstanceOf[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color])
[info]    ||         case red @ io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Red =>
[info]    ||           val red: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Red.type = red
[info]    ||           {
[info]    ||             (red: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Red @scala.unchecked) match {
[info]    ||               case _ =>
[info]    ||                 ()
[info]    ||             }
[info]    ||             ()
[info]    ||           }
[info]    ||           vector.apply(3).asInstanceOf[scala.Function1[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color]].apply(red.asInstanceOf[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color])
[info]    ||         case yellow @ io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Yellow =>
[info]    ||           val yellow: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Yellow.type = yellow
[info]    ||           {
[info]    ||             (yellow: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Yellow @scala.unchecked) match {
[info]    ||               case _ =>
[info]    ||                 ()
[info]    ||             }
[info]    ||             ()
[info]    ||           }
[info]    ||           vector.apply(4).asInstanceOf[scala.Function1[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color]].apply(yellow.asInstanceOf[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color])
[info]    ||         case blue @ io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Blue =>
[info]    ||           val blue: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Blue.type = blue
[info]    ||           {
[info]    ||             (blue: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Blue @scala.unchecked) match {
[info]    ||               case _ =>
[info]    ||                 ()
[info]    ||             }
[info]    ||             ()
[info]    ||           }
[info]    ||           vector.apply(0).asInstanceOf[scala.Function1[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color]].apply(blue.asInstanceOf[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color])
[info]    ||         case orange @ io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.orange =>
[info]    ||           val orange: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.orange.type = orange
[info]    ||           {
[info]    ||             (orange: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.orange @scala.unchecked) match {
[info]    ||               case _ =>
[info]    ||                 ()
[info]    ||             }
[info]    ||             ()
[info]    ||           }
[info]    ||           vector.apply(2).asInstanceOf[scala.Function1[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color]].apply(orange.asInstanceOf[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color])
[info]    ||       }
[info]    ||     }
[info]    ||   }
[info]    || 
[info]    ||   (new $anon(): io.scalaland.chimney.Transformer[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color])
[info]    || }

The enum one:

[info]    || {
[info]    ||   final class $anon() extends io.scalaland.chimney.Transformer[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color] {
[info]    ||     def transform(src: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color): io.moia.food.food.Meal.Color = {
[info]    ||       val color: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color = src
[info]    ||       (color: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color @scala.unchecked) match {
[info]    ||         case blue: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Blue.type =>
[info]    ||           val blue: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Blue.type = blue
[info]    ||           {
[info]    ||             (blue: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Blue @scala.unchecked) match {
[info]    ||               case _ =>
[info]    ||                 ()
[info]    ||             }
[info]    ||             ()
[info]    ||           }
[info]    ||           vector.apply(0).asInstanceOf[scala.Function1[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color]].apply(blue.asInstanceOf[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color])
[info]    ||         case yellow: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Yellow.type =>
[info]    ||           val yellow: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Yellow.type = yellow
[info]    ||           {
[info]    ||             (yellow: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Yellow @scala.unchecked) match {
[info]    ||               case _ =>
[info]    ||                 ()
[info]    ||             }
[info]    ||             ()
[info]    ||           }
[info]    ||           vector.apply(4).asInstanceOf[scala.Function1[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color]].apply(yellow.asInstanceOf[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color])
[info]    ||         case red: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Red.type =>
[info]    ||           val red: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Red.type = red
[info]    ||           {
[info]    ||             (red: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.Red @scala.unchecked) match {
[info]    ||               case _ =>
[info]    ||                 ()
[info]    ||             }
[info]    ||             ()
[info]    ||           }
[info]    ||           vector.apply(3).asInstanceOf[scala.Function1[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color]].apply(red.asInstanceOf[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color])
[info]    ||         case pink: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.pink.type =>
[info]    ||           val pink: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.pink.type = pink
[info]    ||           {
[info]    ||             (pink: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.pink @scala.unchecked) match {
[info]    ||               case _ =>
[info]    ||                 ()
[info]    ||             }
[info]    ||             ()
[info]    ||           }
[info]    ||           vector.apply(1).asInstanceOf[scala.Function1[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color]].apply(pink.asInstanceOf[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color])
[info]    ||         case orange: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.orange.type =>
[info]    ||           val orange: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.orange.type = orange
[info]    ||           {
[info]    ||             (orange: io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color.orange @scala.unchecked) match {
[info]    ||               case _ =>
[info]    ||                 ()
[info]    ||             }
[info]    ||             ()
[info]    ||           }
[info]    ||           vector.apply(2).asInstanceOf[scala.Function1[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color]].apply(orange.asInstanceOf[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color])
[info]    ||       }
[info]    ||     }
[info]    ||   }
[info]    || 
[info]    ||   (new $anon(): io.scalaland.chimney.Transformer[io.moia.protos.teleproto.ProtocolBuffersRoundTripTest.Color, io.moia.food.food.Meal.Color])
[info]    || }

Ignoring all the boilerplate, the only difference is the colon : instead of an @ in each case of the pattern match.

I don't really know the value of vector and can't find it. I assume it's a Vector[io.moia.food.food.Meal.Color] where the To type is gotten by index. Is there a way to get that? I could then try to use the derived code and debug further.

@saeltz
Copy link
Contributor Author

saeltz commented Mar 15, 2024

Using Scala 3.3.1 the problem still exists. Also on Scala 3.4. (Although I found another issue with ScalaPB: scalapb/ScalaPB#1662)

@MateuszKubuszok
Copy link
Member

I don't really know the value of vector and can't find it. I assume it's a Vector[io.moia.food.food.Meal.Color] where the To type is gotten by index. Is there a way to get that? I could then try to use the derived code and debug further.

vector is not printed but it's Vector[Any] where all runtime data is stored (basically all withFieldConst/withFieldComputed/... values). If e.g you took

  given Transformer[Color, food.Meal.Color] = Transformer
    .define[Color, food.Meal.Color]
    .withCoproductInstance[Color.Yellow.type](_ => food.Meal.Color.COLOR_YELLOW)
    .withCoproductInstance[Color.Red.type](_ => food.Meal.Color.COLOR_RED)
    .withCoproductInstance[Color.orange.type](_ => food.Meal.Color.COLOR_ORANGE)
    .withCoproductInstance[Color.pink.type](_ => food.Meal.Color.COLOR_PINK)
    .withCoproductInstance[Color.Blue.type](_ => food.Meal.Color.COLOR_BLUE)
    .buildTransformer

and did this:

  given Transformer[Color, food.Meal.Color] =
    val foo = Transformer
      .define[Color, food.Meal.Color]
      .withCoproductInstance[Color.Yellow.type](_ => food.Meal.Color.COLOR_YELLOW)
      .withCoproductInstance[Color.Red.type](_ => food.Meal.Color.COLOR_RED)
      .withCoproductInstance[Color.orange.type](_ => food.Meal.Color.COLOR_ORANGE)
      .withCoproductInstance[Color.pink.type](_ => food.Meal.Color.COLOR_PINK)
      .withCoproductInstance[Color.Blue.type](_ => food.Meal.Color.COLOR_BLUE)
    println(foo.runtimeData)
    foo.buildTransformer

you'd see its content.

@saeltz
Copy link
Contributor Author

saeltz commented Mar 16, 2024

I minimised the example even further in https://github.com/moia-oss/teleproto/blob/chimney2/src/test/scala/io/moia/protos/teleproto/ProtocolBuffersRoundTripTest.scala.

I found another difference: It's not only the : but also the .type in the same line.

The ClassCastException is thrown by the @unchecked annotation as documented here:
https://www.scala-lang.org/api/current/scala/unchecked.html. I don't really understand why it's not thrown in the enum variant.

@MateuszKubuszok
Copy link
Member

I must be a corner-case that should be fixed in https://github.com/scalalandio/chimney/blob/master/chimney-macro-commons/src/main/scala-3/io/scalaland/chimney/internal/compiletime/ExprPromisesPlatform.scala#L125 which wasn't triggered by any of the tests we have.

I reproduced the issue with Scastie, and my guess is that:

   case pink @ Playground.Color.pink =>
     val pink: Playground.Color.pink.type = pink

must be catching all values (basically being treated as case pink =>), and then this exception is justified. I am not sure though why it happens and why none of our tests are not having this issue (we explicitly test matching of every value of an enum/sealed trait to make sure it doesn't happen).

Perhaps @jchyb would know something?

@jchyb
Copy link
Contributor

jchyb commented Mar 19, 2024

I looked at this yesterday and it seems that the issue is not with the pattern match case catching incorrect values but with incorrect runtimeData being used in the rhs there. E.g.:

//> using dep "io.scalaland::chimney:0.8.5"

import io.scalaland.chimney.dsl.*
import io.scalaland.chimney.Transformer

sealed trait Color
object Color {
  case object orange extends Color
  case object pink extends Color
}

sealed trait Target
object Target {
  case class Impl(value: String) extends Target
}

@main def main() = 
  val writer: Transformer[Color, Target] = Transformer
    .define[Color, Target]
    .withCoproductInstance[Color.orange.type](_ => Target.Impl("orange"))
    .withCoproductInstance[Color.pink.type](_ => Target.Impl("pink"))
    .enableMacrosLogging
    .buildTransformer

  writer.transform(Color.pink)

when running this with -Xprint:inlining, we get (I trimmed some of the types for readability):

val writer: io.scalaland.chimney.Transformer[Color, Target] =
          {
            val TransformerDefinition_this:  io.scalaland.chimney.dsl.TransformerDefinition[...]
             = io.scalaland.chimney.Transformer.define[Color, Target]
            val TransformerDefinition_this: 
                io.scalaland.chimney.dsl.TransformerDefinition[...]
             =
              io.scalaland.chimney.internal.runtime.WithRuntimeDataStore.update[ // idx: 0, orange
               ...
              ](TransformerDefinition_this,
                {
                  def $anonfun(_$1: Color.orange.type): Target =
                    Target.Impl.apply("orange")
                  closure($anonfun)
                }
              ).asInstanceOf[...]
            {
              val TransformerDefinition_this:
                  io.scalaland.chimney.dsl.TransformerDefinition[...]
               =
                io.scalaland.chimney.internal.runtime.WithRuntimeDataStore.
                  update[...](TransformerDefinition_this, // idx: 1, pink
                  {
                    def $anonfun(_$2: Color.pink.type): Target =
                      Target.Impl.apply("pink")
                    closure($anonfun)
                  }
                ).asInstanceOf[...].enableMacrosLogging
              {
                val vector$macro$1: Vector[Any] =
                  TransformerDefinition_this.runtimeData
                {
                  {
                    (vector$macro$1:Vector[Any] @unchecked match 
                      {
                        case _ => ()
                      }
                    )
                    ()
                  }
                  {
                    final class $anon() extends Object(), io.scalaland.chimney.
                      Transformer[Color, Target] {
                      def transform(src: Color): Target =
                        {
                          val color$macro$1: Color = src
                          color$macro$1:Color @unchecked match 
                            {
                              case orange$macro$2 @ Color.orange =>
                                val orange$macro$1: Color.orange.type =
                                  orange$macro$2
                                {
                                  orange$macro$1:Color.orange.type @unchecked
                                     match 
                                    {
                                      case _ => ()
                                    }
                                  ()
                                }
                                vector$macro$1.apply(1).asInstanceOf[ // idx 1: pink for type orange
                                  Color => Target].apply(
                                  orange$macro$1.asInstanceOf[Color])
                              case pink$macro$2 @ Color.pink =>
                                val pink$macro$1: Color.pink.type = pink$macro$2
                                {
                                  pink$macro$1:Color.pink.type @unchecked match 
                                    {
                                      case _ => ()
                                    }
                                  ()
                                }
                                vector$macro$1.apply(0).asInstanceOf[ // idx: 0, orange, for type pink
                                  Color => Target].apply(
                                  pink$macro$1.asInstanceOf[Color])
                            }
                        }
                    }
                    new $anon():io.scalaland.chimney.Transformer[Color, Target]
                  }
                }
              }:io.scalaland.chimney.Transformer[Color, Target]
            }
          }

This can also be replicated in tests, however changing the order of withCoproductInstance will "fix" the issue, so I imagine that is why it was not discovered there. I have not managed to fix this yet, unfortunately

@MateuszKubuszok
Copy link
Member

@jchyb thank you for figuring it out!

If that's the case then either:

So to fix it we'd have to:

@saeltz would you be interested in trying to fix it?

@MateuszKubuszok MateuszKubuszok added the low hanging fruit Task that should be easy to implement - perfect for people that want to start contributing. label Mar 29, 2024
@saeltz
Copy link
Contributor Author

saeltz commented Apr 1, 2024

would you be interested in trying to fix it?

I first want to get #489 over the finish line.

@MateuszKubuszok
Copy link
Member

MateuszKubuszok commented Apr 4, 2024

@jchyb I believe that it a bug in pattern matching on Scala 3 after all. (Or in detecting case objects).

I compared:

  • type level representations of config
  • coproductOverrides

in

They are correct - runtime values are prepended so that what is in Vector[Any] corresponds with the position with type-level-list of overrides (rename is an exception but in everything else prepends both on type-level and on value-level).

  val writer: Transformer[Color, Target] = Transformer
    .define[Color, Target]
    .withCoproductInstance[Color.orange.type](_ => Target.Impl("orange")) // last but one override -> idx = 1
    .withCoproductInstance[Color.pink.type](_ => Target.Impl("pink")) // last override -> idx = 0
    .enableMacrosLogging
    .buildTransformer

What changes is the order of case clauses in a pattern matching - at first Chimney generates them from CoproductInstance overrides - in the order it found them in a config, so it's deterministic - and then fill the remaining subtypes by matching names.

You can see that failing example has orange as the first case and working one has pink as the first case. Pass orange and the outcome reverses.

(I had a much longer comment but I accidentally refreshed and it all got removed :| )

I was certain that I test for these, but apparently the test was not detailed enough. :/

EDIT:

I tried to dig around, I refactored the code a bit, to eliminate leaky abstraction and imprecise methods names to make the code easier to read, but was not successful. Unfortunately, Ident.unapply returns String instead of a symbol, so I have to guess what exactly goes into it when compiler constructs an ADT that is not throwing :/

EDIT2:

I modified the first test of SealedHierarchySpec

    (colors1.Red: colors1.Color).transformInto[colors2.Color] ==> colors2.Red
    (colors1.Green: colors1.Color).transformInto[colors2.Color] ==> colors2.Green
    (colors1.Blue: colors1.Color).transformInto[colors2.Color] ==> colors2.Blue

    (colors1.Red: colors1.Color)
      .into[colors2.Color]
      .withCoproductInstance[colors1.Red.type](_ => colors2.Red)
      .withCoproductInstance[colors1.Blue.type](_ => colors2.Blue)
      .transform ==> colors2.Red

    (colors1.Blue: colors1.Color)
      .into[colors2.Color]
      .withCoproductInstance[colors1.Red.type](_ => colors2.Red)
      .withCoproductInstance[colors1.Blue.type](_ => colors2.Blue)
      .transform ==> colors2.Blue

    (colors1.Green: colors1.Color)
      .into[colors2.Color]
      .withCoproductInstance[colors1.Red.type](_ => colors2.Red)
      .withCoproductInstance[colors1.Blue.type](_ => colors2.Blue)
      .transform ==> colors2.Green

it... passed. So I tried one more thing. I changed all names to PascalCase:

import io.scalaland.chimney.dsl.*
import io.scalaland.chimney.Transformer

sealed trait Color
object Color {
  case object Orange extends Color
  case object Pink extends Color
}

sealed trait Target
object Target {
  case class Impl(value: String) extends Target
}

val testDupa = Transformer
  .define[Color, Target]
  .withCoproductInstance[Color.Orange.type](_ => Target.Impl("orange")) // 1
  .withCoproductInstance[Color.Pink.type](_ => Target.Impl("pink")) // 0
  .enableMacrosLogging

val testDupa2 = Transformer
  .define[Color, Target]
  .withCoproductInstance[Color.Pink.type](_ => Target.Impl("pink")) // 1
  .withCoproductInstance[Color.Orange.type](_ => Target.Impl("orange")) // 0
  .enableMacrosLogging

val writer: Transformer[Color, Target] = testDupa // orange -> 1, pink -> 0
  .buildTransformer

scala.util.Try(writer.transform(Color.Pink))

val writer2: Transformer[Color, Target] = testDupa2 // pink -> 1, orange -> 0
  .buildTransformer

scala.util.Try(writer2.transform(Color.Pink))
scala.util.Try(writer2.transform(Color.Orange))

It works without any issues.

I tried to skip withCoproductInstance but match it against virtually the same sealed hierarchy - also with lowercase names.

It fails in exactly the same way.

This explains why no other tests detected it. This only fails for a combination of:

  • Scala 3
  • sealed trait
  • with case objects with lowercased names

😱

@MateuszKubuszok MateuszKubuszok added dragons ahead Task which requires handwriting compiletime reflection for Scala2&3 and/or updating the architecture bug in compiler Possible compiler bug, either library can work around it or it's a wontfix and removed low hanging fruit Task that should be easy to implement - perfect for people that want to start contributing. dragons ahead Task which requires handwriting compiletime reflection for Scala2&3 and/or updating the architecture labels Apr 4, 2024
@MateuszKubuszok
Copy link
Member

Since there is nothing we can do do fix this, and the only solution I can see is fixing it in the compiler, I'll close it now.

@MateuszKubuszok
Copy link
Member

Reported as scala/scala3#20350

@saeltz
Copy link
Contributor Author

saeltz commented Sep 23, 2024

It seems to be an issue with the macro itself, see scala/scala3#20350 (comment). Would you consider re-opening and fixing?

@MateuszKubuszok
Copy link
Member

I can reopen it, but I don't see any capacity for doing much OSS work anytime soon in my schedule.

@MateuszKubuszok MateuszKubuszok removed bug in compiler Possible compiler bug, either library can work around it or it's a wontfix wontfix labels Oct 2, 2024
@MateuszKubuszok
Copy link
Member

Released in 1.5.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Erroneous behavior in existing features
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants