giiita
giiita

Reputation: 189

Within a function passed to a scala macro, I cannot reference a variable in scope

Cannot compile even if a function that handles function arguments is passed to macro. A sample is shown below.

trait Generated[Z] {
  def deserialize[A](t: A): Z
}

def from[A, Z](apl: A => Z): Generated[Z] = macro GeneratorMacro.from[A, Z]

class GeneratorMacro(val c: blackbox.Context) {
  import c.universe._
  def from[A: c.WeakTypeTag, Z: c.WeakTypeTag](apl: c.Expr[A => Z]): c.Expr[Generated[Z]] = {
    reify {
      new Generated[Z] {
        def deserialize[A](t: A): Z = {
          apl.splice.apply(t)
        }
      }
    }
  }
}
object Generation {
  def apply(input: String): Generated[Int] = {
    Generator.from[Option[String], Int] {
      case Some(i) => input.toInt + i.toInt // compilation failed
      case None => 0
    }
  }
}

An error occurs at this time.

Error: scalac: Error while emitting Generation.scala
value input

Isn't the class recompiled with the macro expanded? If recompiled with inline expansion, no compilation error should occur.

object Generation {
  def apply(input: String): Generated[Int] = {
    new Generated[Int] {
      def deserialize(t: String): Int = {
        {
          case Some(i) => input.toInt + i.toInt // input should be visible
          case None => 0
        }.apply(t)
      }
    }
  }
}

What is going on and how to avoid it.

Upvotes: 0

Views: 712

Answers (2)

Dmytro Mitin
Dmytro Mitin

Reputation: 51648

The first error I have is

Warning:scalac: {
  final class $anon extends App.this.Generated[Int] {
    def <init>() = {
      super.<init>();
      ()
    };
    def deserialize(t: Option[String]): Int = ((x0$1: Option[String]) => x0$1 match {
  case (value: String)Some[String]((i @ _)) => scala.Predef.augmentString(input).toInt.+(scala.Predef.augmentString(i).toInt)
  case scala.None => 0
}).apply(t)
  };
  new $anon()
}

Error:(6, 43) object creation impossible. Missing implementation for:
  def deserialize[A](t: A): Int // inherited from trait Generated
      Generator.from[Option[String], Int] {

Obviously this is because you define

reify {
  new Generated[Z] {
    def deserialize(t: A): Z = {
      ...

instead of def deserialize[A](t: A): Z (@OlegPyzhcov pointed this out in the comments).

Regarding your error Error while emitting ... the thing is that

{
  case Some(i: String) => input.toInt + i.toInt
  case None => 0
}

has type not ... => ... i.e. Function1[..., ...] but actually PartialFunction[..., ...].

Try either

object Generator {
  def from[Z](apl: PartialFunction[Any, Z]): Generated[Z] = macro GeneratorMacro.from[Z]
}

class GeneratorMacro(val c: blackbox.Context) {
  import c.universe._
  def from[Z: c.WeakTypeTag](apl: c.Expr[Any => Z]): c.Expr[Generated[Z]] = {
    reify {
      new Generated[Z] {
        def deserialize[A](t: A): Z = {
          apl.splice.apply(t)
        }
      }
    }
  }
}

Generator.from[Int]({
  case Some(i: String) => input.toInt + i.toInt
  case None => 0
})

or

object Generator {
  def from[Z](apl: Any => Z): Generated[Z] = macro GeneratorMacro.from[Z]
}

class GeneratorMacro(val c: blackbox.Context) {
  import c.universe._
  def from[Z: c.WeakTypeTag](apl: c.Expr[Any => Z]): c.Expr[Generated[Z]] = {
    reify {
      new Generated[Z] {
        def deserialize[A](t: A): Z = {
          apl.splice.apply(t)
        }
      }
    }
  }
}

Generator.from[Int](new (Any => Int) {
  override def apply(x: Any): Int = x match {
    case Some(i: String) => input.toInt + i.toInt
    case None => 0
  }
})

Upvotes: 1

cbley
cbley

Reputation: 4608

This seems to be impossible, AFAICS. The compiler creates an anonymous class for your code, but it will not capture the lexical context of the macro call inside it.

The code looks a bit like this after the lambdalift phase:

def apply(input: String): Generated[Int] = ({
      new <$anon: Generated>()
    }: Generated);

...

final class $anon extends Object with Generated {
  def deserialize(t: Option): Int = {
    ... // <- your code is here !!
  };
}

Of course, the code has no access to the input variable at this place.

This might be a bug in the Scala compiler...

Upvotes: 1

Related Questions