Skip to content

ClassCastException when using Union type with case object and TypeTest #22950

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

Open
GuillaumeSaintRaymondSonos opened this issue Apr 9, 2025 · 0 comments
Labels
area:union-types Issues tied to union types. itype:bug itype:soundness Soundness bug (it lets us compile code that crashes at runtime with a ClassCastException)

Comments

@GuillaumeSaintRaymondSonos

Compiler version

3.6.4

Minimized code

I'm building a toy typeclass (named Typeclass) and I want it to support Union types, so I wrote a combine method that takes a Typeclass[A] and a Typeclass[B] and returns a Typeclass[A | B]. I'm using TypeTest to be able to match the type at compile time.
When declaring a Typeclass instance for a case object, this sample code compiles correctly but generates a ClassCastException.

import scala.reflect.TypeTest

trait Typeclass[A] {
  def print(a: A): String
}

object Typeclass {
  def apply[A: Typeclass]: Typeclass[A] = implicitly

  def instance[A](fn: A => String): Typeclass[A] = fn(_)

  def combine[A, B](
      typeclassA: Typeclass[A],
      typeclassB: Typeclass[B]
  )(using
      TypeTest[A | B, A],
      TypeTest[A | B, B]
  ): Typeclass[A | B] =
    Typeclass.instance[A | B](
      {
        case a: A => typeclassA.print(a)
        case b: B => typeclassB.print(b)
      }
    )
}

case class CaseClassA(value: String = "A")
case class CaseClassB(value: String = "B")
case object CaseObject

val typeclass: Typeclass[CaseClassA | CaseClassB | CaseObject.type] =
  Typeclass.combine(
    Typeclass.instance[CaseClassA](_.value),
    Typeclass.combine(
      Typeclass.instance[CaseObject.type](_ => "C"),
      Typeclass.instance[CaseClassB](_.value)
    )
  )

def print(elem: CaseClassA | CaseClassB | CaseObject.type) =
  println(typeclass.print(elem))

print(CaseClassA())
print(CaseClassB())
print(CaseObject)

Output

By running scala-cli -S 3.6.4 classCastException.sc, I get:

A
B
Exception in thread "main" java.lang.ClassCastException: class classCastException$_$CaseObject$ cannot be cast to class classCastException$_$CaseClassB (classCastException$_$CaseObject$ and classCastException$_$CaseClassB are in unnamed module of loader 'app')
        at classCastException$_.$init$$$anonfun$7(classCastException.sc:38)
        at classCastException$_.classCastException$_$Typeclass$$$_$combine$$anonfun$1(classCastException.sc:22)
        at classCastException$_.classCastException$_$Typeclass$$$_$instance$$anonfun$1(classCastException.sc:10)
        at classCastException$_.print(classCastException.sc:41)
        at classCastException$_.<init>(classCastException.sc:45)
        at classCastException_sc$.script$lzyINIT1(classCastException.sc:60)
        at classCastException_sc$.script(classCastException.sc:60)
        at classCastException_sc$.main(classCastException.sc:64)
        at classCastException_sc.main(classCastException.sc)

Expectation

A
B
C

Workaround

If a declare typeclass by switching CaseClassA and CaseObject typeclass instances, there's no exception and it gives the correct result

val typeclass: Typeclass[CaseClassA | CaseClassB | CaseObject.type] =
  Typeclass.combine(
    Typeclass.instance[CaseObject.type](_ => "C"),
    Typeclass.combine(
      Typeclass.instance[CaseClassA](_.value),
      Typeclass.instance[CaseClassB](_.value)
    )
  )
@GuillaumeSaintRaymondSonos GuillaumeSaintRaymondSonos added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Apr 9, 2025
@Gedochao Gedochao added itype:soundness Soundness bug (it lets us compile code that crashes at runtime with a ClassCastException) area:union-types Issues tied to union types. and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Apr 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:union-types Issues tied to union types. itype:bug itype:soundness Soundness bug (it lets us compile code that crashes at runtime with a ClassCastException)
Projects
None yet
Development

No branches or pull requests

2 participants