Using Scala Macro Annotation in Intellij - scala

I am trying to use scala macro annotation to simply print return value from a method. I am using Intellij 2017. Here is the code:
class PrintResult extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro PrintResult.impl
}
object PrintResult {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val result = {
annottees.map(_.tree).toList match {
case q"$mods def $methodName(...$args): $returnType = { ..$body }" :: Nil => {
q"""$mods def $methodName(...$args): $returnType = {
val res = {..$body}
println($res)
res
}"""
}
case _ => c.abort(c.enclosingPosition, "Annotation #PrintResult can be used only with methods")
}
}
c.Expr[Any](result)
}
}
#PrintResult
object Test extends App {
def add(a: Int, b: Int): Int = {
a+b
}
}
I added this config to sbt:
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)
I also added SBT Compile to Intellij Run Configuration.
I am getting this error:
macro annotation could not be expanded (the most common reason for that is that you need to enable the macro paradise plugin; another possibility is that you try to use macro annotation in the same compilation run that defines it)

To quote the error message:
another possibility is that you try to use macro annotation in the same compilation run that defines it
object Test must be in a different subproject or in src/test/scala (when PrintResult is in src/main/scala).

Related

Extract ClassSymbols for method's parameter bounds

I'm trying to extract ClassSymbols for all type parameters' bounds of a method.
The "solution" I came up with:
Macro annotation implementation:
#compileTimeOnly("Compile-time only annotation")
class classSyms extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro impl
}
object classSyms {
def impl(c: whitebox.Context)(annottees: c.Tree*) = {
import c.universe._
annottees.toList foreach {
case q"$mods def $templatename[..$typeparams](...$paramss): $tpt = $body" =>
typeparams foreach {
case q"$mods type $name[..$tparams] >: $low <: $high" =>
if (!high.isEmpty) {
//requires FQCN, does not work with imported names
val classSymbol = c.mirror.staticClass(high.toString)
println(classSymbol)
}
}
}
q"..$annottees"
}
}
Example:
package pack.age
trait Test
package another.pack.age
import pack.age._
trait Bar{
#classSyms
def foo[M <: pack.age.Test, T](): Unit //works ok
#classSyms
def baz[M <: Test, T](): Unit //throws scala.ScalaReflectionException: class Test not found.
}
The problem is such requirements of specifying fully-qualified class name as a parameter bound does not make this macro implementation very useful (no one wants to write this long fqcn stuff, especially if the name is imported).
Is it possible to extract ClassSymbol by an imported name?
Try to use c.typecheck.
Replace
val classSymbol = c.mirror.staticClass(high.toString)
with
val classSymbol = c.typecheck(tq"$high", mode = c.TYPEmode).symbol.asClass

Scaladoc generation fails when referencing methods generated by annotation macros

I have two classes, call them Foo and Fizz. Foo uses an annotation macro called expand to make aliases of some of its methods (the actual implementation does a little more than creating aliases, but the simple version still exhibits the problem that follows). For simplicity, let's say the expand macro simply takes all of the methods within the annotated class, and makes a copy of them, appending "Copy" to the end of the method name, then forwarding the call to the original method.
My problem is that if I use the expand macro on Foo, which creates a copy of a method Foo#bar called barCopy, when barCopy is called within another class, Fizz, everything compiles but scaladoc generation fails, as such:
[error] ../src/main/scala/Foo.scala:11: value barCopy is not a member of Foo
[error] def str = foo.barCopy("hey")
[error] ^
[info] No documentation generated with unsuccessful compiler run
If I remove the scaladoc that tags the method being copied (Foo#bar), the sbt doc command works again. It's as if the scaladoc generator invokes an early phase of the compiler without using the macro paradise plugin that's enabled, yet somehow it works if the docs are removed from the offending method.
This is the expand macro:
import scala.annotation.{ StaticAnnotation, compileTimeOnly }
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
#compileTimeOnly("You must enable the macro paradise plugin.")
class expand extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro Impl.impl
}
object Impl {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val result = annottees map (_.tree) match {
case (classDef #
q"""
$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents {
$self => ..$stats
}
""") :: _ =>
val copies = for {
q"def $tname[..$tparams](...$paramss): $tpt = $expr" <- stats
ident = TermName(tname.toString + "Copy")
} yield {
val paramSymbols = paramss.map(_.map(_.name))
q"def $ident[..$tparams](...$paramss): $tpt = $tname(...$paramSymbols)"
}
q"""
$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self =>
..$stats
..$copies
}
"""
case _ => c.abort(c.enclosingPosition, "Invalid annotation target: not a class")
}
c.Expr[Any](result)
}
}
And the classes, which exist in a separate project:
/** This is a class that will have some methods copied. */
#expand class Foo {
/** Remove this scaladoc comment, and `sbt doc` will run just fine! */
def bar(value: String) = value
}
/** Another class. */
class Fizz(foo: Foo) {
/** More scaladoc, nothing wrong here. */
def str = foo.barCopy("hey")
}
This seems like a bug, or perhaps a missing feature, but is there a way to generate scaladoc for the above classes without removing docs from copied methods? I've tried this with both Scala 2.11.8 and 2.12.1. This is a simple sbt project that demonstrates the issue I'm having.
This is a bug in Scala, still present in 2.13. This gist of the issue is that when compiling for Scaladoc (as with sbt doc), the compiler introduces extra DocDef AST nodes for holding comments. Those are not matched by the quasiquote pattern. Even worse, they aren't even visible from the scala-reflect API.
Here is an excerpt from a comment by #driuzz explaining the situation on a similar issue in simulacrum:
[...] During normal compilation methods are available as DefDef type even when they have scaladoc comment which is just ignored.
But during sbt doc compiler generates a little different AST. Every method that has scaladoc comment is described as DocDef(comment, DefDef(...)) which causes that macro isn't recognizing them at all [...]
The fix that #driuzz implemented is here. The idea is to try casting the scala-reflect trees into their Scala compiler representations. For the code from the question, this means defining some unwrapDocDef to help remove the docstrings from the methods.
val result = annottees map (_.tree) match {
case (classDef #
q"""
$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents {
$self => ..$stats
}
""") :: _ =>
// If the outer layer of the Tree is a `DocDef`, peel it back
val unwrapDocDef = (t: Tree) => {
import scala.tools.nsc.ast.Trees
if (t.isInstanceOf[Trees#DocDef]) {
t.asInstanceOf[Trees#DocDef].definition.asInstanceOf[Tree]
} else {
t
}
}
val copies = for {
q"def $tname[..$tparams](...$paramss): $tpt = $expr" <- stats.map(unwrapDocDef)
ident = TermName(tname.toString + "Copy")
} yield {
val paramSymbols = paramss.map(_.map(_.name))
q"def $ident[..$tparams](...$paramss): $tpt = $tname(...$paramSymbols)"
}
q"""
$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self =>
..$stats
..$copies
}
"""
case _ => c.abort(c.enclosingPosition, "Invalid annotation target: not a class")
}
Of course, since this imports something from the Scala compiler, the SBT definition of the macro project has to change:
lazy val macros = (project in file("macros")).settings(
name := "macros",
libraryDependencies ++= Seq(
"org.scala-lang" % "scala-reflect" % scalaV,
"org.scala-lang" % "scala-compiler" % scalaV // new
)
).settings(commonSettings: _*)

Getting Parameters from Scala Macro Annotation

So I have an annotation on a function (DefDef). This annotation has parameters.
However, I am confused on how to get the parameters from the constructor.
Usage example:
class TestMacro {
#Foo(true)
def foo(): String = ""
foo
}
Here's the code for the annotation:
class Foo(b: Boolean) extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro Foo.impl
}
object Foo {
def impl(c: whitebox.Context)(annottees: c.Tree*): c.Expr[Any] = {
import c.universe._
//how do I get value of `b` here???
c.abort(c.enclosingPosition, "message")
}
}
What about this:
val b: Boolean = c.prefix.tree match {
case q"new Foo($b)" => c.eval[Boolean](c.Expr(b))
}
For sake of completeness this is the full source:
import scala.reflect.macros.Context
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
import scala.annotation.compileTimeOnly
import scala.reflect.api.Trees
import scala.reflect.runtime.universe._
class Foo(b: Boolean) extends StaticAnnotation {
def macroTransform(annottees: Any*) :Any = macro FooMacro.impl
}
object FooMacro {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val b: Boolean = c.prefix.tree match {
case q"new Foo($b)" => c.eval[Boolean](c.Expr(b))
}
c.abort(c.enclosingPosition, "message")
}
}
This is an answer that shows a variation on Federico's technique, if you want to use a static annotation that has optional named arguments. In that case, you need to consider the possible invocation expressions in the case matching statement. An optional argument might be explicitly named, it might be given without a name, or it might be not present. Each of these shows up at compile time as a separate pattern in c.prefix.tree, as shown below.
#compileTimeOnly("Must enable the Scala macro paradise compiler plugin to expand static annotations")
class noop(arg1: Int, arg2: Int = 0) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro AnnotationMacros.noop
}
class AnnotationMacros(val c: whitebox.Context) {
import c.universe._
// an annotation that doesn't do anything:
def noop(annottees: c.Expr[Any]*): c.Expr[Any] = {
// cases for handling optional arguments
val (arg1q, arg2q) = c.prefix.tree match {
case q"new noop($arg1, arg2 = $arg2)" => (arg1, arg2) // user gave named arg2
case q"new noop($arg1, $arg2)" => (arg1, arg2) // arg2 without name
case q"new noop($arg1)" => (arg1, q"0") // arg2 defaulted
case _ => c.abort(c.enclosingPosition, "unexpected annotation pattern!")
}
// print out the values
println(s"arg1= ${evalTree[Int](arg1q)} arg2= ${evalTree[Int](arg2q)}")
// just return the original annotee:
annottees.length match {
case 1 => c.Expr(q"{ ${annottees(0)} }")
case _ => c.abort(c.enclosingPosition, "Only one annottee!")
}
}
def evalTree[T](tree: Tree) = c.eval(c.Expr[T](c.untypecheck(tree.duplicate)))
}
Here is an example invocation that names arg2, and so it will match the first pattern - case q"new noop($arg1, arg2 = $arg2)" - above:
object demo {
// I will match this pattern: case q"new noop($arg1, arg2 = $arg2)"
#noop(1, arg2 = 2)
trait someDeclarationToAnnotate
}
Note also that because of the way these patterns work, you have to explicitly supply the default argument value inside the macro code, which is unfortunately a bit hacky, but the final evaluated class is not available to you.
As an experiment, I tried actually creating the class by calling evalTree[scope.of.class.noop](c.prefix.tree), but the Scala compiler throws an error because it considered that a reference to the annotation inside the annotation macro code, which is illegal.

scala macros generating implicits

I am trying to generate some implicits via a macro -the condensed version of the macro looks like this:
object Implicits {
def generate(c:Context):c.Expr[Unit]={
import c.universe._
c.Expr[Unit] {
q"""
object Dud{
implicit val p:java.io.File = new java.io.File("/tmp")
def toString():String ={ "Dud here" }
}
import Dud._
"""
}
}
}
I am using the macro:
object ImplicitTest extends App {
def genImplicits = macro Implicits.generate
genImplicits
val f: File = implicitly[File]
println(f)
}
The test bails out complaining that
ImplicitTest.scala could not find implicit value for parameter e: java.io.File
[error] val f: File = implicitly[File]
[error] ^
What am I doing wrong with this macro?
Based on Travis's answer (Thank you) I wrote the macro using macro annotation: Here is the code if it helps someone else -it's proof of concept
#compileTimeOnly("enable macro paradise to expand macro annotations")
class defaultFileMacro extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro DefaultMacro.impl
}
object DefaultMacro {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val inputs:List[Tree] = annottees.map(_.tree).toList
val tree= inputs(0)
val q"val $list:List[$t]= $files" = tree
print(show(q"""implicit val fl1:$t = $files(0)"""))
c.Expr[Any] {
q"""
implicit val fl1:$t = $files(0)
"""
}
}
}
Usage:
object ImplicitTest extends App {
def findDefaultFile() = {
#defaultFileMacro val list: List[File] = List(new File("/tmp"))
val f: File = implicitly[File]
println(f)
}
findDefaultFile()
}
run
> run-main ImplicitTest
[info] Running ImplicitTest
/tmp
The call to the macro method will be expanded to something like this (I generated this text by printing the expression before returning it, and after adding an override to the toString definition):
{
object Dud extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
implicit val p: java.io.File = new java.io.File("/tmp");
override def toString(): String = "Dud here"
};
import Dud._;
()
}
Note that this doesn't bring anything into scope in the body of ImplicitTest. Both the Dud object and the import are inside a block, and neither are available by the time you get to the val f: File = implicitly[File] line.
You can write an implicit macro method, or you can write a macro annotation that you could apply to ImplicitTest that would add both Dud and the import, but you can't introduce new objects or imports into scope with a def macro.

Scala macro expansion of class with companion: type not found

I'm trying to define a macro annotation on a case class that expands to a class with a companion object, but I'm running in some problems.
I can create an object with the same name as the class, and define methods on it.
But when I try to use the typename of the class as a return or argument type of a method, I get an "not found: type "
class term extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro termMacro.impl
}
object termMacro {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val inputs = annottees.map(_.tree).toList
val (cls, comp) = annottees.map(_.tree) match {
case cd#q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$_ } with ..$_ { $self => ..$stats }" :: tail =>
val paramnames = paramss.head.map {
case q"$mods val $name: $tpt = $default" => name
}
val ctorparams = List(paramss.head ++ Seq(
q"val position: SourcePosition = (0,0)"
))
val ctorname = TermName(tpname.decodedName.toString)
//val clstype =
(
q"""
import ast.Term
case class $tpname(...$ctorparams) extends Term { $self =>
def children() = {
List(..$paramnames)
}
..$stats
}
""",
q"""
object $ctorname {
def unapply(t: $tpname): Option[(Int, Int)] = {
Some(Tuple2(3, 4))
}
}
"""
)
case head :: tail =>
c.abort(c.enclosingPosition, s"The #Term annotation is for case classes, found $head")
}
c.error(c.enclosingPosition, showRaw(cls) + "\n" + showRaw(comp))
c.Expr[Any](Block(List(cls, comp), Literal(Constant(()))))
}
}
e.g. usage would be: #term case class A(x: Term) extends Term, and would give me the compiler error "not found: type A' at location #term in that definition.
I've narrowed the location down to the unapply method in the object definition.
Any help is appreciated.
I'm very much new to scala macros, so any further advice is also appreciated.
Side question: any advice on debugging macros in intellij / gradle projects?
q"import foo; class Bar" creates a block, so the macro annotation will inadvertently replace class Bar with { import foo; class Bar }, which makes Bar a local class, which is invisible from the outside of the block.