Calling a macro from a macro - scala

I am trying to call a macro from a macro, but I'm doing something wrong. It looks approximately like this:
import play.api.libs.json._
import scala.reflect.macros.Context
import language.experimental.macros
object Extension {
def apply[A]: Format[A] = macro applyImpl[A]
def applyImpl[A: c.WeakTypeTag](c: Context): c.Expr[Format[A]] = {
import c.universe._
val aTpeW = c.weakTypeOf[A]
val aClazz = aTpeW.typeSymbol.asClass
if (!aClazz.isSealed) { // fall back to Json.format
val t = reify { Json.format[A] } .tree
return c.Expr[Format[A]](t)
}
???
}
}
In other words, based on some condition of the type of A, instead of generating a tree in my macro, I want to return the body of another macro (Json.format). But somehow this gets expanded already before using the macro. When I compile this, I get
[error] .../Extension.scala:47: No unapply function found
[error] val t = reify { Json.format[A] } .tree
[error] ^
Which means that format is already executed (it should not be). The format method is defined as
def format[A] = macro JsMacroImpl.formatImpl[A]

One needs to jump right into the macro body it seems:
if (!aClazz.isSealed) { // fall back to Json.format
return JsMacroImpl.formatImpl[A](c)
}
(IntelliJ had this red, so I thought it was wrong, but it actually compiles)

Alternatively you should be able to call the macro from a macro, when you put both macros in different compilation units (i.e. different projects). Scala cannot compile a macro and apply it in the same compilation run.

Related

How to use a `universe.Tree` created and type-checked in one Scala macro execution, in another macro execution?

In the JSON library jsfacile I was able to automatically derive type-classes for types with recursive type references thanx to a buffer where outer executions of a macro store instances of universe.Tree which are used later by inner executions of the same or other macro.
This works fine as long as the universe.Tree instance was not type-checked (with the Typers.typecheck method).
The problem is that this forces to type-check the same Tree instance more than one time: one time in the macro execution that created it (after having stored it in the buffer); and more times in each inner macro execution that needs it.
The intention of this question is to find out a way to share the Tree instance between macro executions after it was type-checked; in order to improve compilation speed.
I tried to wrap the type-checked Tree into a universe.Expr[Unit] and migrate it to the mirror of the macro execution that uses it by means of the Expr.in[U <: Universe](otherMirror: Mirror[U]): U # Expr[T] method.
But it fails with an internal error:
Internal error: unable to find the outer accessor symbol of class <Name of the class where the macro is expanded>
Any idea?
Generally, typechecking a tree manually and sharing the typed tree among different contexts is a bad idea. See the following example:
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
import scala.collection.mutable
object Macros {
val mtcCache = mutable.Map[whitebox.Context#Type, whitebox.Context#Tree]()
trait MyTypeclass[A]
object MyTypeclass {
implicit def materialize[A]: MyTypeclass[A] = macro materializeImpl[A]
def materializeImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val typA = weakTypeOf[A]
if (!mtcCache.contains(typA))
mtcCache += (typA -> c.typecheck(q"new MyTypeclass[$typA] {}"))
mtcCache(typA).asInstanceOf[Tree]
}
}
}
import Macros.MyTypeclass
object App { // Internal error: unable to find the outer accessor symbol of object App
class A { // Internal error: unable to find the outer accessor symbol of class A
class B { // Internal error: unable to find the outer accessor symbol of class A
class C {
implicitly[MyTypeclass[Int]] // new MyTypeclass[Int] {} is created and typechecked here
}
implicitly[MyTypeclass[Int]] // cached typed instance is inserted here, this is the reason of above error
}
implicitly[MyTypeclass[Int]] // cached typed instance is inserted here, this is the reason of above error
}
implicitly[MyTypeclass[Int]] // cached typed instance is inserted here, this is the reason of above error
}
Scala 2.13.3.
With implicitly we put in some places trees with incorrect symbol owner chain.
If you make A, B, C objects then errors disappear (so whether this prevents compilation depends on a luck).
Also if you remove c.typecheck then errors disappear.
Also if we return c.untypecheck(mtcCache(typA).asInstanceOf[Tree]) instead of mtcCache(typA).asInstanceOf[Tree] then errors disappear. But sometimes c.typecheck + c.untypecheck can damage a tree.
So you can try to put both untyped and typed versions of a tree to the cache if you need both but return the untyped one
type CTree = whitebox.Context#Tree
val mtcCache = mutable.Map[whitebox.Context#Type, (CTree, CTree)]()
trait MyTypeclass[A]
object MyTypeclass {
implicit def materialize[A]: MyTypeclass[A] = macro materializeImpl[A]
def materializeImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val typA = weakTypeOf[A]
val tree = q"new MyTypeclass[$typA] {}"
if (!mtcCache.contains(typA))
mtcCache += (typA -> (tree, c.typecheck(tree)))
mtcCache(typA)._1.asInstanceOf[Tree]
}
}
or if you need typechecking only to trigger the recursion then you can typecheck a tree, put the untyped tree to the cache and return the untyped one
val mtcCache = mutable.Map[whitebox.Context#Type, whitebox.Context#Tree]()
trait MyTypeclass[A]
object MyTypeclass {
implicit def materialize[A]: MyTypeclass[A] = macro materializeImpl[A]
def materializeImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val typA = weakTypeOf[A]
val tree = q"new MyTypeclass[$typA] {}"
c.typecheck(tree)
if (!mtcCache.contains(typA)) mtcCache += (typA -> tree)
mtcCache(typA).asInstanceOf[Tree]
}
}
Pull request: https://github.com/readren/json-facile/pull/1

Def macro - scala 2.13 - not found: value cond

I am trying to make working an example from the scala docs:
https://docs.scala-lang.org/overviews/macros/overview.html
I have two files:
object Main extends App {
println("Testing assert macro...")
val result = Asserts.assert(true, "abc")
}
and:
import scala.reflect.macros.blackbox.Context
import scala.language.experimental.macros
object Asserts {
val assertionsEnabled: Boolean = true
def assert(cond: Boolean, msg: Any): Unit = macro Asserts.assertImpl
private def raise(msg: Any) = throw new AssertionError(msg)
def assertImpl(c: Context)(cond: c.Expr[Boolean], msg: c.Expr[Any]) : c.Expr[Unit] = {
import c.universe._
if (assertionsEnabled)
c.Expr(q"if (!cond) raise(msg)")
else
c.Expr(q"()")
}
}
but I am getting an error:
Error:(8, 30) not found: value cond val result =
Asserts.assert(true, "abc")
any idea how to make it working ?
thanks!
Macros and main code must be in different subprojects
https://www.scala-sbt.org/1.x/docs/Macro-Projects.html
You missed dollar signs ($cond, $msg instead of cond, msg), otherwise while the macro is expanded inside Main you're trying to use absent local variables cond, msg rather than splice parameters cond, msg of the macro. Also it must be known inside Main that raise is Asserts.raise so either use import Asserts.raise in Main or fully qualified name in the quasiquote. Try
c.Expr(q"if (!$cond) Asserts.raise($msg)")
Also Asserts.raise must be accessible inside Main so remove private.

Robustness of pattern matching Trees with quasiquotes

I have a macro and part of that macro consists of replacing every call to a certain method with something else. To accomplish this I use a Transformer and try to match every Tree that enters the transform method against a quasiquote. When I write it like below, it seems to work.
package mypackage
object myobject {
implicit def mymethod[T](t: Option[T]): T = ???
}
object Macros {
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
def myMacro(c: Context)(expr: c.Tree): c.Tree = {
import c.universe._
val transformer = new Transformer {
private def doSomething(value: c.Tree): TermName = {
???
}
override def transform(tree: c.Tree) = tree match {
case q"mypackage.myobject.mymethod[..$_]($value)" =>
val result = doSomething(value)
q"$result"
case _ => super.transform(tree)
}
}
val transformed = transformer.transform(expr)
???
}
}
But I thought you should always use fully qualified names in macros or you could get into trouble. So I wrote it like q"_root_.mypackage.myobject.mymethod[..$_]($value)", but then it no longer matched and the calls to mymethod no longer got replaced.
I also looked at the suggestion in the scala docs to unquote symbols, but I couldn't get that to work either.
So my question is: will this code (with q"mypackage.myobject.mymethod[..$_]($value)") always replace all the calls to mymethod and never replace any other method calls? And if not, how can I make it more robust?
scala.reflect macros are non hygienic, so, theoretically, q"mypackage.myobject.mymethod[..$_]($value)" could be matched by someone else.
I'd suggest match that method with q"..$mods def $name[..$tparams](...$paramss): $tpeopt = $expr" (assuming that is definition, not declaration). You can add checks on name.
Another solution is to mark method with annotation, and remove it in macro phase.

Lifting string variable using Scala quasiquotes

This is a simplified version of the problem I am facing but the underlying issue remains.
After calling a macro, I want to generate case classes dynamically. I am able to retrieve parameters from macro call etc. The issue I am having is trying to use a string variable within quasiquotes. I essentially want to have the following:
def expand_impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val toGen = "case class Foo()"
val toReturn = c.Expr[Any](
q"$toGen"
)
toReturn
}
However, the case class is not generated. Now I know that if I change toGen to q"case class Foo()" it will work, however toGen is a string I will generate after some other processing which returns a string, so I can't do that.
Compiling it like this and manually looking at the the value of toReturn I get the following:
Expr[Any]("case class Foo()")
The string toGen is simply pasted in, along the quotes, meaning the case class is not generated.
I have looked for similar issues but can't find this example anywhere. How can I unquote the double quotes of a string variable within quasiquotes?
There is a parse method defined on Context. It returns a Tree, and because trees can be interpolated in quasiquotes, you can very easily mix and match parsing with quasiquoting.
By example:
scala> :paste
// Entering paste mode (ctrl-D to finish)
import scala.reflect.macros.whitebox.Context
import scala.language.experimental.macros
def test_impl(c: Context)(): c.Tree = {
import c.universe._
val tree = c.parse("""println(2)""")
q"println(1); $tree; println(3)"
}
def test(): Unit = macro test_impl
// Exiting paste mode, now interpreting.
import scala.reflect.macros.whitebox.Context
import scala.language.experimental.macros
test_impl: (c: scala.reflect.macros.whitebox.Context)()c.Tree
defined term macro test: ()Unit
scala> test()
1
2
3
In this example I defined a def macro, but it should work just as well with macro annotations (as in your case).

Is it possible to define companion classes/modules in the Scala interpreter?

It's often convenient to test things out in the Scala interpreter. However, one issue I run into is that I have to restructure code that uses implicit conversions because defining an object with the same name as an existing class does not make it a companion module in the REPL. As a result, I can't be confident my code will still work when I translate back to "real source".
Is there a way to define companions in the REPL? Maybe something along the lines of
bigblock {
class A
object A {
implicit def strToA(s: String): A = // ...
}
}
such that
val v: A = "apple"
will compile.
That's close:
object ABlock {
class A
object A {
implicit def strToA(s: String): A = // ...
}
}
import ABlock._
Or, the following, if you put everything on one line:
class A; object A { implicit def strToA(s: String): A = // ... } }
...though either way you'll still need to import the implicit conversion to make the following work as you requested:
import ABlock.A.strToA // for the form with the enclosing object
import A.strToA // for the one-line form without an enclosing object
val v: A = "apple"
The reason you need to do this is that every line you enter at the REPL is enclosed in an object and each subsequent one is nested within the immediately preceding one. This is done so you can do things like the following without getting redefinition errors:
val a = 5
val a = "five"
(Effectively, the second definition of a here shadows the first.)
With more recent versions use can use the :paste command.