Note that there are some explanatory texts on larger screens.

plurals
  1. POGetting a structural type with an anonymous class's methods from a macro
    primarykey
    data
    text
    <p>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:</p> <pre><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) )) } } </code></pre> <p>(Where <code>ReflectionUtils</code> is a <a href="https://gist.github.com/4552471" rel="nofollow noreferrer">convenience trait</a> that provides my <code>constructor</code> method.)</p> <p>This macro lets us specify the name of the anonymous class's type member as a string literal:</p> <pre><code>scala&gt; MacroExample.foo("T") res0: AnyRef{type T = Int} = $1$$1@7da533f6 </code></pre> <p>Note that it's appropriately typed. We can confirm that everything's working as expected:</p> <pre><code>scala&gt; implicitly[res0.T =:= Int] res1: =:=[res0.T,Int] = &lt;function1&gt; </code></pre> <p>Now suppose that we try to do the same thing with a method:</p> <pre><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) )) } </code></pre> <p>But when we try it out, we don't get a structural type:</p> <pre><code>scala&gt; MacroExample.bar("test") res1: AnyRef = $1$$1@da12492 </code></pre> <p>But if we stick an extra anonymous class in there:</p> <pre><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) )) } </code></pre> <p>It works:</p> <pre><code>scala&gt; MacroExample.baz("test") res0: AnyRef{def test: Int} = $2$$1@6663f834 scala&gt; res0.test res1: Int = 42 </code></pre> <p>This is extremely handy—it lets you do things like <a href="https://github.com/travisbrown/rillit" rel="nofollow noreferrer">this</a>, for example—but I don't understand why it works, and the type member version works, but not <code>bar</code>. I know this <a href="https://stackoverflow.com/q/13669974/334519">may not be defined behavior</a>, but does it make any sense? Is there an cleaner way to get a structural type (with the methods on it) from a macro?</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload