测试c.universe.Type是否可分配给宏

时间:2015-06-30 14:34:53

标签: scala scala-macros

我试图编写一个宏,它接受带有java bean接口和case类的类,并创建一对用于在它们之间进行映射的方法。

我试图检查每个属性的类型是否匹配,但java bean中的类型是例如java.lang.Long和case类中的类型是scala.Long。

我的问题是,鉴于这些2的c.universe.Type对象,有没有办法测试它们之间是否存在隐式转换?即测试我是否可以将一个传递给期望另一个的方法。

1 个答案:

答案 0 :(得分:0)

如果要检查是否存在隐式转换,可以使用c.inferImplicitView

概念证明:

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)
           ^

如果您没有value,显然如果您执行c.inferImplicitView(EmptyTree, weakTypeOf[T], weakTypeOf[S]),它也会有效。

更接近实际问题的更复杂的例子:

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)
                                     ^