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).
Related
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.
Im using ArgonautShapeless to define some json codecs.
When I provide the type for my codec I get StackOverflowError's but If I leave the type off it works. How can I provide the type?
My understanding of the problem is that the implicit lookup from def of[A: DecodeJson] = implicitly[DecodeJson[A]] finds my definition on the same line implicit def fooCodec: DecodeJson[Foo] and thus is recursive so breaks.
Is there some other way that will allow me to provide the type? Ideally I want to have one object in my project where I define all of the codes and they may depend on each other.
import $ivy.`com.github.alexarchambault::argonaut-shapeless_6.2:1.2.0-M4`
import argonaut._, Argonaut._
case class Foo(a: Int)
object SomeCodecs {
import ArgonautShapeless._
// this doesnt work
implicit def fooCodec: DecodeJson[Foo] = DecodeJson.of[Foo]
}
import SomeCodecs._
"""{"a":1}""".decode[Foo]
java.lang.StackOverflowError
ammonite.$sess.cmd3$SomeCodecs$.fooCodec(cmd3.sc:3)
It works if I leave the type off.
object SomeCodecs {
import ArgonautShapeless._
// this works
implicit def fooCodec = DecodeJson.of[Foo]
}
import SomeCodecs._
"""{"a":1}""".decode[Foo]
res4: Either[Either[String, (String, CursorHistory)], Foo] = Right(Foo(1))
Thanks
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.
I would like to write a function like the following:
def printFieldOfClass(field: ???) =
println(field)
Suppose there is a case class definition such as case class A(id: String). It would then be possible to call printFieldOfClass(A.id) which would print the string "A.id". If we, however, try to call printFieldOfClass(A.idd), I would want the code not to compile. Is this even possible? And if so, what is the type of the parameter field?
Help is much appreciated!
EDIT: As there seems to be some confusion about what I am trying to do, let me clarify: I do not want to pass an instance of the case class, I much rather want to pass a reference to some field in a class definition. Also I do not want my function to be hard wired to any such class, it should work with all Scala classes, like so: printFieldOfClass(SomeClassTheFunctionDoesNotKnowAbout.someField) should either print "SomeClassTheFunctionDoesNotKnowAbout.someField" in case SomeClassTheFunctionDoesNotKnowAbout's definition specifies a field someField or, if the class has no such field, the call should not compile.
This isn't possible if printFieldOfClass is a normal method, as the comments explain. But you can make it a macro instead. It's basically a function which runs at compile-time, receives the syntax tree of its argument and generates a new tree to replace it. There are quite a few introductions to Scala macros, and writing another one in the answer doesn't make sense. But I don't advise trying it until you are very comfortable with Scala in general.
An example which does something close to what you want:
import scala.annotation.tailrec
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
object Macros {
def nameOfImpl(c: blackbox.Context)(x: c.Tree): c.Tree = {
import c.universe._
#tailrec def extract(x: c.Tree): String = x match {
case Ident(TermName(s)) => s
case Select(_, TermName(s)) => s
case Function(_, body) => extract(body)
case Block(_, expr) => extract(expr)
case Apply(func, _) => extract(func)
}
val name = extract(x)
q"$name"
}
def nameOfMemberImpl(c: blackbox.Context)(f: c.Tree): c.Tree = nameOfImpl(c)(f)
def nameOf(x: Any): String = macro nameOfImpl
def nameOf[T](f: T => Any): String = macro nameOfMemberImpl
}
//
// Sample usage:
//
val someVariable = ""
Macros.nameOf(someVariable) // "someVariable"
def someFunction(x: Int): String = ???
Macros.nameOf(someFunction _) // "someFunction"
case class SomeClass(someParam: String)
val myClass = SomeClass("")
Macros.nameOf(myClass.someParam) // "someParam"
// without having an instance of the class:
Macros.nameOf[SomeClass](_.someParam) // "someParam"
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.