I'm writing a custom serializer for Scala case classes and have a utility function to determine whether various symbols are case classes:
import scala.reflect.runtime.{universe => ru}
def isCaseClass(symbol: ru.Symbol): Boolean = (symbol.isInstanceOf[ru.MethodSymbol] &&
symbol.asInstanceOf[ru.MethodSymbol].isCaseAccessor) ||
(symbol.isInstanceOf[ru.ClassSymbol] &&
symbol.asInstanceOf[ru.ClassSymbol].isCaseClass)
The isInstanceOf
checks lead to warnings but I don't know why:
abstract type reflect.runtime.universe.MethodSymbol is unchecked since it is eliminated by erasure
abstract type reflect.runtime.universe.ClassSymbol is unchecked since it is eliminated by erasure
I understand type erasure when generics are involved, e.g. JVM can't distinuguish List[Integer]
from List[String]
and we enter the realm of type tags to sort these sorts of issues. But in my case MethodSymbol
is a subclass of Symbol
so why the type erasure?
Could someone kindly explain?
Thanks,
David
As raised in a comment, I was originally going to raise that this is caused by the usage of type-casting, but changing the code to use pattern matching instead did not help:
import scala.reflect.runtime.universe.{MethodSymbol, ClassSymbol, Symbol}
def isCaseClass(symbol: Symbol): Boolean =
symbol match {
case m: MethodSymbol if m.isCaseAccessor => true
case c: ClassSymbol if c.isCaseClass => true
case _ => false
}
The warning is still there, but I would still in general recommend to use pattern matching instead of isInstanceOf
/asInstanceOf
(I remember Scala creator Martin Odersky mentioning in his "Principles of Functional Programming in Scala" on Coursera that he made the pair verbose on purpose to incentivize people to use pattern matching).
I'm not 100% sure about the specific reason why the warning appears in this context, but one possible explanation is that the actual thing backed by the Symbol
itself might appear in a generic context, meaning that it is a parameter to a generic class. As such, the compiler cannot guarantee that the concrete implementation of Symbol
is captured at runtime, leading to the warning.
As a workaround, if you are OK with this information not being captured if they appear as a generic argument, you can mark the types as @unchecked
as follows:
import scala.reflect.runtime.universe.{MethodSymbol, ClassSymbol, Symbol}
def isCaseClass(symbol: Symbol): Boolean =
symbol match {
case m: MethodSymbol @unchecked if m.isCaseAccessor => true
case c: ClassSymbol @unchecked if c.isCaseClass => true
case _ => false
}
You can play around with this code here on Scastie.