Get the type parameters of an instance with scala reflection

Hello everyone, I’m trying to create a method to access the concrete type of type parameters of a class.

import scala.reflect.runtime.universe._

abstract class BaseClass
class A[T](a: T) extends BaseClass
class B[T, U](a: T, b: U) extends BaseClass
class C[T, U]() extends BaseClass

// Get the parametrics of a given instance x 
def getConstructorParametrics[T](x: T)(implicit wtt: WeakTypeTag[T]): Seq[String] = {
  wtt.tpe match {
     case TypeRef(utype, usymbol, args) => args.map(_.toString.replaceAll(",(?! )", ", "))
  }
}

This method works but only if I call it directly:

getConstructorParametrics(new A(1))             // List("Int")
getConstructorParametrics(new B(1, "hello"))    // List("Int", "String")
getConstructorParametrics(new C[Int, String])   // List("Int", "String")
getConstructorParametrics(new C[Int, A[Int]])   // List("Int", "A[Int]")

In my use case, I have to call that function from a wrapper function. However, it returns an empty list:

def wrapperFn1[T <: BaseClass](x: T) = getConstructorParametrics(x)
wrapperFn1(new A(1))             // List()
wrapperFn1(new B(1, "hello"))    // List()
wrapperFn1(new C[Int, String])   // List()
wrapperFn1(new C[Int, A[Int]])   // List()

I found that it works if I do something like this:

def wrapperFn2[T <: BaseClass](x: T)(implicit wtt: WeakTypeTag[T]) = getConstructorParametrics(x)(wtt)

But I cannot, in my case, the wrapper function signature is already defined and I can’t really update it. I also tried to use TypeTag but it returns the type parameter symbol rather than the concrete type. Namely, List(T), List(T, U), List(T, U), List(T, U) for the cases above.

Does anyone have suggestions for doing that?

This is the scastie template: Scastie - An interactive playground for Scala.

You can’t access at runtime the type information, because that is already lost.
The only way is to preserve it since compile time using a typeclass like WeakTypeTag, but that requires no indirection; because it needs to be derived at the site of the creation of the value.

My two cents, approach the meta-problem in a different way.

Tell us what you want to do here.

What you really, really want.

If it’s a zagga-zagga thing you need (sorry, just had to do that), you could try getting the Java class object and work back to a type tag in Scala 2.13/2.12:

object typeTagForClass {
  def apply[T](clazz: Class[T]): TypeTag[T] = {
    val classSymbol = currentMirror.classSymbol(clazz)
    val classType   = classSymbol.toType
    TypeTag(
      currentMirror,
      new TypeCreator {
        def apply[U <: Universe with Singleton](
            m: scala.reflect.api.Mirror[U]): U#Type =
          if (m eq currentMirror) classType.asInstanceOf[U#Type]
          else
            throw new IllegalArgumentException(
              s"Type tag defined in $currentMirror cannot be migrated to other mirrors.")
      }
    )
  }
}

I stole this from some Scala luminary who is probably reading this, so in case it blows up, it’s their fault. :melting_face:.

Disclaimer: I removed this piece of code and cut over to using plain Java class.

+1 for @BalmungSan’s advice.

1 Like

Basically, given an instance of a child of an abstract class (BaseClass), I want to get the concrete type parameters of that instance. The challenge is that, the function would be wrapped inside another function:

def topFunction(x: BaseClass) = {
// ....
getTypeParameters(x)
// ....
}

I tried to use this in the example I provided in scastie but it didn’t work.

So, you mean to pass (implicit wtt: WeakTypeTag[T]) since the first function in the call stack, right?

Yes, that is the only way.
Because, again, you want to access during runtime type information; which is only available during compile time. So you need to preserve it.

While we understand that, we ask what is the meta-problem that leads you here.

This kind of class / type checking in runtime is usually considered a bad practice and IME is the result of a bad design; sometimes that bad design comes from a third-party framework so refactoring may not be an option.

Looking at the Scastie console, it did work (or you got it working in the meantime), but you seem set on digging out the unerased type parameters at runtime, so a plain type tag won’t do.

I still don’t know what you’re trying to achieve overall, but here’s a question - do you own the type hierarchy BaseClass and its various subclasses?

If you do, why not ask them to do whatever it is you really need with a new abstract method?

If you don’t own BaseClass, but do own the subclasses, can you freeze the type parameters by instantiating a subclass in parallel for each of A, B and C? Perhaps they shouldn’t be generic if you care so much about the concrete types of the parameters - or perhaps they should have type bounds on them so that you can work with them without knowing their exact runtime type.

You could add an extension interface / trait to the subclasses, dig it out at runtime and ask questions of that.

The ultimate in runtime hacking is to use ByteBuddy to make either new classes at runtime that parallel some existing ones that you can’t touch, or to make augmented proxy objects that decorate existing ones. You can use both ByteBuddy and plain Java reflection to tear apart your objects field by field if you absolutely have to know everything about some arbitrary objects thrown at you.

Are you absolutely sure you need to solve this problem? Would stepping back and thinking about the bigger picture allow you to avoid it in the first place?

I’ve run out of ideas, so I’ll leave it with you to ponder…

1 Like