2

How can I implement the following psuedocode in Scala using reflection?

I require this for the purposes of looking-up a generic type from Guice:

trait Foo[A]
class FooInt extends Foo[Int]
class FooString extends Foo[String]

bind(new TypeLiteral<Foo<Int>>() {}).to(FooInt.class);

def fooTypeLiteral(paramA: Class[_]): TypeLiteral[_] = ???

val foo = injector.getInstance(fooTypeLiteral(classOf[Int])
// foo: FooInt

Note: I do not have access to the type of A at compile time, hence the _. The entire solution needs to be performed reflectively (e.g. I cannot have parameterizeFoo[A : ClassTag](...)).

Lawrence Wagerfield
  • 6,471
  • 5
  • 42
  • 84
  • If you replace `???` by `classOf[Foo[_]]` and `===` by `==`, then it works as-is. It's just that the `classOf[Foo[_]]` does not know or care about the parameter passed to `Foo[_]` because of the runtime type erasure. If the type parameter is erased at runtime, and if you don't have it at compile time as some kind of type tag, then you have nothing to work with. Therefore, it's not clear what exactly you're asking. – Andrey Tyukin Jun 25 '18 at 11:04
  • I will be passing the `Class[_]` to `Guice.injector.getInstance(class)` so I'd assume the `class` instance would need to contain sufficient type information for Guice to perform the lookup? – Lawrence Wagerfield Jun 25 '18 at 11:13
  • No, `Class[_]` does not contain sufficient type information if the class has generic parameters. That's why [there are guice `TypeLiteral`s](https://stackoverflow.com/questions/24657127/guice-injecting-generic-type). Is it a duplicate? – Andrey Tyukin Jun 25 '18 at 11:16
  • Interesting. I would still need to reflectively generate an instance of `TypeLiteral>` as I don't know what `Int` is until runtime. – Lawrence Wagerfield Jun 25 '18 at 11:21
  • I think that it's a crucial enough detail that should definitely be added to the question then. The question should also be tagged `[guice]`. – Andrey Tyukin Jun 25 '18 at 11:26

2 Answers2

1

You could try to create a ParameterizedType and pass it to the factory method of the TypeLiteral:

def fooTypeLiteral(paramA: Class[_]): TypeLiteral[_] = {
  TypeLiteral.get(new java.lang.reflect.ParameterizedType() {
    def getRawType = classOf[Foo[_]]
    def getOwnerType = null
    def getActualTypeArguments = Array(paramA)
  })
}

If you have only a finite number of Foo implementations, you could try this:

trait Foo[A]
class FooInt extends Foo[Int]
class FooString extends Foo[String]

val TLFI = new TypeLiteral[Foo[Int]](){}
val TLFS = new TypeLiteral[Foo[String]](){}

bind(TLFI).to(FooInt.class);
bind(TLFS).to(FooString.class);

def fooTypeLiteral(c: Class[_]): TypeLiteral[_] = {
  if (c == classOf[Int]) TLFI
  else if (c == classOf[String]) TLFS
  else throw new Error
}
Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
0

Both Scala and Java compilers implement generics with type erasure. This means that all type information for sub-types of generics is lost when the source code is converted to JVM byte code. If the generic class itself does not hold ClassTag or similar, embedded information, then you cannot get the class at run time.

Bob Dalgleish
  • 8,167
  • 4
  • 32
  • 42
  • Ok. And how then do guice's `TypeLiteral`s retain the complete type information? – Andrey Tyukin Jun 25 '18 at 14:49
  • Because you supply a fully qualified generic instance with bound types in the source code. The object you pass in to bind has all the necessary type information in place as it is constructed. What you are trying to do through reflection is quite different. – Bob Dalgleish Jun 25 '18 at 16:41