scalareflectiontype-erasurescala-reflect

Scala type erasure warning for non-generics scenario


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


Solution

  • 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.