scala def macro and new method

1,106 views
Skip to first unread message

Arnaud PEZEL

unread,
Oct 17, 2013, 5:02:53 AM10/17/13
to scala...@googlegroups.com
Hi,

I'm new to scala and i'm trying to implement a simple macro that add an hello method to a class. Here is the macro's implementation (inside the object 'macros') :

def myMacro():Any = macro impl

def impl(c: Context)(): c.Expr[Any] =  {

import c.universe._


def generateHelloMethod():Tree = {

 

val name = TermName("sayHello")

    val tpe = TypeName("String")

    val hello = "hello world"

    q"protected def $name():$tpe = { return $hello }"

}

return c.Expr[Unit](Block(generateHelloMethod()))

}


And i call it in my class :


class Test {

macros.myMacro()

}


The problem is that when i look at the generated bytecode i see my new method declared as 'private final', why ? Should i added a modifier ? Moreover, why '$1' is added to the end of the generated method name ?


private final String sayHello$1() {

        return "hello world";

    }


I guess that there's something that i don't understand because this result seems very strange to me.


Thx

Arnaud PEZEL

unread,
Oct 17, 2013, 5:06:27 AM10/17/13
to scala...@googlegroups.com
Sorry, there's a little mistake in my code : the return type is Expr[Any] and i return a Expr[Unit] but that doesn't change the outputted method : the problem's stille there

I'm using scala 2.11.0-M5

Eugene Burmako

unread,
Oct 17, 2013, 5:07:06 AM10/17/13
to Arnaud PEZEL, scala-user
Def macros in Scala 2.10 can't add publicly visible definitions. 

For example, your macro expands into a block, which has a local method definition inside it. This method is invisible to anyone outside the block. When being compiled down to Java bytecode, such local method ends up having $1, or $2, or etc in its name.

In Scala 2.12, we are considering adding macro annotations [1] - macros that can create new definitions (add methods, create new classes, objects, etc). You can try them out right now with macro paradise [2], a compiler plugin for Scala 2.10.



--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Arnaud PEZEL

unread,
Oct 17, 2013, 5:28:34 AM10/17/13
to scala...@googlegroups.com, Arnaud PEZEL
Thank you. You saved my day trying to make this thing work... I'm actually using scala 2.11.0-M5 to try quasiquotes but i guess it doesn't make any difference. I tried the paradise plugin but i can't make this piece of code to compile. I ended up with a 

error: java.lang.AbstractMethodError: org.scalalang.macroparadise.Plugin$$anon$1.synthesisUtil()Lscala/tools/nsc/typechecker/MethodSynthesis$synthesisUtil$;

i'm using the macro-paradise_2.11.0-SNAPSHOT in maven. (i tried scala 2.10.3 and an older version of the macro-paradise too but it doesn't compile neither).

Im very interested in this feature : i'm building a jsf library fully written in scala (à la primefaces) and annotation macro could really improve my component creation syntax. So let me know if you need early beta tester of the 2.12.0 version or any contribution that can help...

Thx

Eugene Burmako

unread,
Oct 17, 2013, 5:31:25 AM10/17/13
to Arnaud PEZEL, scala-user
Looks like a binary incompatibility between M5 and master. Let me roll an M5-compatible build for you. Might take an hour or two.

Eugene Burmako

unread,
Oct 17, 2013, 7:14:30 AM10/17/13
to scala...@googlegroups.com, Arnaud PEZEL
Just published paradise for 2.11.0-M5. Please let me know if you encounter any difficulties.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+unsubscribe@googlegroups.com.
Message has been deleted

Arnaud PEZEL

unread,
Oct 17, 2013, 11:52:21 AM10/17/13
to scala...@googlegroups.com, Eugene Burmako
Ok, 'inputs = annottees.map(_.tree).toList' doesn't contains the enclosing class in the case of a def annotation. That's why it doesn't work. My mistake. Is it possible to get the enclosing class from a DefDef ?

Arnaud PEZEL

unread,
Oct 17, 2013, 11:56:45 AM10/17/13
to scala...@googlegroups.com, Arnaud PEZEL
Re-posted because the former message contained raw email address :

The plugin seems to work like a charm ! Thank you. I managed to add the 'hello' method to my class Test : generated bytecode is ok ! I'll now try with a more realistic test case...

Actually, i managed to get the annotation on a class to work but not on a def. Inputs doesn't seems to match. I probably missed something with the 'enclosing'...


val (annotteeexpandees) = inputs match {

     case (param: ClassDef) :: rest => {
  println("##====> ClassDef") //called
       (param, addHelloToEnclosing(param) :: rest)
  }
      case (param: DefDef) :: (enclosing: ClassDef) ::  rest => {
       println("##====> DefDef") //never called
       (param, addHelloToEnclosing(enclosing) :: rest)
      }
      case _ => (EmptyTreeinputs)


Annotations :

@WidgetAttribute class Test {


  

@WidgetAttribute def test:String = "test"

  

}

Eugene Burmako

unread,
Oct 17, 2013, 11:59:43 AM10/17/13
to Arnaud PEZEL, scala-user
Not really. There is c.enclosingClass, but it's not very robust and at times might give unexpected results, so I would advise against using it.

Back then, when we were discussing macro annotations internally, there was strong emphasis on macros staying local, i.e. affecting / accessing only the tree of the annottee (annotations on parameters are an exception that we're still on the fence about).

Therefore if you want to access the enclosing tree, at the moment you will have to annotate the enclosing definition. Could you elaborate on your use case, so that we could discuss potential workarounds? 

In principle, I don't mind refining the rules of how macro annotations work, but there should be a very compelling reason to do that, so let's start with workarounds first.

Eugene Burmako

unread,
Oct 17, 2013, 12:01:45 PM10/17/13
to Arnaud PEZEL, scala-user
Oh, I realized that by annotating a method you might just want to add an additional helper method to the enclosing class. 

If that's the case, then this is very easy. Just provide that additional helper method in the expansion along with the original method, and they both will be inserted into the enclosing class.

Arnaud PEZEL

unread,
Oct 18, 2013, 5:10:08 AM10/18/13
to scala...@googlegroups.com, Arnaud PEZEL
Thank you. OK, i can generate the helper method but what if i want to check that the enclosing class doesn't already contain my method. I've various use case :

For example, i want to automatically generate getter/setter following the Java Bean convention by annotating a scala method.The @BeanProperty doesn't fit my needs because it doesn't check for an existing method (like objective c do, for example) and because my methods have to get/put their values from/into a map. For example the actual code looks like this : 

def style:String = return getProperty(Properties.style, "default value").asInstanceOf[String];

def getStyle:String = style;

def style_=(style:String) = setProperty(Properties.style, style)

def setStyle(style:String) = style_=(style);


I could have used the java get/set method directly but, even if the framework is built on top of a pure java library, i want the user to feel coding in scala, not in java. So the best solution would be to have a macro that automatically writes all of this code, simply by annotating a var like this :


@FrameworkProperty(Properties.style, "default value") var style


And to let the user override one or more of the generated method if he wants to : the macro have to check for an existing def before inserting a new one.


The other use case is a little more complicated : i want to remove reflection from my framework to improve performance. The idea is to annotate a method in order to register it for future serialization (during the rendering phase). The macro would look into the enclosing class for an existing def "serializeAllAnnottees()" : if exists it appends a new line to the body of the def "serializeIntoJSON(myDefOrVar)", if not it creates it.


By the way, i'm really interested in scala and macros. I don't know if i can help but if you need something let me know.

Eugene Burmako

unread,
Oct 20, 2013, 6:45:33 AM10/20/13
to Arnaud PEZEL, scala-user
Thanks a lot for exploring the use cases of macro annotations! There is indeed a tension between the desire to make macro expansions localized and make them useful.

The first use case (@FrameworkProperty) is particularly illustrative, because it shows that apart from blind expansion we also need to be aware of what's around. In a def macro, you would do c.typeCheck with a term corresponding to the method you want to check for existence and get an answer, but in annotation macros that's more difficult as described in https://github.com/aztek/scala-workflow/issues/2#issuecomment-26529093. I don't yet have a good answer here, but I've been thinking about it for the last couple of days.

The second use case (@Serializable) is a bit controversial though, as it comes dangerously close to being too powerful [1]. Adding new members seems okay, but if we allow macros to remove or change existing unrelated members, then things might become hard to reason about. Do you think there might be a more controlled way of making macro annotations work to your liking in this use case?

Also big thanks for your offer to help. I think I have a project that might be interesting for you, and I'll email you the details shortly.

Reply all
Reply to author
Forward
0 new messages