Some Name
Some Name

Reputation: 9521

Using freshName as a parameter without explicitly specifying type

I'm trying to use freshName as a parameter name in the following macro:

I.

def test: Unit = macro implTst

def implTst(c: blackbox.Context): c.Expr[Unit] = {
  import c.universe._

  def withImplicitsM(exprs: List[c.Tree], expr: c.Tree): c.Tree =
    exprs match {
      case Nil =>
        expr
      case head :: tail =>
        //error here
        q"""$head.flatMap(implicit ${c.freshName()} => ${withImplicitsM(tail, expr)})"""

    }

  val exprsIo    = List(q"cats.effect.IO.apply(1)", q"cats.effect.IO.apply(2)")
  val resultTree = q"""println(${withImplicitsM(exprsIo, q"cats.effect.IO.apply(3)")}.unsafeRunSync())"""

  c.Expr[Unit](resultTree)
}

It throws compile error:

[error] Main.scala:25:9: exception during macro expansion: 
[error] java.lang.IllegalArgumentException: "fresh$macro$2" is not valid representation of a parameter, consider reformatting it into q"val $name: $T = $default" shape

II.

Replacing the freshname with hardcoded identifier makes it work:

def test: Unit = macro implTst

def implTst(c: blackbox.Context): c.Expr[Unit] = {
  import c.universe._

  def withImplicitsM(exprs: List[c.Tree], expr: c.Tree): c.Tree =
    exprs match {
      case Nil =>
        expr
      case head :: tail =>
        q"""$head.flatMap(implicit i => ${withImplicitsM(tail, expr)})"""

    }

  val exprsIo    = List(q"cats.effect.IO.apply(1)", q"cats.effect.IO.apply(2)")
  val resultTree = q"""println(${withImplicitsM(exprsIo, q"cats.effect.IO.apply(3)")}.unsafeRunSync())"""

  c.Expr[Unit](resultTree)
}

Is there a way to use implicit ${c.freshName()} without specifying the parameter type explicitly?

Upvotes: 2

Views: 90

Answers (1)

Some Name
Some Name

Reputation: 9521

Solution:

Use empty type explicitly with parameter definition.

def withImplicitsM(exprs: List[c.Tree], expr: c.Tree): c.Tree =
  exprs match {
    case Nil =>
      expr
    case head :: tail =>
      val emptyType = tq""
      val v         = q"implicit val ${TermName(c.freshName())}: $emptyType"
      q"""$head.flatMap($v => ${withImplicitsM(tail, expr)})"""

  }

How I figured that out:

I destructured a similar flatMap call and looked at how the paramter definition looked like:

  val flatmapExpression               = q"cats.effect.IO.apply(1).flatMap(implicit i => cats.effect.IO.apply(2))"
  val q"$foo($args)"                  = flatmapExpression
  val q"(..$params) => $body"         = args
  val q"$mods val $name: $tpt = $rhs" = params(0)
  println(mods)
  println(name)
  println(tpt)
  println(rhs)

Here is what was printed out:

Modifiers(implicit <param>, , Map())
i
<type ?>
<empty>

notice <type ?> which is an empty type.

Upvotes: 1

Related Questions