2012-06-26 7 views
11

Sto lavorando a un DSL incorporato di Scala e i macro stanno diventando uno strumento principale per raggiungere i miei scopi. Ricevo un errore durante il tentativo di riutilizzare una sottostruttura dall'espressione della macro in entrata in quella risultante. La situazione è abbastanza complessa, ma (spero) l'ho semplificata per la sua comprensione.Come posso riutilizzare i sottotitoli di definizione (AST) in una macro?

Supponiamo di avere questo codice:

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

dove 'trasformare' è la macro coinvolti. Anche se potrebbe sembrare che non fa assolutamente nulla, è davvero trasformando il blocco mostrato in questa espressione:

3 match { case x => x } 

E 'fatto con questa macro implementazione:

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 
    } 
} 

noti che xNam corrisponde al nome variabile, xVal corrisponde al valore associato e infine xExp corrisponde all'espressione che contiene la variabile. Bene, se stampo l'albero crudo xExp ottengo Ident (newTermName ("x")), e questo è esattamente ciò che è impostato nel caso RHS. Poiché l'espressione potrebbe essere modificata (ad esempio x + 2 anziché x), questa non è una soluzione valida per me. Quello che voglio fare è riutilizzare l'albero xExp (vedere il commento xExp) mentre si altera il significato di 'x' (è una definizione nell'espressione di input ma sarà una variabile di caso LHS nell'output), ma avvia un lungo errore di riassumere in:

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

mia soluzione attuale consiste nella analisi della xExp a sustitute tutte le Idents con quelli nuovi, ma è totalmente dipendente i meccanismi interni del compilatore, e così, una soluzione temporale. È ovvio che xExp sia accompagnato da ulteriori informazioni offerte da showRaw. Come posso pulire xExp per consentire a 'x' di modificare la variabile del caso? Qualcuno può spiegare l'intera immagine di questo errore?

PS: Ho cercato invano di utilizzare la famiglia di metodi sostitutivi * dallo TreeApi ma mi mancano le nozioni di base per comprenderne le implicazioni.

+0

"resetAllAttrs" ha funzionato, dopo tutto? –

+0

Sì, lo ha fatto. Ha funzionato nel codice mostrato, così come in un albero abbastanza complesso con diverse variabili "modifiche". – jeslg

+0

Hai chiamato 'resetAllAttr' sul risultato' c.Expr', su 'block', o su un albero selezionato? –

risposta

21

Disassemblare le espressioni di input e rimontarle in modo diverso è uno scenario importante in macrologia (questo è ciò che facciamo internamente nella macro reify). Ma sfortunatamente, non è particolarmente facile al momento.

Il problema è che gli argomenti di input dell'implementazione macro della macro di copertura sono già digitati. Questa è sia una benedizione che una maledizione.

Di particolare interesse per noi è il fatto che le associazioni variabili degli alberi corrispondenti agli argomenti sono già stabilite. Ciò significa che tutti i nodi Ident e Select hanno i loro campi sym compilati, che indicano le definizioni a cui fanno riferimento questi nodi.

Ecco un esempio di come funzionano i simboli.Copio/incollo una stampa da uno dei miei discorsi (non fornisco un collegamento qui, perché la maggior parte delle informazioni nei miei discorsi è ormai deprecata, ma questa particolare stampa ha un'utilità 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) 

Per ricapitolare, scriviamo un piccolo snippet e poi lo compiliamo con scalac, chiedendo al compilatore di scaricare gli alberi dopo la fase di typer, stampando id univoci dei simboli assegnati agli alberi (se presenti).

Nella stampa risultante possiamo vedere che gli identificatori sono stati collegati alle definizioni corrispondenti. Ad esempio, da un lato, lo ValDef("x", ...), che rappresenta il parametro del metodo foo, definisce un simbolo del metodo con id = 9529. D'altra parte, il Ident("x") nel corpo del metodo ha ottenuto il suo campo sym impostato sullo stesso simbolo, che stabilisce l'associazione.

Ok, abbiamo visto come funzionano i bind in scalac e ora è il momento perfetto per introdurre un dato fondamentale.

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

Questo è il motivo per cui reify è igienico. Puoi prendere un risultato di reify e inserirlo in un albero arbitrario (che probabilmente definisce variabili con nomi in conflitto) - le associazioni originali rimarranno intatte. Funziona perché reify conserva i simboli originali, quindi i successivi controlli di tipo non riassociano i nodi AST reificati.

ora siamo tutti insieme per spiegare l'errore che stai affrontando:

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

L'argomento della transform macro contiene sia una definizione e un riferimento a una variabile x. Come appena appreso, ciò significa che i corrispondenti ValDef e Ident avranno i loro campi sym sincronizzati. Fin qui tutto bene.

Tuttavia, purtroppo la macro corrompe il legame stabilito. Ricrea il ValDef, ma non ripulisce il campo sym dell'ident corrispondente. Il controllo tipografico successivo assegna un nuovo simbolo al ValDef appena creato, ma non tocca l'Ident originale che viene copiato nel risultato letterale.

Dopo il controllo della digitazione, l'Ident originale si rivolge a un simbolo che non esiste più (questo è esattamente ciò che il messaggio di errore stava dicendo :)), che porta a un arresto anomalo durante la generazione di bytecode.

Quindi, come possiamo correggere l'errore? Purtroppo non c'è una risposta facile.

Un'opzione potrebbe essere quella di utilizzare c.resetLocalAttrs, che cancella in modo ricorsivo tutti i simboli in un determinato nodo AST. Il typecheck successivo ristabilirà i binding concessi che il codice che hai generato non li rovini (se, ad esempio, sposti xExp in un blocco che definisce esso stesso un valore chiamato x, allora sei nei guai).

Un'altra opzione è quella di giocare con i simboli. Ad esempio, è possibile scrivere il proprio resetLocalAttrs che cancella solo i collegamenti danneggiati e non tocca quelli validi. Potresti anche provare ad assegnare i simboli da solo, ma questa è una strada breve verso la follia, anche se a volte uno è costretto a camminare.

Non fantastico, sono d'accordo. Ne siamo consapevoli e intendiamo provare a risolvere questo problema fondamentale a volte. Tuttavia in questo momento le nostre mani sono piene di bugfixing prima della versione definitiva della 2.10.0, quindi non saremo in grado di risolvere il problema nel prossimo futuro. upd. Vedere https://groups.google.com/forum/#!topic/scala-internals/rIyJ4yHdPDU per alcune informazioni aggiuntive.


Bottom line. Le cose brutte accadono, perché i binding vengono incasinati. Prova prima resetLocalAttrs, e se non funziona, preparati a un lavoro di routine.