【问题标题】:Test if c.universe.Type is assignable to another type in a macro测试 c.universe.Type 是否可分配给宏中的另一种类型
【发布时间】:2024-01-13 03:58:01
【问题描述】:

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

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

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

【问题讨论】:

  • 您找到解决方案了吗?
  • 很遗憾没有,我最终得到了基本类型的硬编码映射(那些是我真正担心的那些)。
  • 是的,我也在这样做。很伤心;(

标签: scala scala-macros


【解决方案1】:

如果要检查是否存在隐式转换,可以使用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)
                                     ^

【讨论】:

  • 这适用于宏本身的参数,但不适用于被检查类的反射成员。
  • @Marcin 为什么不呢?问题是你有 Type 代表所有涉及的类型的对象。 weakTypeOf[T] 只是获取Type 对象的一种方式。
  • 不,它没有。你选择解决一个更简单的问题。如果您认为我错了,请通过使用这种技术来解决所陈述的实际问题来证明我错了 - 比较类中成员的类型。
  • @Marcin 我选择解决一个更容易重现的问题,并且在问题中字面意思是:给定这 2 个的 c.universe.Type 对象,有没有办法测试它们之间是否存在隐式转换?.如果问题中没有提供任何代码,要重现完整且准确的原始问题并不容易。但如果我有时间我会试试的。
  • 顺便说一句,我感觉您的问题与此处提出的问题 100% 不相符。您可能还想看看herehere
最近更新 更多