I am trying to use Java reflection to replace the value for an object's field at runtime, but in a type-safe way.
Let's assume we have the following object.
import cats.Eval
object Foo {
val bar: Eval[Int] = Eval.later(throw new Exception)
}
We want to change the value for Foo.bar
at runtime. We can easily define the following method using normal Java reflection:
import java.lang.reflect.Field
def usingJavaReflection[T](targetObject: AnyRef, fieldName: String)(newValue: T): Unit = {
val field = targetObject.getClass.getDeclaredField(fieldName)
field.setAccessible(true)
field.set(targetObject, newValue)
}
// usage
usingJavaReflection(Foo, "bar")(Eval.now(42))
Now the question becomes: how to do that in a more type-safe way?
def usingJavaReflectionButTypesafe[T](field: T)(newValue: T): Unit = ???
// intended usage
usingJavaReflectionButTypesafe(Foo.bar)(Eval.now(42))
This means being able to do a few things:
Foo.bar
should not be evaluatedFoo.bar
should be broken down into a target object Foo
and a field "bar"
Foo.bar
should be the same as for newValue
Bonus question: If this is possible, how to make it work for Scala 2.11 and 2.12 and 2.13. I just cannot wrap my head around which Scala macros techniques are safe to use in which versions of Scala, and how.
Try
def usingJavaReflectionButTypesafe[T](field: => T)(newValue: T): Unit = macro impl[T]
def impl[T: c.WeakTypeTag](c: blackbox.Context)(field: c.Tree)(newValue: c.Tree): c.Tree = {
import c.universe._
field match {
case q"$expr.$tname" =>
q"usingJavaReflection($expr, ${tname.toString})($newValue)"
}
}