I need to add a method to some other methods in the application. There are quite a lot of them. Is there any sensible way to do that except the straightforward one -- inserting the method call into each method manually?
See this answer for implementation with def macros.
Implementation with macro annotations:
import scala.reflect.macros.Context
import scala.language.experimental.macros
def wrappedImpl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
import scala.reflect.internal.Flags._
object DefMods{
def unapply(mods: Modifiers): Option[Modifiers] = {
val fs = mods.flags.asInstanceOf[Long]
if ((fs & DEFERRED) == 0 && (fs & STABLE) == 0) Some(mods)
else None
}
}
def wrap(t: Tree) = t match {
case DefDef(DefMods(mods), name, tparams, vparams, tpt, body) if name != nme.CONSTRUCTOR =>
DefDef(mods, name, tparams, vparams, tpt,
q"""
println("before")
val res = $body
println("after")
res""")
case x => x
}
def transform(t: Tree) = t match {
case ClassDef(mods, name, tparams, Template(parents, self, methods)) =>
ClassDef(mods, name, tparams, Template(parents, self, methods.map{wrap(_)}))
case x => x
}
c.Expr[Any](Block(annottees.map(_.tree).map(transform(_)).toList, Literal(Constant(()))))
}
import scala.annotation.StaticAnnotation
class wrapped extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro wrappedImpl
}
You'll have to use a compiler plugin for quasiquotes.
Usage:
#wrapped class Test {
def test() = println("test")
}
scala> new Test().test()
before
test
after
Related
Aloha! :)
I'm not happy about running all the time to the stackoverflow forums to get some help with another piece of strangeness scala/cats has thrown at me.
Problem is: There seems to be no really useful documentation, only some worthless - at least for me - repl lines.
Could please somebody of you point to some useful documentation? Some real code? Not just lines in the repl?
Here I just tried to work with scala/cats Eq and Show typeclasses...
What the hell am I doing wrong?
The class:
package org.hudelundpfusch.utilites.decisions.data
import cats.Show
import cats.kernel.Eq
case class Fact[+T <: Any](name: String, value: T)
extends Equals {
override def canEqual(that: Any): Boolean = that match {
case _: Fact[_] => true
case _ => false
}
override def equals(other: Any): Boolean = other match {
case that: Fact[_] =>
(that canEqual this) &&
name == that.name &&
value == that.value
case _ => false
}
override def hashCode(): Int = {
val state = Seq(name, value)
state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)
}
override def toString = s"Fact(name=$name, value=$value)"
}
case object Fact {
implicit val factEq: Eq[Fact[_]] = Eq.fromUniversalEquals[Fact[_]] // Neither of this works
// implicit def factEq: Eq[Fact[_]] = new Eq[Fact[_]] {
// def eqv(x: Fact[_], y: Fact[_]): Boolean = (x != null, y != null) match {
// case (true, _) => x.equals(y)
// case (_, true) => y.equals(x)
// case _ => true
// }
// }
implicit def factShow[T]: Show[Fact[T]] = (t: Fact[T]) => t.toString // Example calls for 'implicit val factShow[Fact[_]]' but that doesn't work
}
And the big surprise:
package org.hudelundpfusch.utilites.decisions
import cats._
import cats.data._
import cats.syntax._
import cats.implicits._
import cats.implicits.eq
import com.typesafe.scalalogging.LazyLogging
import org.hudelundpfusch.utilites.decisions.data.Fact
import org.hudelundpfusch.utilites.decisions.data.Fact._
// Tried to import everything that came to my mind to make the stuff working
object Fuddel
extends App
with LazyLogging {
logger.info("Let's start to fuddel!")
this.fuddel()
logger.info("Enough with fuddling!")
def fuddel(): Unit = {
val fact1: Fact[String] = Fact[String]("FactName", "FactValue")
println(s"${fact1.show}")
val fact2: Fact[String] = Fact[String]("FactName", "FactValue")
println(s"${fact2.show}")
println(s"${fact1.equals(fact2)}")
println(s"${fact1 == fact2}")
// println(s"${fact1 === fact2}") // Not resolved...According to the repl example this should work with implicits imported
println(s"${fact1 eq fact2}") // False? Oh joy! Thanks to the great repl example!
}
}
So please, is there any documentation not beeing worthless?
Thanks in advance
Have a better day than me
Alex
1. This here compiles just fine (I removed your package name and logging dependency):
import cats.Show
import cats.kernel.Eq
case class Fact[+T](name: String, value: T) extends Equals {
override def canEqual(that: Any): Boolean = that match {
case _: Fact[_] => true
case _ => false
}
override def equals(other: Any): Boolean = other match {
case that: Fact[_] => true // TODO: replaced, irrelevant
case _ => false
}
override def hashCode(): Int = {
val state = Seq(name, value)
state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)
}
override def toString = s"Fact(name=$name, value=$value)"
}
case object Fact {
implicit def factEq[A]: Eq[Fact[A]] = Eq.fromUniversalEquals[Fact[A]]
implicit def factShow[T]: Show[Fact[T]] = (t: Fact[T]) => t.toString
}
Note the universal quantification instead of wildcard in factEq[A]. Then in Fuddel.scala:
import cats.syntax.show._
import cats.syntax.eq._
import Fact._
object Fuddel
extends App {
this.fuddel()
def fuddel(): Unit = {
val fact1: Fact[String] = Fact[String]("FactName", "FactValue")
println(s"${fact1.show}")
val fact2: Fact[String] = Fact[String]("FactName", "FactValue")
println(s"${fact2.show}")
println(s"${fact1.equals(fact2)}")
println(s"${fact1 == fact2}")
println(s"${fact1 === fact2}")
println(s"${fact1 eq fact2}")// must be false, different instances
}
}
Note that the eq is a method that is available on every object in Scala, there is no way to override it.
2. I'd recommend to read Welsh, Gurnell "Scala with Cats". Scaladoc is good too, but you cannot navigate it effectively until you read the introductory chapter about the organization of packages and implicits in the cats library.
I have a macro annotation that I use to inject implicit type class to a companion method.
#MyMacro case class MyClass[T](a: String, b: Int, t: T)
Most of the time it work as expected, but it breaks when I use type constraint notation:
#MyMacro case class MyClass[T: TypeClass](a: String, b: Int, t: T)
// private[this] not allowed for case class parameters
This error was described on SO and reported as a bug.
Thing is: macros (v1) are no longer maintained, so I cannot expect that this will be fixed.
So what I wanted to know is: can I fix this myself within a macro? Is this change done to AST in a way that I could somehow undo it? I would like to try repairing it within a macro instead of forcing all users to rewrite their code to ...(implicit tc: TypeClass[T]).
class AnnotationType() extends scala.annotation.StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro AnnotationTypeImpl.impl
}
class AnnotationTypeImpl(val c: blackbox.Context) {
import c.universe._
def impl(annottees: Tree*): Tree = {
val tree = annottees.head.asInstanceOf[ClassDef]
val newTree = tree match {
case ClassDef(mods, name, tparams, impl#Template(parents, self, body)) =>
val newBody = body.map {
case ValDef(mods, name, tpt, rhs) =>
// look here
// the flag of `private[this]` is Flag.PRIVATE | Flag.LOCAL
// the flag of `private` is Flag.PRIVATE
// drop Flag.LOCAL in Modifiers.flags , it will change `private[this]` to `private`
val newMods =
if(mods.hasFlag(Flag.IMPLICIT))
mods.asInstanceOf[scala.reflect.internal.Trees#Modifiers].&~(Flag.LOCAL.asInstanceOf[Long]).&~(Flag.CASEACCESSOR.asInstanceOf[Long]).asInstanceOf[Modifiers]
else
mods
ValDef(newMods, name, tpt, rhs)
case e => e
}
ClassDef(mods, name, tparams, Template(parents, self, newBody))
}
println(show(tree))
println(show(newTree))
q"..${newTree +: annottees.tail}"
}
}
// test
#AnnotationType()
case class AnnotationTypeTest[T: Option](a: T){
def option = implicitly[Option[T]]
}
object AnnotationTypeTest {
def main(args: Array[String]): Unit = {
implicit val x = Option(1)
println(AnnotationTypeTest(100))
println(AnnotationTypeTest(100).option)
println(AnnotationTypeTest(100).copy(a =2222))
println(AnnotationTypeTest(100).copy(a =2222)(Some(999)).option)
}
}
I am creating some macro libraries that reads some information from annotation on the enclosing method.
#info(foo(bar, baz))
def enclosing() = {
myMacro()
}
These information are encoded as foo(bar, baz) in a StaticAnnotation #info.
foo(bar, baz) contains information myMacro need, however, foo(bar, baz) is not able to type-check at the position #info, and cause compiler error when type-checking foo(bar, baz).
I wonder if I can create a macro dontTypecheck that prevent foo(bar, baz) being type checked. So that I can create something like:
#info(dontTypecheck {
foo(bar, baz)
})
def enclosing() = {
myMacro()
}
The dontTypecheck macro should produce a Tree that contains untype-checked foo(bar, baz).
How to create the dontTypecheck macro?
one idea is use another annotation save info
class Info[T](t: T) extends scala.annotation.StaticAnnotation {
}
class AnnInfo extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro AnnInfImpl.impl
}
trait AnnotationUtils {
val c: scala.reflect.macros.blackbox.Context
import c.universe._
final def getAnnotation(x: MemberDef) = x.mods.annotations
}
class AnnInfImpl(val c: blackbox.Context) extends AnnotationUtils {
import c.universe._
// edit 1
def impl(annottees: Tree*): Tree = {
annottees.head match {
case x: DefDef =>
// collect value from `#Info(value)`
val info: List[Tree] = getAnnotation(x).collect { case q"new $name ($value)" => value }
val newBody =
q"""
{
val info = ${info.map(e => show(e))}
println(info)// just print it
${x.rhs}
}"""
DefDef(
mods = Modifiers(), //dropMods
name = x.name,
tparams = x.tparams,
vparamss = x.vparamss,
tpt = x.tpt,
rhs = newBody
)
}
}
}
// test
class AnnInfoTest {
val a = 1
val b = 2
def f(a: Int, b: Int) = a + b
#Info(f(a, b))
#AnnInfo
def e = ???
}
if you call e will print List(f(a, b))
I have a case class with annotated fields, like this:
case class Foo(#alias("foo") bar: Int)
I have a macro that processes the declaration of this class:
val (className, access, fields, bases, body) = classDecl match {
case q"case class $n $m(..$ps) extends ..$bs { ..$ss }" => (n, m, ps, bs, ss)
case _ => abort
}
Later, I search for the aliased fields, as follows:
val aliases = fields.asInstanceOf[List[ValDef]].flatMap {
field => field.symbol.annotations.collect {
//deprecated version:
//case annotation if annotation.tpe <:< cv.weakTypeOf[alias] =>
case annotation if annotation.tree.tpe <:< c.weakTypeOf[alias] =>
//deprecated version:
//annotation.scalaArgs.head match {
annotation.tree.children.tail.head match {
case Literal(Constant(param: String)) => (param, field.name)
}
}
}
However, the list of aliases ends up being empty. I have determined that field.symbol.annotations.size is, in fact, 0, despite the annotation clearly sitting on the field.
Any idea of what's wrong?
EDIT
Answering the first two comments:
(1) I tried mods.annotations, but that didn't work. That actually returns List[Tree] instead of List[Annotation], returned by symbol.annotations. Perhaps I didn't modify the code correctly, but the immediate effect was an exception during macro expansion. I'll try to play with it some more.
(2) The class declaration is grabbed while processing an annotation macro slapped on the case class.
The complete code follows. The usage is illustrated in the test code further below.
package com.xxx.util.macros
import scala.collection.immutable.HashMap
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
import scala.reflect.macros.whitebox
trait Mapped {
def $(key: String) = _vals.get(key)
protected def +=(key: String, value: Any) =
_vals += ((key, value))
private var _vals = new HashMap[String, Any]
}
class alias(val key: String) extends StaticAnnotation
class aliased extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro aliasedMacro.impl
}
object aliasedMacro {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val (classDecl, compDecl) = annottees.map(_.tree) match {
case (clazz: ClassDef) :: Nil => (clazz, None)
case (clazz: ClassDef) :: (comp: ModuleDef) :: Nil => (clazz, Some(comp))
case _ => abort(c, "#aliased must annotate a class")
}
val (className, access, fields, bases, body) = classDecl match {
case q"case class $n $m(..$ps) extends ..$bs { ..$ss }" => (n, m, ps, bs, ss)
case _ => abort(c, "#aliased is only supported on case class")
}
val mappings = fields.asInstanceOf[List[ValDef]].flatMap {
field => field.symbol.annotations.collect {
case annotation if annotation.tree.tpe <:< c.weakTypeOf[alias] =>
annotation.tree.children.tail.head match {
case Literal(Constant(param: String)) =>
q"""this += ($param, ${field.name})"""
}
}
}
val classCode = q"""
case class $className $access(..$fields) extends ..$bases {
..$body; ..$mappings
}"""
c.Expr(compDecl match {
case Some(compCode) => q"""$compCode; $classCode"""
case None => q"""$classCode"""
})
}
protected def abort(c: whitebox.Context, message: String) =
c.abort(c.enclosingPosition, message)
}
The test code:
package test.xxx.util.macros
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import org.junit.runner.RunWith
import com.xxx.util.macros._
#aliased
case class Foo(#alias("foo") foo: Int,
#alias("BAR") bar: String,
baz: String) extends Mapped
#RunWith(classOf[JUnitRunner])
class MappedTest extends FunSuite {
val foo = 13
val bar = "test"
val obj = Foo(foo, bar, "extra")
test("field aliased with its own name") {
assertResult(Some(foo))(obj $ "foo")
}
test("field aliased with another string") {
assertResult(Some(bar))(obj $ "BAR")
assertResult(None)(obj $ "bar")
}
test("unaliased field") {
assertResult(None)(obj $ "baz")
}
}
Thanks for the suggestions! In the end, using field.mods.annotations did help. This is how:
val mappings = fields.asInstanceOf[List[ValDef]].flatMap {
field => field.mods.annotations.collect {
case Apply(Select(New(Ident(TypeName("alias"))), termNames.CONSTRUCTOR),
List(Literal(Constant(param: String)))) =>
q"""this += ($param, ${field.name})"""
}
}
I am evaluating the possibility and effort to create a macro annotation to turn this:
#txn class Foo(var bar: Int)
into this:
import concurrent.stm.{Ref, InTxn}
class Foo(bar0: Int) {
private val _bar = Ref(bar0)
def bar(implicit tx: InTxn): Int = _bar()
def bar_=(value: Int)(implicit tx: InTxn): Unit = _bar() = value
}
(or perhaps creating a Foo companion object with apply method, no sure yet)
Now what I'm seeing from the ClassDef body is something like this
List(<paramaccessor> var bar: Int = _,
def <init>(bar: Int) = {
super.<init>();
()
})
So bar appears as param-accessor with no init (_), and also as argument to the <init> method.
How would I go about rewriting that body?
import scala.reflect.macros.Context
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
class txn extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro txnMacro.impl
}
object txnMacro {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val inputs = annottees.map(_.tree).toList
def reportInvalidAnnotationTarget(): Unit =
c.error(c.enclosingPosition,
"This annotation can only be used on a class definition")
val expandees: List[Tree] = inputs match {
case cd # ClassDef(mods, nme, tp, tmp # Template(parents, self, body)) :: Nil =>
println(s"Body: $body")
???
case _ =>
reportInvalidAnnotationTarget()
inputs
}
val outputs = expandees
c.Expr[Any](Block(outputs, Literal(Constant(()))))
}
}
As Eugene suggests, using quasiquotes can make this much easier. I haven't put together a full solution, but I tried some pieces so I think something along these lines will work:
case q"class $name extends $parent with ..$traits { ..$body }"=>
val newBody = body.flatMap {
case q"var $varName = $varBody" =>
List(
//put the three defs you need here. some variant of this ...
q"""private val ${stringToTermName("_" + varName.toString)} = Ref(${stringToTermName(varName.name + "0")})""",
//etc ...
)
case other => other
}
q"class $name extends $parent with ..$ttraits { ..${(newBody).toList} }"
You might find my blog post relevant: http://imranrashid.com/posts/learning-scala-macros/
The most relevant code snippet is here:
https://github.com/squito/learn_macros/blob/master/macros/src/main/scala/com/imranrashid/oleander/macros/FillTraitDefs.scala#L78
You may also find this useful for defining the getters & setters:
https://groups.google.com/forum/#!searchin/scala-user/macro|sort:date/scala-user/Ug75FW73mJI/F1ijU0uxZAUJ