dimanche 6 décembre 2020

How to reflect concrete types that corresponds to the type parameters of an abstraction type in Scala?

Suppose we have a generic type (for example, Seq[E]) and a concrete subtype (for example, Seq[Int]). How can we extract concrete type that corresponds to the type parameters of the abstraction type. In other words, how can we know E -> Int.

Below is a minimal code example that tests for the desired behavior. The extractTypeBinding function would perform the transformation in question.

import scala.reflect.runtime.{universe => ru}

class MyFuncs

object MyFuncs {
  def fn1[E](s: Seq[E]): E = ???
  def fn2[K, V](m: Map[K, V]): Int = ???
}

case class Bar[T]()

trait Foo[T] { def bar: Bar[T] }

object Scratch {

  def extractTypeBinding(genType: ru.Type, typeParam: ru.Type)(concreteType: ru.Type): ru.Type = ???

  def getArgTypes(methodSymbol: ru.MethodSymbol): Seq[ru.Type] =
    methodSymbol.paramLists.headOption.getOrElse(Nil).map(_.typeSignature)

  def main(a: Array[String]): Unit = {

    // Grab the argument types of our methods.
    val funcsType = ru.typeOf[MyFuncs].companion
    val fn1ArgTypes = getArgTypes(funcsType.member(ru.TermName("fn1")).asMethod)
    val fn2ArgTypes = getArgTypes(funcsType.member(ru.TermName("fn2")).asMethod)

    val genericSeq = fn1ArgTypes.head  // Seq[E]
    val genericMap = fn2ArgTypes.head  // Map[K, V]

    // Create an extractor for the `E` in `Seq[E]`.
    val seqElExtractor = extractTypeBinding(genericSeq, genericSeq.typeArgs.head) _
    // Extractor for the `K` in `Map[K,V]`
    val mapKeyExtractor = extractTypeBinding(genericMap, genericMap.typeArgs.head) _
    // Extractor for the `V` in `Map[K,V]`
    val mapValueExtractor = extractTypeBinding(genericMap, genericMap.typeArgs(1)) _

    println(seqElExtractor(ru.typeOf[Seq[Int]])) // should be Int
    println(seqElExtractor(ru.typeOf[Seq[Map[String, Double]]])) // should be Map[String, Double]
    println(mapKeyExtractor(ru.typeOf[Map[String, Double]])) // should be String
    println(mapKeyExtractor(ru.typeOf[Map[Int, Boolean]])) // should be Int
    println(mapValueExtractor(ru.typeOf[Map[String, Double]])) // should be Double
    println(mapValueExtractor(ru.typeOf[Map[Int, Boolean]])) // should be Boolean
  }

}

Based on the docstrings, it seems like asSeenFrom should be the key to implementing extractTypeBinding. I tried the below implementation, but it returned the type parameter unchanged.

  def extractTypeBinding(genType: ru.Type, typeParam: ru.Type)(concreteType: ru.Type): ru.Type =
    typeParam.asSeenFrom(concreteType, genType.typeSymbol.asClass)
  
  ...

  println(seqElExtractor(ru.typeOf[Seq[Int]])) // E
  println(seqElExtractor(ru.typeOf[Seq[Map[String, Double]]])) // E

If asSeenFrom is the correct approach, what would the correct incantation be? If not, then how should this be done?





Aucun commentaire:

Enregistrer un commentaire