Compile-Time Metaprogramming

26.1 AST Transformation Basics

Local transformations are triggered by annotations on specific nodes. Global transformations apply to every compilation unit and are registered via META-INF/services/org.codehaus.groovy.transform.ASTTransformation.

Compilation phases: Initialization → Parsing → Conversion → Semantic Analysis → Canonicalization → Instruction Selection → Class Generation → Output → Finalization. Most transforms run in semantic analysis or canonicalization.

Explore ASTs in groovyConsole → Script → Inspect AST.

26.2 Writing a Local AST Transformation

Helpers: GeneralUtils (callX, varX, constX, stmt, block, ifS, returnS, ...), AstBuilder (buildFromString, buildFromCode, buildFromSpec), ClassCodeVisitorSupport, ClassCodeExpressionTransformer.

Testing: assertScript, GroovyShell with a custom CompilerConfiguration, ASTTest annotation (@ASTTest(phase=..., value={ assert node... })).

// Annotation
import org.codehaus.groovy.transform.GroovyASTTransformationClass
import java.lang.annotation.*
@Retention(RetentionPolicy.SOURCE)
@Target([ElementType.METHOD])
@GroovyASTTransformationClass("com.example.LoggingASTTransformation")
@interface WithLogging {}

// Transformation (must be compiled before the code that uses it)
package com.example
import org.codehaus.groovy.ast.*
import org.codehaus.groovy.ast.stmt.*
import org.codehaus.groovy.ast.expr.*
import org.codehaus.groovy.control.*
import org.codehaus.groovy.transform.*
import static org.codehaus.groovy.ast.tools.GeneralUtils.*

@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
class LoggingASTTransformation implements ASTTransformation {
    void visit(ASTNode[] nodes, SourceUnit source) {
        MethodNode method = nodes[1] as MethodNode
        def start = stmt(callX(varX("this"), "println", constX("Entering ${method.name}".toString())))
        def body = method.code as BlockStatement
        body.statements.add(0, start)
    }
}

26.3 Macros (Groovy 2.5+)

Macro methods (@Macro) expand at compile time like functions. The macro-library module provides SV (debug print) and NV macros.

import org.codehaus.groovy.macro.runtime.Macro
// macro { ... } generates AST from code inside transformations:
def expr = macro { println "generated code" }      // returns an AST node
def withVar = macro { println $v{ varX('name') } } // $v substitution

26.4 Compiler Customization

import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.*

def config = new CompilerConfiguration()
config.addCompilationCustomizers(
    new ImportCustomizer().addStarImports('java.time').addStaticStars('java.lang.Math'),
    new ASTTransformationCustomizer(groovy.transform.CompileStatic),
    new SecureASTCustomizer().with { receiversClassesBlackList = [System]; it }
)
config.scriptBaseClass = 'com.example.DslBaseScript'
def shell = new GroovyShell(config)
println shell.evaluate("sqrt(LocalDate.now().year)")