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:
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:
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:
scala> implicitly[res0.T =:= Int]
res1: =:=[res0.T,Int] = <function1>
Now suppose that we try to do the same thing with a method:
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:
scala> MacroExample.bar("test")
res1: AnyRef = $1$$1@da12492
But if we stick an extra anonymous class in there:
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:
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?
This question is answered in duplicate by Travis here. There are links to the issue in the tracker and to Eugene's discussion (in the comments and mailing list).
In the famous "Skylla and Charybdis" section of the type checker, our hero decides what shall escape dark anonymity and see the light as a member of the structural type.
There are a couple of ways to trick the type checker (which do not entail Odysseus's ploy of hugging a sheep). The simplest is to insert a dummy statement so that the block doesn't look like an anonymous class followed by its instantiation.
If the typer notices that you're a public term that isn't referenced by the outside, it will make you private.
object Mac {
import scala.language.experimental.macros
import scala.reflect.macros.Context
/* Make an instance of a structural type with the named member. */
def bar(name: String): Any = macro bar_impl
def bar_impl(c: Context)(name: c.Expr[String]) = {
import c.universe._
val anon = TypeName(c.freshName)
// next week, val q"${s: String}" = name.tree
val Literal(Constant(s: String)) = name.tree
val A = TermName(s)
val dmmy = TermName(c.freshName)
val tree = q"""
class $anon {
def $A(i: Int): Int = 2 * i
}
val $dmmy = 0
new $anon
"""
// other ploys
//(new $anon).asInstanceOf[{ def $A(i: Int): Int }]
// reference the member
//val res = new $anon
//val $dmmy = res.$A _
//res
// the canonical ploy
//new $anon { } // braces required
c.Expr(tree)
}
}
来源:https://stackoverflow.com/questions/14370842/getting-a-structural-type-with-an-anonymous-classs-methods-from-a-macro