Howto model named parameters in method invocations with Scala macros?

丶灬走出姿态 提交于 2019-11-27 13:10:58

Here's an implementation that's also a little more generic:

import scala.language.experimental.macros

object WithIdExample {
  import scala.reflect.macros.Context

  def withId[T, I](entity: T, id: I): T = macro withIdImpl[T, I]

  def withIdImpl[T: c.WeakTypeTag, I: c.WeakTypeTag](c: Context)(
    entity: c.Expr[T], id: c.Expr[I]
  ): c.Expr[T] = {
    import c.universe._

    val tree = reify(entity.splice).tree
    val copy = entity.actualType.member(newTermName("copy"))

    val params = copy match {
      case s: MethodSymbol if (s.paramss.nonEmpty) => s.paramss.head
      case _ => c.abort(c.enclosingPosition, "No eligible copy method!")
    }

    c.Expr[T](Apply(
      Select(tree, copy),
      params.map {
        case p if p.name.decoded == "id" => reify(id.splice).tree
        case p => Select(tree, p.name)
      }
    ))
  }
}

It'll work on any case class with a member named id, no matter what its type is:

scala> case class Bar(arg0: String, id: Option[Int])
defined class Bar

scala> case class Foo(x: Double, y: String, id: Int)
defined class Foo

scala> WithIdExample.withId(Bar("bar", None), Some(2))
res0: Bar = Bar(bar,Some(2))

scala> WithIdExample.withId(Foo(0.0, "foo", 1), 2)
res1: Foo = Foo(0.0,foo,2)

If the case class doesn't have an id member, withId will compile—it just won't do anything. If you want a compile error in that case, you can add an extra condition to the match on copy.


Edit: As Eugene Burmako just pointed out on Twitter, you can write this a little more naturally using AssignOrNamedArg at the end:

c.Expr[T](Apply(
  Select(tree, copy),
  AssignOrNamedArg(Ident("id"), reify(id.splice).tree) :: Nil
))

This version won't compile if the case class doesn't have an id member, but that's more likely to be the desired behavior anyway.

This is the solution of Travis where all parts are put together:

import scala.language.experimental.macros

object WithIdExample {

  import scala.reflect.macros.Context

  def withId[T, I](entity: T, id: I): T = macro withIdImpl[T, I]

  def withIdImpl[T: c.WeakTypeTag, I: c.WeakTypeTag](c: Context)(
    entity: c.Expr[T], id: c.Expr[I]
  ): c.Expr[T] = {

    import c.universe._

    val tree = reify(entity.splice).tree
    val copy = entity.actualType.member(newTermName("copy"))

    copy match {
      case s: MethodSymbol if (s.paramss.flatten.map(_.name).contains(
        newTermName("id")
      )) => c.Expr[T](
        Apply(
          Select(tree, copy),
          AssignOrNamedArg(Ident("id"), reify(id.splice).tree) :: Nil))
      case _ => c.abort(c.enclosingPosition, "No eligible copy method!")
    }

  }

}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!