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
.