Obtenir un type structurel avec les méthodes d’une classe anonyme à partir d’une macro

Supposons que nous voulions écrire une macro qui définit une classe anonyme avec des membres ou des méthodes de type, puis crée une instance de cette classe qui est typée statiquement en tant que type structurel avec ces méthodes, etc. Cela est possible avec le macro-système de 2.10. 0, et la partie membre de type est extrêmement facile:

object MacroExample extends ReflectionUtils { import scala.language.experimental.macros import scala.reflect.macros.Context def foo(name: Ssortingng): Any = macro foo_impl def foo_impl(c: Context)(name: c.Expr[Ssortingng]) = { import c.universe._ val Literal(Constant(lit: Ssortingng)) = 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) )) } } 

(Où ReflectionUtils est un trait pratique qui fournit ma méthode constructor )

Cette macro nous permet de spécifier le nom du membre de type de la classe anonyme en tant que littéral de chaîne:

 scala> MacroExample.foo("T") res0: AnyRef{type T = Int} = $1$$1@7da533f6 

Notez qu’il est correctement saisi. Nous pouvons confirmer que tout fonctionne comme prévu:

 scala> implicitly[res0.T =:= Int] res1: =:=[res0.T,Int] =  

Maintenant, supposons que nous essayions de faire la même chose avec une méthode:

 def bar(name: Ssortingng): Any = macro bar_impl def bar_impl(c: Context)(name: c.Expr[Ssortingng]) = { import c.universe._ val Literal(Constant(lit: Ssortingng)) = 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) )) } 

Mais quand on l’essaie, on n’a pas de type structurel:

 scala> MacroExample.bar("test") res1: AnyRef = $1$$1@da12492 

Mais si nous y collons une classe supplémentaire anonyme:

 def baz(name: Ssortingng): Any = macro baz_impl def baz_impl(c: Context)(name: c.Expr[Ssortingng]) = { import c.universe._ val Literal(Constant(lit: Ssortingng)) = 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) )) } 

Ça marche:

 scala> MacroExample.baz("test") res0: AnyRef{def test: Int} = $2$$1@6663f834 scala> res0.test res1: Int = 42 

C’est très pratique – cela vous permet par exemple de faire des choses comme ça – mais je ne comprends pas pourquoi cela fonctionne, et la version du membre type fonctionne, mais pas la bar . Je sais que ce n’est peut-être pas un comportement défini , mais est-ce que cela a du sens? Existe-t-il un moyen plus propre d’obtenir un type structurel (avec les méthodes) à partir d’une macro?

Cette question est répondue en double par Travis ici . Il y a des liens vers le problème dans le tracker et la discussion d’Eugene (dans les commentaires et la liste de diffusion).

Dans la fameuse section “Skylla et Charybdis” du vérificateur de types, notre héros décide de ce qui doit échapper à l’anonymat obscur et voir la lumière comme un membre du type structurel.

Il y a plusieurs façons de tromper le vérificateur de type (ce qui n’implique pas le stratagème d’Ulysse de serrer un mouton). Le plus simple est d’insérer une instruction factice afin que le bloc ne ressemble pas à une classe anonyme suivie de son instanciation.

Si le typer remarque que vous êtes un terme public non référencé par l’extérieur, cela vous rendra privé.

 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: Ssortingng): Any = macro bar_impl def bar_impl(c: Context)(name: c.Expr[Ssortingng]) = { import c.universe._ val anon = TypeName(c.freshName) // next week, val q"${s: Ssortingng}" = name.tree val Literal(Constant(s: Ssortingng)) = 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) } }