By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
459,268 Members | 1,262 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 459,268 IT Pros & Developers. It's quick & easy.

Getting a structural type with an anonymous class's methods from a macro ?

P: 11
Suppose we want to write a macro that defines an anonymous class with some type members or methods, and then creates an instance of that class that's statically typed as a structural type with those methods, etc. This is possible with the macro system in 2.10.0, and the type member part is extremely easy:


Expand|Select|Wrap|Line Numbers
  1. object MacroExample extends ReflectionUtils {
  2.   import scala.language.experimental.macros
  3.   import scala.reflect.macros.Context
  4.  
  5.   def foo(name: String): Any = macro foo_impl
  6.   def foo_impl(c: Context)(name: c.Expr[String]) = {
  7.     import c.universe._
  8.  
  9.     val Literal(Constant(lit: String)) = name.tree
  10.     val anon = newTypeName(c.fresh)
  11.  
  12.     c.Expr(Block(
  13.       ClassDef(
  14.         Modifiers(Flag.FINAL), anon, Nil, Template(
  15.           Nil, emptyValDef, List(
  16.             constructor(c.universe),
  17.             TypeDef(Modifiers(), newTypeName(lit), Nil, TypeTree(typeOf[Int]))
  18.           )
  19.         )
  20.       ),
  21.       Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil)
  22.     ))
  23.   }
  24. }
(Where ReflectionUtils is a convenience trait that provides my constructor method.)

This macro lets us specify the name of the anonymous class's type member as a string literal:

Expand|Select|Wrap|Line Numbers
  1. scala> MacroExample.foo("T")
  2. res0: AnyRef{type T = Int} = $1$$1@7da533f6
Note that it's appropriately typed. We can confirm that everything's working as expected:

Expand|Select|Wrap|Line Numbers
  1. scala> implicitly[res0.T =:= Int]
  2. res1: =:=[res0.T,Int] = <function1>
  3.  
Now suppose that we try to do the same thing with a method:

Expand|Select|Wrap|Line Numbers
  1. def bar(name: String): Any = macro bar_impl
  2. def bar_impl(c: Context)(name: c.Expr[String]) = {
  3.   import c.universe._
  4.  
  5.   val Literal(Constant(lit: String)) = name.tree
  6.   val anon = newTypeName(c.fresh)
  7.  
  8.   c.Expr(Block(
  9.     ClassDef(
  10.       Modifiers(Flag.FINAL), anon, Nil, Template(
  11.         Nil, emptyValDef, List(
  12.           constructor(c.universe),
  13.           DefDef(
  14.             Modifiers(), newTermName(lit), Nil, Nil, TypeTree(),
  15.             c.literal(42).tree
  16.           )
  17.         )
  18.       )
  19.     ),
  20.     Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil)
  21.   ))
  22. }
But when we try it out, we don't get a structural type

Expand|Select|Wrap|Line Numbers
  1. scala> MacroExample.bar("test")
  2. res1: AnyRef = $1$$1@da12492
But if we stick an extra anonymous class in there:

Expand|Select|Wrap|Line Numbers
  1. def baz(name: String): Any = macro baz_impl
  2. def baz_impl(c: Context)(name: c.Expr[String]) = {
  3.   import c.universe._
  4.  
  5.   val Literal(Constant(lit: String)) = name.tree
  6.   val anon = newTypeName(c.fresh)
  7.   val wrapper = newTypeName(c.fresh)
  8.  
  9.   c.Expr(Block(
  10.     ClassDef(
  11.       Modifiers(), anon, Nil, Template(
  12.         Nil, emptyValDef, List(
  13.           constructor(c.universe),
  14.           DefDef(
  15.             Modifiers(), newTermName(lit), Nil, Nil, TypeTree(),
  16.             c.literal(42).tree
  17.           )
  18.         )
  19.       )
  20.     ),
  21.     ClassDef(
  22.       Modifiers(Flag.FINAL), wrapper, Nil,
  23.       Template(Ident(anon) :: Nil, emptyValDef, constructor(c.universe) :: Nil)
  24.     ),
  25.     Apply(Select(New(Ident(wrapper)), nme.CONSTRUCTOR), Nil)
  26.   ))
  27. }
It works:

Expand|Select|Wrap|Line Numbers
  1. scala> MacroExample.baz("test")
  2. res0: AnyRef{def test: Int} = $2$$1@6663f834
  3.  
  4. scala> res0.test
  5. res1: Int = 42
This is extremely handy—it lets you do things like this, for example—but I don't understand why it works, and the type member version works, but not bar. I know this may not be defined behavior, but does it make any sense? Is there an cleaner way to get a structural type (with the methods on it) from a macro?
Sep 18 '13 #1
Share this question for a faster answer!
Share on Google+

Post your reply

Sign in to post your reply or Sign up for a free account.