2012-06-26 12 views
11

Estoy trabajando en un DSL incorporado Scala y las macros se están convirtiendo en una herramienta principal para lograr mis propósitos. Obtengo un error al intentar reutilizar un subárbol de la expresión de macro entrante en el resultante. La situación es bastante compleja, pero (espero) la he simplificado para su comprensión.¿Cómo puedo reutilizar los subárboles de definición (AST) en una macro?

Supongamos que tenemos este código:

val y = transform { 
    val x = 3 
    x 
} 
println(y) // prints 3 

donde 'transformar' es la macro involucrados. Aunque podría parecer que no hace absolutamente nada, en realidad está transformando el bloque mostrado en esta expresión:

3 match { case x => x } 

Se realiza con esta macro aplicación:

def transform(c: Context)(block: c.Expr[Int]): c.Expr[Int] = { 
    import c.universe._ 
    import definitions._ 

    block.tree match { 
    /* { 
    * val xNam = xVal 
    * xExp 
    * } 
    */ 
    case Block(List(ValDef(_, xNam, _, xVal)), xExp) => 
     println("# " + showRaw(xExp)) // prints Ident(newTermName("x")) 
     c.Expr(
     Match(
      xVal, 
      List(CaseDef(
      Bind(xNam, Ident(newTermName("_"))), 
      EmptyTree, 
      /* xExp */ Ident(newTermName("x")))))) 
    case _ => 
     c.error(c.enclosingPosition, "Can't transform block to function") 
     block // keep original expression 
    } 
} 

en cuenta que xNam se corresponde con el nombre de variable, xVal corresponde con su valor asociado y finalmente xExp se corresponde con la expresión que contiene la variable. Bueno, si imprimo el árbol sin procesar xExp obtengo Ident (newTermName ("x")), y eso es exactamente lo que se establece en el caso RHS. Dado que la expresión se puede modificar (por ejemplo, x + 2 en lugar de x), esta no es una solución válida para mí. Lo que quiero hacer es reutilizar el árbol xExp (ver el comentario xExp) mientras altera el significado 'x' (es una definición en la expresión de entrada pero será una variable LHS de caso en la salida), pero lanza un de error a largo resume en:

symbol value x does not exist in org.habla.main.Main$delayedInit$body.apply); see the error output for details. 

Mi solución actual consiste en el análisis de la xExp a sustitute todos los Idents por otras nuevas, pero es totalmente dependiente de las partes internas del compilador, y por lo tanto, una solución temporal. Es obvio que el xExp viene con más información que el ofrecido por showRaw. ¿Cómo puedo limpiar ese xExp para permitir que 'x' emita la variable de caso? ¿Alguien puede explicar la imagen completa de este error?

PD: he intentado sin éxito utilizar la familia de métodos sustituto * del TreeApi, pero me faltan elementos básicos para comprender sus implicaciones.

+0

¿Ha funcionado 'resetAllAttrs', después de todo? –

+0

Sí, lo hizo. Funcionó en el código mostrado, así como en un árbol bastante complejo con varios "cambios" variables. – jeslg

+0

Has llamado 'resetAllAttrs' en el resultado' c.Expr', en 'block', o en algún árbol seleccionado? –

Respuesta

21

Desensamblar expresiones de entrada y volverlas a ensamblar de una manera diferente es un escenario importante en macrología (esto es lo que hacemos internamente en la macro reify). Pero desafortunadamente, no es particularmente fácil en este momento.

El problema es que los argumentos de entrada de la macro alcanzan la implementación de macro que ya se ha seleccionado. Esto es a la vez una bendición y una maldición.

De particular interés para nosotros es el hecho de que las consolidaciones de variables en los árboles correspondientes a los argumentos ya están establecidas. Esto significa que todos los nodos Ident y Select tienen sus campos sym rellenos, apuntando a las definiciones a las que se refieren estos nodos.

Aquí hay un ejemplo de cómo funcionan los símbolos.Copiaré/pegaré una impresión de una de mis charlas (no doy ningún enlace aquí, porque la mayoría de la información en mis charlas ya está obsoleta, pero esta impresión en particular tiene utilidad eterna):

>cat Foo.scala 
def foo[T: TypeTag](x: Any) = x.asInstanceOf[T] 
foo[Long](42) 

>scalac -Xprint:typer -uniqid Foo.scala 
[[syntax trees at end of typer]]// Scala source: Foo.scala 
def foo#8339 
    [T#8340 >: Nothing#4658 <: Any#4657] 
    (x#9529: Any#4657) 
    (implicit evidence$1#9530: TypeTag#7861[T#8341]) 
    : T#8340 = 
x#9529.asInstanceOf#6023[T#8341]; 
Test#14.this.foo#8339[Long#1641](42)(scala#29.reflect#2514.`package`#3414.mirror#3463.TypeTag#10351.Long#10361) 

Para recapitular, escribimos un pequeño fragmento y luego lo compilamos con scalac, pidiendo al compilador que voltee los árboles después de la fase typer, imprimiendo identificadores únicos de los símbolos asignados a los árboles (si los hay).

En la impresión resultante, podemos ver que los identificadores se han vinculado a las definiciones correspondientes. Por ejemplo, por un lado, el ValDef("x", ...), que representa el parámetro del método foo, define un símbolo de método con id = 9529. Por otro lado, el Ident("x") en el cuerpo del método obtuvo su campo sym establecido en el mismo símbolo, que establece el enlace.

Bien, hemos visto cómo funcionan las vinculaciones en scalac, y ahora es el momento perfecto para presentar un hecho fundamental.

If a symbol has been assigned to an AST node, 
then subsequent typechecks will never reassign it. 

Es por esto que reificar es higiénico. Puede tomar el resultado de reify e insertarlo en un árbol arbitrario (que posiblemente defina variables con nombres en conflicto): los enlaces originales permanecerán intactos. Esto funciona porque reify conserva los símbolos originales, por lo que las siguientes pruebas de tipo no reencaminarán los nodos AST reificados.

Ahora estamos listos para explicar el error que estamos enfrentando:

symbol value x does not exist in org.habla.main.Main$delayedInit$body.apply); see the error output for details. 

El argumento de la transform macro contiene tanto una definición y una referencia a una variable x. Como acabamos de aprender, esto significa que los campos ValDef e Ident correspondientes tendrán sus campos sym sincronizados. Hasta aquí todo bien.

Sin embargo, lamentablemente, la macro corrompe el enlace establecido. Recrea el ValDef, pero no limpia el campo sym del identificador correspondiente. La verificación de tipo subsiguiente asigna un nuevo símbolo al ValDef recién creado, pero no toca la Ident original que se copia al resultado palabra por palabra.

Después de la verificación de tipo, la identificación original apunta a un símbolo que ya no existe (esto es exactamente lo que decía el mensaje de error :)), lo que provoca un bloqueo durante la generación de bytecode.

Entonces, ¿cómo solucionamos el error? Lamentablemente, no hay una respuesta fácil.

Una opción sería utilizar c.resetLocalAttrs, que borra recursivamente todos los símbolos en un nodo AST determinado. La verificación de tipo posterior restablecerá los enlaces garantizados de que el código que generó no se meta con ellos (si, por ejemplo, ajusta xExp en un bloque que a su vez define un valor llamado x, entonces tiene problemas).

Otra opción es jugar con símbolos. Por ejemplo, puede escribir su propio resetLocalAttrs que solo borre los enlaces dañados y no toque los válidos. También puede tratar de asignar símbolos usted mismo, pero ese es un camino corto hacia la locura, aunque a veces uno se ve obligado a caminar.

No es genial, estoy de acuerdo. Somos conscientes de eso y tenemos la intención de intentar solucionar este problema fundamental a veces. Sin embargo, en este momento nuestras manos están llenas de errores antes de la versión final 2.10.0, por lo que no podremos solucionar el problema en un futuro cercano. upd. Consulte https://groups.google.com/forum/#!topic/scala-internals/rIyJ4yHdPDU para obtener más información.


Línea inferior. Suceden cosas malas, porque las encuadernaciones se estropean. Pruebe resetLocalAttrs primero, y si no funciona, prepárese para una tarea rutinaria.

Cuestiones relacionadas