-
-
Notifications
You must be signed in to change notification settings - Fork 94
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
Comments
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
|
The erroneous derived transformer looks like:
The enum one:
Ignoring all the boilerplate, the only difference is the colon I don't really know the value of |
Using Scala 3.3.1 the problem still exists. Also on Scala 3.4. (Although I found another issue with ScalaPB: scalapb/ScalaPB#1662) |
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. |
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 The |
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 Perhaps @jchyb would know something? |
I looked at this yesterday and it seems that the issue is not with the pattern match case catching incorrect values but with incorrect //> 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 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 |
@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? |
I first want to get #489 over the finish line. |
@jchyb I believe that it a bug in pattern matching on Scala 3 after all. (Or in detecting case objects). I compared:
in
They are correct - runtime values are prepended so that what is in 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 You can see that failing example has (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, 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)) 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:
😱 |
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. |
Reported as scala/scala3#20350 |
It seems to be an issue with the macro itself, see scala/scala3#20350 (comment). Would you consider re-opening and fixing? |
I can reopen it, but I don't see any capacity for doing much OSS work anytime soon in my schedule. |
Released in 1.5.0 |
Checklist
TransformerF
s) orunsafeOption
flagsDescribe the bug
A
ClassCastException
is thrown when trying to transform asealed trait
into a ProtobufGeneratedEnum
.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
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
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 3enum
. I still thought it'd be helpful for you to take a look.The text was updated successfully, but these errors were encountered: