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

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • Aurangzeb
    New Member
    • Sep 2013
    • 11

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

    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:


    Code:
    object MacroExample extends ReflectionUtils {
      import scala.language.experimental.macros
      import scala.reflect.macros.Context
    
      def foo(name: String): Any = macro foo_impl
      def foo_impl(c: Context)(name: c.Expr[String]) = {
        import c.universe._
    
        val Literal(Constant(lit: String)) = name.tree
        val anon = newTypeName(c.fresh)
    
        c.Expr(Block(
          ClassDef(
            Modifiers(Flag.FINAL), anon, Nil, Template(
              Nil, emptyValDef, List(
                constructor(c.universe),
                TypeDef(Modifiers(), newTypeName(lit), Nil, TypeTree(typeOf[Int]))
              )
            )
          ),
          Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil)
        ))
      }
    }
    (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:

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

    Code:
    scala> implicitly[res0.T =:= Int]
    res1: =:=[res0.T,Int] = <function1>
    Now suppose that we try to do the same thing with a method:

    Code:
    def bar(name: String): Any = macro bar_impl
    def bar_impl(c: Context)(name: c.Expr[String]) = {
      import c.universe._
    
      val Literal(Constant(lit: String)) = name.tree
      val anon = newTypeName(c.fresh)
    
      c.Expr(Block(
        ClassDef(
          Modifiers(Flag.FINAL), anon, Nil, Template(
            Nil, emptyValDef, List(
              constructor(c.universe),
              DefDef(
                Modifiers(), newTermName(lit), Nil, Nil, TypeTree(),
                c.literal(42).tree
              )
            )
          )
        ),
        Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil)
      ))
    }
    But when we try it out, we don't get a structural type

    Code:
    scala> MacroExample.bar("test")
    res1: AnyRef = $1$$1@da12492
    But if we stick an extra anonymous class in there:

    Code:
    def baz(name: String): Any = macro baz_impl
    def baz_impl(c: Context)(name: c.Expr[String]) = {
      import c.universe._
    
      val Literal(Constant(lit: String)) = name.tree
      val anon = newTypeName(c.fresh)
      val wrapper = newTypeName(c.fresh)
    
      c.Expr(Block(
        ClassDef(
          Modifiers(), anon, Nil, Template(
            Nil, emptyValDef, List(
              constructor(c.universe),
              DefDef(
                Modifiers(), newTermName(lit), Nil, Nil, TypeTree(),
                c.literal(42).tree
              )
            )
          )
        ),
        ClassDef(
          Modifiers(Flag.FINAL), wrapper, Nil,
          Template(Ident(anon) :: Nil, emptyValDef, constructor(c.universe) :: Nil)
        ),
        Apply(Select(New(Ident(wrapper)), nme.CONSTRUCTOR), Nil)
      ))
    }
    It works:

    Code:
    scala> MacroExample.baz("test")
    res0: AnyRef{def test: Int} = $2$$1@6663f834
    
    scala> res0.test
    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?
Working...