Home > database >  Scala upper type bounds for reflectioned type
Scala upper type bounds for reflectioned type

Time:12-07

For instance a have a type B. I know it is subtype of A.
I want to use type B in runtime, having only class name and using reflection.

I am trying to get it with Class.forName("B").asSubclass(classOf[A])

The problem is, I cant use this type in upper bounded functions
def test[T <: A]() = ???

Minimal example:

trait A
class B extends A

val cls: Class[_ <: A] = Class.forName("B").asSubclass(classOf[A])

def test[T <: A]() = ???

test[cls.type]()   // Error: type arguments cls.type 
                   // do not conform to upper bound A 
                   // of type parameter T

Any way to make compiler work?

CodePudding user response:

You should better describe the actual problem you're trying to solve. Currently this sounds like XY problem.

Here seems to be a confusion of type (normally existing at compile time) and class (normally existing at runtime)

What is the difference between a class and a type in Scala (and Java)?

What is the difference between Type and Class?

https://typelevel.org/blog/2017/02/13/more-types-than-classes.html

test[cls.type] doesn't make sense. x.type is a singleton type.

Maybe what @MateuszKubuszok proposed can help

trait A
class B extends A
class C

def test[T <: A](): Unit = ??? 

def test[T <: A](@unused clazz: Class[T]): Unit = test[T]()

val cls: Class[_ <: A] = Class.forName("B").asSubclass(classOf[A])
val cls1: Class[_] = classOf[C]

test(cls) // compiles
test(cls1) // doesn't compile, inferred type arguments [_$2] do not conform to method test's type parameter bounds [T <: A], type mismatch: found: Class[_$2] where type _$2, required: Class[T]

If you really want to substitute a class into a type-parameter position of a generic method then the problem is that T being a subtype of A or not, should be checked by the compiler at compile time while Class[_] object exists at runtime. So if you really want this you should find a way either to have the class earlier, at compile time, e.g. with a macro (using runtime reflection in macros: 1 2 3 4)

trait A
class B extends A
class C

val cls: Class[_ <: A] = Class.forName("B").asSubclass(classOf[A])
val cls1: Class[_] = classOf[C]

import scala.language.experimental.macros
import scala.reflect.macros.blackbox // libraryDependencies  = scalaOrganization.value % "scala-reflect" % scalaVersion.value
import scala.reflect.runtime.{currentMirror => rm}
import scala.reflect.runtime.{universe => ru}

def checkB(): Unit = macro Macros.checkBImpl
def checkC(): Unit = macro Macros.checkCImpl

class Macros(val c: blackbox.Context) {
  import c.universe._
  val cm = c.mirror
  def register(name: String): Unit =
    rm.runtimeClass(cm.staticClass(name).asInstanceOf[ru.ClassSymbol])

  def checkBImpl(): c.Tree = impl(cls)
  def checkCImpl(): c.Tree = impl(cls1)

  def impl(cls: Class[_]): c.Tree = {
    register(cls.getName) // side effect (otherwise even for B: type arguments [B] do not conform to method test's type parameter bounds [T <: A])
    q"App.test[${rm.classSymbol(cls).toType.asInstanceOf[Type]}]()"
  }
}
// in a different subproject

object App {
  def test[T <: A]() = ???
}

checkB() // compiles
checkC() // doesn't compile: type arguments [C] do not conform to method test's type parameter bounds [T <: A]

or vice versa, to postpone type checking till runtime, e.g. with runtime compilation (using reflective toolbox)

trait A
class B extends A
class C

object App {
  def test[T <: A]() = ???
}

val cls: Class[_ <: A] = Class.forName("B").asSubclass(classOf[A])
val cls1 = classOf[C]

import scala.reflect.runtime.{currentMirror => rm}
import scala.reflect.runtime.universe.Quasiquote
import scala.tools.reflect.ToolBox // libraryDependencies  = scalaOrganization.value % "scala-compiler" % scalaVersion.value
val tb = rm.mkToolBox()
def check(cls: Class[_]): Unit = tb.typecheck(q"App.test[${rm.classSymbol(cls)}]()")
check(cls) // at runtime: ok
check(cls1) // at runtime: scala.tools.reflect.ToolBoxError: reflective typecheck has failed: type arguments [C] do not conform to method test's type parameter bounds [T <: A]

Besides tb.typecheck there are also tb.compile, tb.eval.

  • Related