Les réflexions d'exécution Scala obtiennent tous les membres d'un type spécifique, même pour les classes internes

Dec 16 2020

Avec scala 2.12.10

Supposons que je veuille convertir implicitement au moment de l'exécution une classe de cas, dans ce cas Specialen une classe de cas SpecialString. La conversion implicite est fournie par un trait External. Le nom de SpecialStringdoit être le nom de déclaration de la 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")
  }
}

Actuellement, je suis capable de convertir implicitement les membres convertis déclarés dans une classe étendant le Externaltrait.

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)

Mais je ne parviens pas à convertir les membres déclarés dans une super classe

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

De l'aide?

Réponses

1 DmytroMitin Dec 17 2020 at 11:51

Si vous trouvez le membre nécessaire par son nom plutôt que typeSignature(et il est effectivement trouvé alors) et imprimez specials.head.typeSignatureet typeOf[Special]vous verrez pourquoi l'un n'est pas un sous-type d'un autre

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

Le type d'une méthode nullaire retournant Specialn'est pas un sous-type de Special.

Vous devriez ajouter resultType. Remplacer

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

avec

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

Comment trouver le type de données de paramètre de classe au moment de l'exécution dans scala