0

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?

Oleg
  • 899
  • 1
  • 8
  • 22

1 Answers1

2

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, 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

  // weird :)
  def register(name: String): Unit =
    rm.runtimeClass(cm.staticClass(name).asInstanceOf[ru.ClassSymbol])

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

  def impl(cls: Class[_]): 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, universe => ru}
import ru.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.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Thanks for this great answer. The meta problem is I want to parse and read protos from a big backup using `scalaPB`. It requires to summon `GeneratedMessageCompanion[A <: GeneratedMessage]` instance and invoke `parseFrom` method for specific contract. – Oleg Dec 07 '22 at 07:29
  • @Oleg Interesting. It's not clear for me though whether you actually need both `Class[_]` objects and generic methods (with `A <: ...`). Maybe you can avoid `Class[_]`. Maybe you'll have more conventional Scala then (using just macros or just runtime-reflection but not mixing macros with runtime reflection or runtime reflection with runtime compilation). If you still have questions maybe you could create [MCVE](https://stackoverflow.com/help/minimal-reproducible-example) in your ScalaPB setting (in this question or maybe better in a new one) and we'll see. – Dmytro Mitin Dec 07 '22 at 11:57
  • @Oleg Do MateuszKubuszok's approach or one of mine work for you? – Dmytro Mitin Dec 07 '22 at 11:58