As reflexões de tempo de execução do Scala obtêm todos os membros de um tipo específico, mesmo para classes internas

Dec 16 2020

Com scala 2.12.10

Suponha que eu queira converter implicitamente no tempo de execução uma classe de caso, neste caso Specialem uma classe de caso SpecialString. A conversão implícita é fornecida por um traço External. O nome de SpecialStringdeve ser o nome da declaração da classe Special.

import scala.reflect.runtime.universe.{runtimeMirror, typeOf}
import scala.reflect.runtime.universe


case class Special(i: Int)
case class SpecialString(s: String)

trait External {
  val rm = runtimeMirror(getClass.getClassLoader)
  val im = rm.reflect(this)
  val members = im.symbol.typeSignature.members
  def specials: Iterable[universe.Symbol] = members.filter(_.typeSignature <:< typeOf[Special] )
  implicit def SpecialIntToString(s: Special): SpecialString = {
    val name = im.reflectField(specials.filter(x => im.reflectField(x.asTerm).get.asInstanceOf[Special] == s).head.asTerm).symbol.toString.replace("value ", "")
    SpecialString(s"name = $name")
  }
}

Atualmente sou capaz de converter implicitamente os membros convert declarados dentro de uma classe que estende o Externaltraço.

class MyClass extends External {
  val firstSpecial = Special(1)
  val two = 2
  val specialS: SpecialString = firstSpecial
}

class MySecondClass extends MyClass {
  val specialS2: SpecialString = firstSpecial
}
val myClass = new MyClass
print(myClass.specialS) // SpecialString(name = firstSpecial)

Mas não consigo converter membros declarados em uma superclasse

class MyClass {
  val firstSpecial = Special(1)
  val two = 2
  val specialS: SpecialString = firstSpecial
}

class MySecondClass extends MyClass with External {
  val specialS2: SpecialString = firstSpecial
}
val myClass = new MyClass
print(myClass.specialS)
val mySecondClass = new MySecondClass
print(mySecondClass.specialS2) // java.util.NoSuchElementException: next on empty iterator

Qualquer ajuda?

Respostas

1 DmytroMitin Dec 17 2020 at 11:51

Se você encontrar o membro necessário pelo nome em vez de typeSignature(e ele é realmente encontrado) e imprimir specials.head.typeSignaturee typeOf[Special]você verá porque um não é um subtipo de outro

trait External {
  ...
  def specials: Iterable[universe.Symbol] =
    members.filter(_.name == universe.TermName("firstSpecial") )
    //members.filter(_.typeSignature.resultType <:< typeOf[Special] )
  println(s"specials.head.typeSignature=${specials.head.typeSignature}=${universe.showRaw(specials.head.typeSignature)}")
  println(s"typeOf[Special]=${typeOf[Special]}=${universe.showRaw(typeOf[Special])}")
  println(s"specials.head.typeSignature <:< typeOf[Special]=${specials.head.typeSignature <:< typeOf[Special]}")
  ...
}

//specials.head.typeSignature=pckg.App.Special=NullaryMethodType(TypeRef(ThisType(pckg.App), pckg.App.Special, List()))
//typeOf[Special]            =pckg.App.Special=TypeRef(ThisType(pckg.App), pckg.App.Special, List())
//specials.head.typeSignature <:< typeOf[Special]=false

O tipo de retorno de método nulo Specialnão é um subtipo de Special.

Você deve adicionar resultType. Substituir

trait External {
  ...
  def specials: Iterable[universe.Symbol] =
    members.filter(_.typeSignature <:< typeOf[Special] )

com

trait External {
  ...
  def specials: Iterable[universe.Symbol] =
    members.filter(_.typeSignature.resultType <:< typeOf[Special])

Como encontrar o tipo de dados do parâmetro de classe em tempo de execução no scala