9

I'm attempting to write a macro which takes a class with a java bean interface and a case class and creates a pair of methods for mapping between them.

I am attempting to check that the types match for each property, however the types in the java bean are e.g. java.lang.Long and the types in the case class are scala.Long.

My question is, given the c.universe.Type object for these 2, is there a way to test if there are implicit conversions between them? i.e. to test if I can pass one to a method which expects the other.

Angelo Genovese
  • 3,398
  • 17
  • 23

1 Answers1

0

If you want to check whether an implicit conversion exists, you can use c.inferImplicitView.

Proof of concept:

scala> :paste
// Entering paste mode (ctrl-D to finish)

import scala.reflect.macros.blackbox.Context
import scala.language.experimental.macros

def test[T,S,R](value: T, func: S => R): R = macro Macro.myMacro[T,S,R]

object Macro {
  def myMacro[T: c.WeakTypeTag,S: c.WeakTypeTag,R](c: Context)(value: c.Tree, func: c.Tree): c.Tree = {
    import c.universe._
    val view = c.inferImplicitView(value, weakTypeOf[T], weakTypeOf[S])
    if (view == EmptyTree) 
      c.abort(c.enclosingPosition, "Cannot apply function")
    else
      q"$func($value)"
  }
}

// Exiting paste mode, now interpreting.

import scala.reflect.macros.blackbox.Context
import scala.language.experimental.macros
defined term macro test: [T, S, R](value: T, func: S => R)R
defined object Macro

scala> test(3L, (l: java.lang.Long) => l.toString)
res20: String = 3

scala> test(3L, (l: java.lang.Integer) => l.toString)
<console>:23: error: Cannot apply function
       test(3L, (l: java.lang.Integer) => l.toString)
           ^

If you don't have a value, apparently it also works if you do c.inferImplicitView(EmptyTree, weakTypeOf[T], weakTypeOf[S]).


A more complex example closer to the Actual Problem:

scala> :paste
// Entering paste mode (ctrl-D to finish)

import scala.reflect.macros.blackbox.Context
import scala.language.experimental.macros

def mapBetween[JB,CC](jb: JB): CC = macro Macro.myMacro[JB,CC]

object Macro {
  def myMacro[JB: c.WeakTypeTag, CC: c.WeakTypeTag](c: Context)(jb: c.Tree): c.Tree = {
    import c.universe._
    val jbTpe = weakTypeOf[JB]
    val ccTpe = weakTypeOf[CC]
    val constructor = ccTpe.members.filter(m => 
      m.isConstructor && m.name != TermName("$init$")
    ).head.asMethod
    if(constructor.paramLists.size != 1 || constructor.paramLists.head.size != 1)
      c.abort(c.enclosingPosition, "not supported :(")
    val ccParam = constructor.paramLists.head.head
    val ccParamType = ccParam.typeSignature
    val ccParamName = ccParam.name.toString

    val jbGetter = jbTpe.member(TermName(s"get${ccParamName.head.toUpper + ccParamName.tail}"))
    val getterType = jbGetter.asMethod.returnType

    val view = c.inferImplicitView(EmptyTree, getterType, ccParamType)
    if (view == EmptyTree) 
      c.abort(c.enclosingPosition, "Cannot apply function")
    else
      q"new ${ccTpe.typeSymbol.name.toTypeName}($jb.${jbGetter.name.toTermName})"
  }
}

// Exiting paste mode, now interpreting.

import scala.reflect.macros.blackbox.Context
import scala.language.experimental.macros
defined term macro mapBetween: [JB, CC](jb: JB)CC
defined object Macro

scala> case class CaseClass(foo: Int)
defined class CaseClass

scala> class JavaBean{ def getFoo(): java.lang.Integer = 42 }
defined class JavaBean

scala> mapBetween[JavaBean,CaseClass](new JavaBean)
res0: CaseClass = CaseClass(42)

scala> case class CaseClass(foo: Int)
defined class CaseClass

scala> class JavaBean{ def getFoo(): java.lang.Double = 42.0 }
defined class JavaBean

scala> mapBetween[JavaBean,CaseClass](new JavaBean)
<console>:27: error: Cannot apply function
       mapBetween[JavaBean,CaseClass](new JavaBean)
                                     ^
Jasper-M
  • 14,966
  • 2
  • 26
  • 37
  • This works for parameters to the macro as such, but it won't work for reflected members of the classes under inspection. – Marcin Jan 05 '17 at 22:38
  • @Marcin Why not? The question says you have `Type` objects representing all the types that are involved. `weakTypeOf[T]` is just a way of getting a `Type` object. – Jasper-M Jan 05 '17 at 22:42
  • No it doesn't. You're choosing to solve an easier problem. If you think I'm wrong, prove me wrong by using this technique to solve the actual problem stated - comparing types of members inside classes. – Marcin Jan 06 '17 at 01:25
  • @Marcin I chose to solve a problem that is **easier to reproduce** and is literally stated in the question: *given the c.universe.Type object for these 2, is there a way to test if there are implicit conversions between them?*. It is not easy to reproduce the complete and exact original problem without any code provided in the question. But if I have some time I will try. – Jasper-M Jan 06 '17 at 07:03
  • By the way, I have a feeling *your* problem doesn't correspond 100% to question asked here. You may also want to have a look [here](http://stackoverflow.com/questions/19379436/cant-access-parents-members-while-dealing-with-macro-annotations) and [here](http://stackoverflow.com/questions/19791686/type-parameters-on-scala-macro-annotations). – Jasper-M Jan 06 '17 at 09:27
  • In so doing, you were ignoring **java bean interface and a case class and creates a pair of methods for mapping between them.** I'll try out what you have later. Thank you for updating. – Marcin Jan 06 '17 at 18:18