You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2019/12/17 02:40:26 UTC

[groovy] branch master updated (85c1891 -> 8fe41a2)

This is an automated email from the ASF dual-hosted git repository.

emilles pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git.


    from 85c1891  normalize addPhaseOperations()
     new 33bbc38  add functional interfaces for phase operations to support lambdas
     new 8fe41a2  minor edits

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../groovy/tools/ast/TransformTestHelper.groovy    |   8 +-
 src/main/java/groovy/lang/GroovyClassLoader.java   |   2 +-
 src/main/java/groovy/util/GroovyScriptEngine.java  |  17 +-
 .../java/org/codehaus/groovy/ast/ClassHelper.java  |  27 +-
 .../org/codehaus/groovy/ast/VariableScope.java     |   3 +-
 .../groovy/classgen/asm/InvocationWriter.java      |   7 +-
 .../codehaus/groovy/control/CompilationUnit.java   | 667 ++++++++++-----------
 .../groovy/control/CompilerConfiguration.java      |   2 +-
 .../groovy/control/ParserPluginFactory.java        |   2 +-
 .../codehaus/groovy/control/ResolveVisitor.java    |   3 +-
 .../groovy/control/StaticImportVisitor.java        |  22 +-
 .../control/customizers/CompilationCustomizer.java |   4 +-
 .../tools/javac/JavaAwareCompilationUnit.java      |  40 +-
 .../tools/javac/JavaStubCompilationUnit.java       |  26 +-
 .../groovy/transform/ASTTransformationVisitor.java |  37 +-
 .../transform/sc/StaticCompilationVisitor.java     |  14 +-
 .../transform/trait/TraitASTTransformation.java    |  23 +-
 src/test/groovy/lang/GroovyClassLoaderTest.groovy  |   4 +-
 .../ClosureAndInnerClassNodeStructureTest.groovy   |   4 +-
 .../codehaus/groovy/tools/gse/DependencyTest.java  |  41 +-
 .../console/ui/AstNodeToScriptAdapter.groovy       |   3 +-
 .../console/ui/ScriptToTreeNodeAdapter.groovy      |   3 +-
 .../groovysh/util/ScriptVariableAnalyzer.groovy    |   4 +-
 .../codehaus/groovy/jsr223/JSR223SecurityTest.java |   9 +-
 .../groovy/parser/antlr4/util/AstDumper.groovy     |   3 +-
 25 files changed, 450 insertions(+), 525 deletions(-)


[groovy] 02/02: minor edits

Posted by em...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

emilles pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 8fe41a2feff9960a6b9bcd62a1077b32ccde839b
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Mon Dec 16 20:22:50 2019 -0600

    minor edits
---
 .../java/org/codehaus/groovy/ast/ClassHelper.java  | 27 ++++++++++------------
 .../org/codehaus/groovy/ast/VariableScope.java     |  3 +--
 .../groovy/classgen/asm/InvocationWriter.java      |  7 +++---
 .../groovy/control/ParserPluginFactory.java        |  2 +-
 .../codehaus/groovy/control/ResolveVisitor.java    |  3 ++-
 .../groovy/transform/ASTTransformationVisitor.java |  4 ++--
 6 files changed, 21 insertions(+), 25 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/ast/ClassHelper.java b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java
index 0a659d2..17bcc8d 100644
--- a/src/main/java/org/codehaus/groovy/ast/ClassHelper.java
+++ b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java
@@ -68,7 +68,6 @@ import java.math.BigInteger;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.regex.Pattern;
 
 /**
@@ -408,38 +407,36 @@ public class ClassHelper {
         return false;
     }
 
-    /**
-     * Check if the type is a generated function, i.e. closure/lambda
-     * @param type the type to check
-     * @return the check result
-     * @since 3.0.0
-     */
-    public static boolean isGeneratedFunction(ClassNode type) {
-        Objects.requireNonNull(type, "type should not be null");
-        return type.implementsAnyInterfaces(GENERATED_CLOSURE_Type, GENERATED_LAMBDA_TYPE);
-    }
-
     static class ClassHelperCache {
         static ManagedConcurrentMap<Class, SoftReference<ClassNode>> classCache = new ManagedConcurrentMap<Class, SoftReference<ClassNode>>(ReferenceBundle.getWeakBundle());
     }
 
-    public static boolean isSAMType(ClassNode type) {
+    public static boolean isSAMType(final ClassNode type) {
         return findSAM(type) != null;
     }
 
-    public static boolean isFunctionalInterface(ClassNode type) {
+    public static boolean isFunctionalInterface(final ClassNode type) {
         // Functional interface must be an interface at first, or the following exception will occur:
         // java.lang.invoke.LambdaConversionException: Functional interface SamCallable is not an interface
         return type.isInterface() && isSAMType(type);
     }
 
     /**
+     * Checks if the type is a generated function, i.e. closure or lambda.
+     *
+     * @since 3.0.0
+     */
+    public static boolean isGeneratedFunction(final ClassNode type) {
+        return type.implementsAnyInterfaces(GENERATED_CLOSURE_Type, GENERATED_LAMBDA_TYPE);
+    }
+
+    /**
      * Returns the single abstract method of a class node, if it is a SAM type, or null otherwise.
      *
      * @param type a type for which to search for a single abstract method
      * @return the method node if type is a SAM type, null otherwise
      */
-    public static MethodNode findSAM(ClassNode type) {
+    public static MethodNode findSAM(final ClassNode type) {
         if (!Modifier.isAbstract(type.getModifiers())) return null;
         if (type.isInterface()) {
             List<MethodNode> methods;
diff --git a/src/main/java/org/codehaus/groovy/ast/VariableScope.java b/src/main/java/org/codehaus/groovy/ast/VariableScope.java
index 4b0fbfc..942cfd5 100644
--- a/src/main/java/org/codehaus/groovy/ast/VariableScope.java
+++ b/src/main/java/org/codehaus/groovy/ast/VariableScope.java
@@ -27,7 +27,7 @@ import java.util.Map;
  * Records declared and referenced variabes for a given scope.  Helps determine
  * variable sharing across closure and method boundaries.
  */
-public class VariableScope implements Cloneable {
+public class VariableScope {
 
     private VariableScope parent;
     private ClassNode classScope;
@@ -186,7 +186,6 @@ public class VariableScope implements Cloneable {
 
     //
 
-    // TODO: implement Cloneable and override Object.clone()
     public VariableScope copy() {
         VariableScope that = new VariableScope(parent);
         that.classScope = this.classScope;
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/InvocationWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/InvocationWriter.java
index 221361b..6848dab 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/InvocationWriter.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/InvocationWriter.java
@@ -51,6 +51,7 @@ import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Objects;
 import java.util.TreeMap;
 
 import static org.objectweb.asm.Opcodes.AALOAD;
@@ -187,10 +188,8 @@ public class InvocationWriter {
                     mv.visitTypeInsn(CHECKCAST, owner);
                 }
             } else if (target.isPublic()
-                    && (!Modifier.isPublic(declaringClass.getModifiers())
-                    && !receiverType.equals(declaringClass))
-                    && receiverType.isDerivedFrom(declaringClass)
-                    && !receiverType.getPackageName().equals(classNode.getPackageName())) {
+                    && (!receiverType.equals(declaringClass) && !Modifier.isPublic(declaringClass.getModifiers()))
+                    && receiverType.isDerivedFrom(declaringClass) && !Objects.equals(receiverType.getPackageName(), classNode.getPackageName())) {
                 // GROOVY-6962: package private class, public method
                 owner = BytecodeHelper.getClassInternalName(receiverType);
             }
diff --git a/src/main/java/org/codehaus/groovy/control/ParserPluginFactory.java b/src/main/java/org/codehaus/groovy/control/ParserPluginFactory.java
index cced8a9..ee09105 100644
--- a/src/main/java/org/codehaus/groovy/control/ParserPluginFactory.java
+++ b/src/main/java/org/codehaus/groovy/control/ParserPluginFactory.java
@@ -34,7 +34,7 @@ public abstract class ParserPluginFactory {
     }
 
     /**
-     * Previously, created the ANTLR 2.7 parser, now throws an exception.
+     * Creates the ANTLR 2 parser.
      *
      * @throws UnsupportedOperationException always
      */
diff --git a/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java b/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java
index 30b3476..07c3c88 100644
--- a/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java
+++ b/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java
@@ -321,7 +321,7 @@ public class ResolveVisitor extends ClassCodeExpressionTransformer {
         genericParameterNames = oldPNames;
     }
 
-    private boolean resolveToInner(final ClassNode type) {
+    protected boolean resolveToInner(final ClassNode type) {
         // we do not do our name mangling to find an inner class
         // if the type is a ConstructedClassWithPackage, because in this case we
         // are resolving the name at a different place already
@@ -355,6 +355,7 @@ public class ResolveVisitor extends ClassCodeExpressionTransformer {
     // when resolving the outer class later, we set the resolved type of ConstructedOuterNestedClass instance to the actual inner class node(SEE GROOVY-7812(#2))
     private boolean resolveToOuterNested(final ClassNode type) {
         CompileUnit compileUnit = currentClass.getCompileUnit();
+        if (compileUnit == null) return false;
         String typeName = type.getName();
 
         BiConsumer<ConstructedOuterNestedClassNode, ClassNode> setRedirectListener = (s, c) -> type.setRedirect(s);
diff --git a/src/main/java/org/codehaus/groovy/transform/ASTTransformationVisitor.java b/src/main/java/org/codehaus/groovy/transform/ASTTransformationVisitor.java
index ea950a6..b8348c6 100644
--- a/src/main/java/org/codehaus/groovy/transform/ASTTransformationVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/ASTTransformationVisitor.java
@@ -332,10 +332,10 @@ public final class ASTTransformationVisitor extends ClassCodeVisitorSupport {
                 if (ASTTransformation.class.isAssignableFrom(gTransClass)) {
                     ASTTransformation instance = (ASTTransformation) gTransClass.getDeclaredConstructor().newInstance();
                     if (instance instanceof CompilationUnitAware) {
-                        ((CompilationUnitAware)instance).setCompilationUnit(compilationUnit);
+                        ((CompilationUnitAware) instance).setCompilationUnit(compilationUnit);
                     }
                     CompilationUnit.ISourceUnitOperation suOp = source -> {
-                        instance.visit(new ASTNode[] {source.getAST()}, source);
+                        instance.visit(new ASTNode[]{source.getAST()}, source);
                     };
                     if (isFirstScan) {
                         compilationUnit.addPhaseOperation(suOp, transformAnnotation.phase().getPhaseNumber());


[groovy] 01/02: add functional interfaces for phase operations to support lambdas

Posted by em...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

emilles pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 33bbc38e7a443929e3be380741a7186a1244437f
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Mon Dec 16 16:12:03 2019 -0600

    add functional interfaces for phase operations to support lambdas
    
    - retain abstract classes and method overloads for binary compatibility
---
 .../groovy/tools/ast/TransformTestHelper.groovy    |   8 +-
 src/main/java/groovy/lang/GroovyClassLoader.java   |   2 +-
 src/main/java/groovy/util/GroovyScriptEngine.java  |  17 +-
 .../codehaus/groovy/control/CompilationUnit.java   | 667 ++++++++++-----------
 .../groovy/control/CompilerConfiguration.java      |   2 +-
 .../groovy/control/StaticImportVisitor.java        |  22 +-
 .../control/customizers/CompilationCustomizer.java |   4 +-
 .../tools/javac/JavaAwareCompilationUnit.java      |  40 +-
 .../tools/javac/JavaStubCompilationUnit.java       |  26 +-
 .../groovy/transform/ASTTransformationVisitor.java |  35 +-
 .../transform/sc/StaticCompilationVisitor.java     |  14 +-
 .../transform/trait/TraitASTTransformation.java    |  23 +-
 src/test/groovy/lang/GroovyClassLoaderTest.groovy  |   4 +-
 .../ClosureAndInnerClassNodeStructureTest.groovy   |   4 +-
 .../codehaus/groovy/tools/gse/DependencyTest.java  |  41 +-
 .../console/ui/AstNodeToScriptAdapter.groovy       |   3 +-
 .../console/ui/ScriptToTreeNodeAdapter.groovy      |   3 +-
 .../groovysh/util/ScriptVariableAnalyzer.groovy    |   4 +-
 .../codehaus/groovy/jsr223/JSR223SecurityTest.java |   9 +-
 .../groovy/parser/antlr4/util/AstDumper.groovy     |   3 +-
 20 files changed, 430 insertions(+), 501 deletions(-)

diff --git a/src/main/groovy/org/codehaus/groovy/tools/ast/TransformTestHelper.groovy b/src/main/groovy/org/codehaus/groovy/tools/ast/TransformTestHelper.groovy
index d4005e6..8a073bf 100644
--- a/src/main/groovy/org/codehaus/groovy/tools/ast/TransformTestHelper.groovy
+++ b/src/main/groovy/org/codehaus/groovy/tools/ast/TransformTestHelper.groovy
@@ -22,7 +22,6 @@ import groovy.transform.PackageScope
 import org.codehaus.groovy.ast.ClassNode
 import org.codehaus.groovy.classgen.GeneratorContext
 import org.codehaus.groovy.control.CompilationUnit
-import org.codehaus.groovy.control.CompilationUnit.PrimaryClassNodeOperation
 import org.codehaus.groovy.control.CompilePhase
 import org.codehaus.groovy.control.CompilerConfiguration
 import org.codehaus.groovy.control.SourceUnit
@@ -104,15 +103,16 @@ class TestHarnessClassLoader extends GroovyClassLoader {
  * Operation exists so that an AstTransformation can be run against the SourceUnit.
  */
 @PackageScope
-class TestHarnessOperation extends PrimaryClassNodeOperation {
+class TestHarnessOperation implements CompilationUnit.IPrimaryClassNodeOperation {
 
     private final ASTTransformation transform
 
-    TestHarnessOperation(transform) {
+    TestHarnessOperation(ASTTransformation transform) {
         this.transform = transform
     }
 
+    @Override
     void call(SourceUnit source, GeneratorContext ignoredContext, ClassNode ignoredNode) {
-        transform.visit(null, source)
+        this.transform.visit(null, source)
     }
 }
diff --git a/src/main/java/groovy/lang/GroovyClassLoader.java b/src/main/java/groovy/lang/GroovyClassLoader.java
index 27e7038..e4c8aae 100644
--- a/src/main/java/groovy/lang/GroovyClassLoader.java
+++ b/src/main/java/groovy/lang/GroovyClassLoader.java
@@ -1157,7 +1157,7 @@ public class GroovyClassLoader extends URLClassLoader {
         clearCache();
     }
 
-    private static class TimestampAdder extends CompilationUnit.PrimaryClassNodeOperation implements Opcodes {
+    private static class TimestampAdder implements CompilationUnit.IPrimaryClassNodeOperation, Opcodes {
         private static final TimestampAdder INSTANCE = new TimestampAdder();
 
         private TimestampAdder() {}
diff --git a/src/main/java/groovy/util/GroovyScriptEngine.java b/src/main/java/groovy/util/GroovyScriptEngine.java
index fa813a6..7afd183 100644
--- a/src/main/java/groovy/util/GroovyScriptEngine.java
+++ b/src/main/java/groovy/util/GroovyScriptEngine.java
@@ -25,7 +25,6 @@ import groovy.lang.GroovyResourceLoader;
 import groovy.lang.Script;
 import org.codehaus.groovy.GroovyBugError;
 import org.codehaus.groovy.ast.ClassNode;
-import org.codehaus.groovy.ast.InnerClassNode;
 import org.codehaus.groovy.classgen.GeneratorContext;
 import org.codehaus.groovy.control.ClassNodeResolver;
 import org.codehaus.groovy.control.CompilationFailedException;
@@ -166,16 +165,12 @@ public class GroovyScriptEngine implements ResourceConnector {
             // remove all old entries including the "." entry
             cache.clear();
 
-            cu.addPhaseOperation(new CompilationUnit.PrimaryClassNodeOperation() {
-                @Override
-                public void call(final SourceUnit source, GeneratorContext context, ClassNode classNode)
-                        throws CompilationFailedException {
-                    // GROOVY-4013: If it is an inner class, tracking its dependencies doesn't really
-                    // serve any purpose and also interferes with the caching done to track dependencies
-                    if (classNode instanceof InnerClassNode) return;
-                    DependencyTracker dt = new DependencyTracker(source, cache, precompiledEntries);
-                    dt.visitClass(classNode);
-                }
+            cu.addPhaseOperation((final SourceUnit sourceUnit, final GeneratorContext context, final ClassNode classNode) -> {
+               // GROOVY-4013: If it is an inner class, tracking its dependencies doesn't really
+               // serve any purpose and also interferes with the caching done to track dependencies
+               if (classNode.getOuterClass() != null) return;
+               DependencyTracker dt = new DependencyTracker(sourceUnit, cache, precompiledEntries);
+               dt.visitClass(classNode);
             }, Phases.CLASS_GENERATION);
 
             cu.setClassNodeResolver(new ClassNodeResolver() {
diff --git a/src/main/java/org/codehaus/groovy/control/CompilationUnit.java b/src/main/java/org/codehaus/groovy/control/CompilationUnit.java
index 210ec3f..5b81391 100644
--- a/src/main/java/org/codehaus/groovy/control/CompilationUnit.java
+++ b/src/main/java/org/codehaus/groovy/control/CompilationUnit.java
@@ -72,8 +72,8 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.Queue;
 import java.util.Set;
-import java.util.stream.Collectors;
 
+import static java.util.stream.Collectors.toList;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
 import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.DYNAMIC_OUTER_NODE_CALLBACK;
@@ -123,8 +123,6 @@ public class CompilationUnit extends ProcessingUnit {
 
     protected ClassNodeResolver classNodeResolver = new ClassNodeResolver();
     protected ResolveVisitor resolveVisitor = new ResolveVisitor(this);
-    protected OptimizerVisitor optimizer = new OptimizerVisitor(this);
-    protected Verifier verifier = new Verifier();
 
     /** The AST transformations state data. */
     protected ASTTransformationsContext astTransformationsContext;
@@ -164,9 +162,10 @@ public class CompilationUnit extends ProcessingUnit {
      * Initializes the CompilationUnit with a CodeSource for controlling
      * security stuff, a class loader for loading classes, and a class
      * loader for loading AST transformations.
-     * <b>Note</b> The transform loader must be
-     * able to load compiler classes. That means CompilationUnit.class.classLoader
-     * must be at last a parent to transformLoader. The other loader has no such constraint.
+     * <p>
+     * <b>Note</b>: The transform loader must be able to load compiler classes.
+     * That means {@link #classLoader} must be at last a parent to {@code transformLoader}.
+     * The other loader has no such constraint.
      *
      * @param transformLoader - the loader for transforms
      * @param loader          - loader used to resolve classes against during compilation
@@ -178,188 +177,165 @@ public class CompilationUnit extends ProcessingUnit {
         super(configuration, loader, null);
 
         this.astTransformationsContext = new ASTTransformationsContext(this, transformLoader);
-        this.ast = new CompileUnit(this.classLoader, codeSource, this.configuration);
+        this.ast = new CompileUnit(getClassLoader(), codeSource, getConfiguration());
 
         addPhaseOperations();
         applyCompilationCustomizers();
     }
 
     private void addPhaseOperations() {
-        addPhaseOperation(new SourceUnitOperation() {
-            @Override
-            public void call(final SourceUnit source) throws CompilationFailedException {
-                source.parse();
-            }
-        }, Phases.PARSING);
-
-        addPhaseOperation(new SourceUnitOperation() {
-            @Override
-            public void call(final SourceUnit source) throws CompilationFailedException {
-                source.convert();
-                // add module to compile unit
-                getAST().addModule(source.getAST());
-
-                if (progressCallback != null) {
-                    progressCallback.call(source, phase);
-                }
-            }
+        addPhaseOperation(SourceUnit::parse, Phases.PARSING);
+
+        addPhaseOperation(source -> {
+            source.convert();
+            // add module to compile unit
+            getAST().addModule(source.getAST());
+            Optional.ofNullable(getProgressCallback())
+                .ifPresent(callback -> callback.call(source, getPhase()));
         }, Phases.CONVERSION);
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                GroovyClassVisitor visitor = new EnumVisitor(CompilationUnit.this, source);
-                visitor.visitClass(classNode);
-            }
+
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            GroovyClassVisitor visitor = new EnumVisitor(this, source);
+            visitor.visitClass(classNode);
         }, Phases.CONVERSION);
 
         addPhaseOperation(resolve, Phases.SEMANTIC_ANALYSIS);
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                StaticImportVisitor visitor = new StaticImportVisitor();
-                visitor.visitClass(classNode, source);
-            }
+
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            GroovyClassVisitor visitor = new StaticImportVisitor(classNode, source);
+            visitor.visitClass(classNode);
         }, Phases.SEMANTIC_ANALYSIS);
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                GroovyClassVisitor visitor = new InnerClassVisitor(CompilationUnit.this, source);
-                visitor.visitClass(classNode);
-            }
+
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            GroovyClassVisitor visitor = new InnerClassVisitor(this, source);
+            visitor.visitClass(classNode);
         }, Phases.SEMANTIC_ANALYSIS);
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                if (!classNode.isSynthetic()) {
-                    GroovyClassVisitor visitor = new GenericsVisitor(source);
-                    visitor.visitClass(classNode);
-                }
+
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            if (!classNode.isSynthetic()) {
+                GroovyClassVisitor visitor = new GenericsVisitor(source);
+                visitor.visitClass(classNode);
             }
         }, Phases.SEMANTIC_ANALYSIS);
 
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                TraitComposer.doExtendTraits(classNode, source, CompilationUnit.this);
-            }
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            TraitComposer.doExtendTraits(classNode, source, this);
         }, Phases.CANONICALIZATION);
-        addPhaseOperation(new SourceUnitOperation() {
-            @Override
-            public void call(final SourceUnit source) throws CompilationFailedException {
-                List<ClassNode> classes = source.ast.getClasses();
-                for (ClassNode node : classes) {
-                    CompileUnit cu = node.getCompileUnit();
-                    for (Iterator<String> it = cu.iterateClassNodeToCompile(); it.hasNext(); ) {
-                        String name = it.next();
-                        StringBuilder message = new StringBuilder();
-                        message
-                                .append("Compilation incomplete: expected to find the class ")
-                                .append(name)
-                                .append(" in ")
-                                .append(source.getName());
-                        if (classes.isEmpty()) {
-                            message.append(", but the file seems not to contain any classes");
-                        } else {
-                            message.append(", but the file contains the classes: ");
-                            boolean first = true;
-                            for (ClassNode cn : classes) {
-                                if (!first) {
-                                    message.append(", ");
-                                } else {
-                                    first = false;
-                                }
-                                message.append(cn.getName());
+
+        addPhaseOperation(source -> {
+            List<ClassNode> classes = source.getAST().getClasses();
+            for (ClassNode node : classes) {
+                CompileUnit cu = node.getCompileUnit();
+                for (Iterator<String> it = cu.iterateClassNodeToCompile(); it.hasNext(); ) {
+                    String name = it.next();
+                    StringBuilder message = new StringBuilder();
+                    message
+                            .append("Compilation incomplete: expected to find the class ")
+                            .append(name)
+                            .append(" in ")
+                            .append(source.getName());
+                    if (classes.isEmpty()) {
+                        message.append(", but the file seems not to contain any classes");
+                    } else {
+                        message.append(", but the file contains the classes: ");
+                        boolean first = true;
+                        for (ClassNode cn : classes) {
+                            if (first) {
+                                first = false;
+                            } else {
+                                message.append(", ");
                             }
+                            message.append(cn.getName());
                         }
-
-                        getErrorCollector().addErrorAndContinue(
-                                new SimpleMessage(message.toString(), CompilationUnit.this)
-                        );
-                        it.remove();
                     }
+
+                    getErrorCollector().addErrorAndContinue(
+                            new SimpleMessage(message.toString(), this)
+                    );
+                    it.remove();
                 }
             }
         }, Phases.CANONICALIZATION);
 
         addPhaseOperation(classgen, Phases.CLASS_GENERATION);
 
-        addPhaseOperation(output);
+        addPhaseOperation(groovyClass -> {
+            String name = groovyClass.getName().replace('.', File.separatorChar) + ".class";
+            File path = new File(getConfiguration().getTargetDirectory(), name);
 
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                AnnotationCollectorTransform.ClassChanger xformer = new AnnotationCollectorTransform.ClassChanger();
-                xformer.transformClass(classNode);
+            // ensure the path is ready for the file
+            File directory = path.getParentFile();
+            if (directory != null && !directory.exists()) {
+                directory.mkdirs();
             }
+
+            // create the file and write out the data
+            try (FileOutputStream stream = new FileOutputStream(path)) {
+                byte[] bytes = groovyClass.getBytes();
+                stream.write(bytes, 0, bytes.length);
+            } catch (IOException e) {
+                getErrorCollector().addError(Message.create(e.getMessage(), this));
+            }
+        });
+
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            AnnotationCollectorTransform.ClassChanger xformer = new AnnotationCollectorTransform.ClassChanger();
+            xformer.transformClass(classNode);
         }, Phases.SEMANTIC_ANALYSIS);
         ASTTransformationVisitor.addPhaseOperations(this);
 
         // post-transform operations:
 
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                StaticVerifier verifier = new StaticVerifier();
-                verifier.visitClass(classNode, source);
-            }
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            StaticVerifier verifier = new StaticVerifier();
+            verifier.visitClass(classNode, source);
         }, Phases.SEMANTIC_ANALYSIS);
 
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                GroovyClassVisitor visitor = new InnerClassCompletionVisitor(CompilationUnit.this, source);
-                visitor.visitClass(classNode);
-            }
-        }, Phases.CANONICALIZATION);
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                GroovyClassVisitor visitor = new EnumCompletionVisitor(CompilationUnit.this, source);
-                visitor.visitClass(classNode);
-            }
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            GroovyClassVisitor visitor = new InnerClassCompletionVisitor(this, source);
+            visitor.visitClass(classNode);
+
+            visitor = new EnumCompletionVisitor(this, source);
+            visitor.visitClass(classNode);
         }, Phases.CANONICALIZATION);
 
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                Object callback = classNode.getNodeMetaData(DYNAMIC_OUTER_NODE_CALLBACK);
-                if (callback instanceof PrimaryClassNodeOperation) {
-                    ((PrimaryClassNodeOperation) callback).call(source, context, classNode);
-                    classNode.removeNodeMetaData(DYNAMIC_OUTER_NODE_CALLBACK);
-                }
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            Object callback = classNode.getNodeMetaData(DYNAMIC_OUTER_NODE_CALLBACK);
+            if (callback instanceof IPrimaryClassNodeOperation) {
+                ((IPrimaryClassNodeOperation) callback).call(source, context, classNode);
+                classNode.removeNodeMetaData(DYNAMIC_OUTER_NODE_CALLBACK);
             }
         }, Phases.INSTRUCTION_SELECTION);
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                // TODO: Could this be moved into org.codehaus.groovy.transform.sc.transformers.VariableExpressionTransformer?
-                new ClassCodeExpressionTransformer() {
-                    @Override
-                    protected SourceUnit getSourceUnit() {
-                        return source;
-                    }
 
-                    @Override
-                    public Expression transform(final Expression expression) {
-                        if (expression instanceof VariableExpression) {
-                            // check for "switch(enumType) { case CONST: ... }"
-                            ClassNode enumType = expression.getNodeMetaData(SWITCH_CONDITION_EXPRESSION_TYPE);
-                            if (enumType != null) {
-                                // replace "CONST" variable expression with "EnumType.CONST" property expression
-                                Expression propertyExpression = propX(classX(enumType), expression.getText());
-                                setSourcePosition(propertyExpression, expression);
-                                return propertyExpression;
-                            }
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            // TODO: Can this be moved into org.codehaus.groovy.transform.sc.transformers.VariableExpressionTransformer?
+            GroovyClassVisitor visitor = new ClassCodeExpressionTransformer() {
+                @Override
+                protected SourceUnit getSourceUnit() {
+                    return source;
+                }
+
+                @Override
+                public Expression transform(final Expression expression) {
+                    if (expression instanceof VariableExpression) {
+                        // check for "switch(enumType) { case CONST: ... }"
+                        ClassNode enumType = expression.getNodeMetaData(SWITCH_CONDITION_EXPRESSION_TYPE);
+                        if (enumType != null) {
+                            // replace "CONST" variable expression with "EnumType.CONST" property expression
+                            Expression propertyExpression = propX(classX(enumType), expression.getText());
+                            setSourcePosition(propertyExpression, expression);
+                            return propertyExpression;
                         }
-                        return expression;
                     }
-                }.visitClass(classNode);
-            }
+                    return expression;
+                }
+            };
+            visitor.visitClass(classNode);
         }, Phases.INSTRUCTION_SELECTION);
     }
 
     private void applyCompilationCustomizers() {
-        for (CompilationCustomizer customizer : configuration.getCompilationCustomizers()) {
+        for (CompilationCustomizer customizer : getConfiguration().getCompilationCustomizers()) {
             if (customizer instanceof CompilationUnitAware) {
                 ((CompilationUnitAware) customizer).setCompilationUnit(this);
             }
@@ -367,26 +343,26 @@ public class CompilationUnit extends ProcessingUnit {
         }
     }
 
-    public void addPhaseOperation(final GroovyClassOperation op) {
+    public void addPhaseOperation(final IGroovyClassOperation op) {
         phaseOperations[Phases.OUTPUT].addFirst(op);
     }
 
-    public void addPhaseOperation(final SourceUnitOperation op, final int phase) {
+    public void addPhaseOperation(final ISourceUnitOperation op, final int phase) {
         validatePhase(phase);
         phaseOperations[phase].add(op);
     }
 
-    public void addPhaseOperation(final PrimaryClassNodeOperation op, final int phase) {
+    public void addPhaseOperation(final IPrimaryClassNodeOperation op, final int phase) {
         validatePhase(phase);
         phaseOperations[phase].add(op);
     }
 
-    public void addFirstPhaseOperation(final PrimaryClassNodeOperation op, final int phase) {
+    public void addFirstPhaseOperation(final IPrimaryClassNodeOperation op, final int phase) {
         validatePhase(phase);
         phaseOperations[phase].addFirst(op);
     }
 
-    public void addNewPhaseOperation(final SourceUnitOperation op, final int phase) {
+    public void addNewPhaseOperation(final ISourceUnitOperation op, final int phase) {
         validatePhase(phase);
         newPhaseOperations[phase].add(op);
     }
@@ -404,7 +380,7 @@ public class CompilationUnit extends ProcessingUnit {
     @Override
     public void configure(final CompilerConfiguration configuration) {
         super.configure(configuration);
-        this.debug = this.configuration.getDebug();
+        this.debug = getConfiguration().getDebug();
         this.configured = true;
     }
 
@@ -435,16 +411,13 @@ public class CompilationUnit extends ProcessingUnit {
      */
     public ClassNode getClassNode(final String name) {
         ClassNode[] result = new ClassNode[1];
-        PrimaryClassNodeOperation handler = new PrimaryClassNodeOperation() {
-            @Override
-            public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) {
-                if (classNode.getName().equals(name)) {
-                    result[0] = classNode;
-                }
+        IPrimaryClassNodeOperation handler = (source, context, classNode) -> {
+            if (classNode.getName().equals(name)) {
+                result[0] = classNode;
             }
         };
         try {
-            applyToPrimaryClassNodes(handler);
+            handler.doPhaseOperation(this);
         } catch (CompilationFailedException e) {
             if (debug) e.printStackTrace();
         }
@@ -506,26 +479,26 @@ public class CompilationUnit extends ProcessingUnit {
      * Adds a source file to the unit.
      */
     public SourceUnit addSource(final File file) {
-        return addSource(new SourceUnit(file, configuration, classLoader, getErrorCollector()));
+        return addSource(new SourceUnit(file, getConfiguration(), getClassLoader(), getErrorCollector()));
     }
 
     /**
      * Adds a source file to the unit.
      */
     public SourceUnit addSource(final URL url) {
-        return addSource(new SourceUnit(url, configuration, classLoader, getErrorCollector()));
+        return addSource(new SourceUnit(url, getConfiguration(), getClassLoader(), getErrorCollector()));
     }
 
     /**
      * Adds a InputStream source to the unit.
      */
     public SourceUnit addSource(final String name, final InputStream stream) {
-        ReaderSource source = new InputStreamReaderSource(stream, configuration);
-        return addSource(new SourceUnit(name, source, configuration, classLoader, getErrorCollector()));
+        ReaderSource source = new InputStreamReaderSource(stream, getConfiguration());
+        return addSource(new SourceUnit(name, source, getConfiguration(), getClassLoader(), getErrorCollector()));
     }
 
     public SourceUnit addSource(final String name, final String scriptText) {
-        return addSource(new SourceUnit(name, scriptText, configuration, classLoader, getErrorCollector()));
+        return addSource(new SourceUnit(name, scriptText, getConfiguration(), getClassLoader(), getErrorCollector()));
     }
 
     /**
@@ -533,7 +506,7 @@ public class CompilationUnit extends ProcessingUnit {
      */
     public SourceUnit addSource(final SourceUnit source) {
         String name = source.getName();
-        source.setClassLoader(this.classLoader);
+        source.setClassLoader(getClassLoader());
         for (SourceUnit su : queuedSources) {
             if (name.equals(su.getName())) return su;
         }
@@ -589,6 +562,10 @@ public class CompilationUnit extends ProcessingUnit {
         void call(ClassVisitor writer, ClassNode node) throws CompilationFailedException;
     }
 
+    public ClassgenCallback getClassgenCallback() {
+        return classgenCallback;
+    }
+
     /**
      * Sets a ClassgenCallback.  You can have only one, and setting
      * it to {@code null} removes any existing setting.
@@ -597,10 +574,6 @@ public class CompilationUnit extends ProcessingUnit {
         this.classgenCallback = visitor;
     }
 
-    public ClassgenCallback getClassgenCallback() {
-        return classgenCallback;
-    }
-
     /**
      * A callback interface you can use to get a callback after every
      * unit of the compile process.  You will be called-back with a
@@ -612,6 +585,10 @@ public class CompilationUnit extends ProcessingUnit {
         void call(ProcessingUnit context, int phase) throws CompilationFailedException;
     }
 
+    public ProgressCallback getProgressCallback() {
+        return progressCallback;
+    }
+
     /**
      * Sets a ProgressCallback.  You can have only one, and setting
      * it to {@code null} removes any existing setting.
@@ -620,15 +597,11 @@ public class CompilationUnit extends ProcessingUnit {
         this.progressCallback = callback;
     }
 
-    public ProgressCallback getProgressCallback() {
-        return progressCallback;
-    }
-
     //---------------------------------------------------------------------------
     // ACTIONS
 
     /**
-     * Synonym for compile(Phases.ALL).
+     * Synonym for {@code compile(Phases.ALL)}.
      */
     public void compile() throws CompilationFailedException {
         compile(Phases.ALL);
@@ -656,9 +629,10 @@ public class CompilationUnit extends ProcessingUnit {
             // Grab processing may have brought in new AST transforms into various phases, process them as well
             processNewPhaseOperations(phase);
 
-            if (progressCallback != null) progressCallback.call(this, phase);
+            Optional.ofNullable(getProgressCallback())
+                .ifPresent(callback -> callback.call(this, phase));
             completePhase();
-            applyToSourceUnits(mark);
+            mark();
 
             if (dequeued()) continue;
 
@@ -669,7 +643,7 @@ public class CompilationUnit extends ProcessingUnit {
             }
         }
 
-        errorCollector.failIfErrors();
+        getErrorCollector().failIfErrors();
     }
 
     private void processPhaseOperations(final int phase) {
@@ -735,56 +709,28 @@ public class CompilationUnit extends ProcessingUnit {
     /**
      * Resolves all types.
      */
-    private final SourceUnitOperation resolve = new SourceUnitOperation() {
-        @Override
-        public void call(final SourceUnit source) throws CompilationFailedException {
-            List<ClassNode> classes = source.ast.getClasses();
-            for (ClassNode node : classes) {
-                VariableScopeVisitor scopeVisitor = new VariableScopeVisitor(source);
-                scopeVisitor.visitClass(node);
-
-                resolveVisitor.setClassNodeResolver(classNodeResolver);
-                resolveVisitor.startResolving(node, source);
-            }
-
-        }
-    };
-
-    private final GroovyClassOperation output = new GroovyClassOperation() {
-        @Override
-        public void call(final GroovyClass groovyClass) throws CompilationFailedException {
-            String name = groovyClass.getName().replace('.', File.separatorChar) + ".class";
-            File path = new File(configuration.getTargetDirectory(), name);
-
-            // ensure the path is ready for the file
-            File directory = path.getParentFile();
-            if (directory != null && !directory.exists()) {
-                directory.mkdirs();
-            }
-
-            // create the file and write out the data
-            byte[] bytes = groovyClass.getBytes();
+    private final ISourceUnitOperation resolve = (final SourceUnit source) -> {
+        for (ClassNode classNode : source.getAST().getClasses()) {
+            GroovyClassVisitor visitor = new VariableScopeVisitor(source);
+            visitor.visitClass(classNode);
 
-            try (FileOutputStream stream = new FileOutputStream(path)) {
-                stream.write(bytes, 0, bytes.length);
-            } catch (IOException e) {
-                getErrorCollector().addError(Message.create(e.getMessage(), CompilationUnit.this));
-            }
+            resolveVisitor.setClassNodeResolver(classNodeResolver);
+            resolveVisitor.startResolving(classNode, source);
         }
     };
 
     /**
-     * Runs classgen() on a single ClassNode.
+     * Runs {@link #classgen()} on a single {@code ClassNode}.
      */
-    private final PrimaryClassNodeOperation classgen = new PrimaryClassNodeOperation() {
+    private final IPrimaryClassNodeOperation classgen = new IPrimaryClassNodeOperation() {
         @Override
         public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-            optimizer.visitClass(classNode, source); // GROOVY-4272: repositioned it here from static import visitor
+            new OptimizerVisitor(CompilationUnit.this).visitClass(classNode, source); // GROOVY-4272: repositioned from static import visitor
 
             //
             // Run the Verifier on the outer class
             //
-            GroovyClassVisitor visitor = verifier;
+            GroovyClassVisitor visitor = new Verifier();
             try {
                 visitor.visitClass(classNode);
             } catch (RuntimeParserException rpe) {
@@ -808,8 +754,6 @@ public class CompilationUnit extends ProcessingUnit {
             visitor = new ExtendedVerifier(source);
             visitor.visitClass(classNode);
 
-            visitor = null;
-
             // because the class may be generated even if a error was found
             // and that class may have an invalid format we fail here if needed
             getErrorCollector().failIfErrors();
@@ -822,33 +766,34 @@ public class CompilationUnit extends ProcessingUnit {
             String sourceName = (source == null ? classNode.getModule().getDescription() : source.getName());
             // only show the file name and its extension like javac does in its stacktraces rather than the full path
             // also takes care of both \ and / depending on the host compiling environment
-            if (sourceName != null)
+            if (sourceName != null) {
                 sourceName = sourceName.substring(Math.max(sourceName.lastIndexOf('\\'), sourceName.lastIndexOf('/')) + 1);
-            AsmClassGenerator generator = new AsmClassGenerator(source, context, classVisitor, sourceName);
+            }
 
             //
             // Run the generation and create the class (if required)
             //
-            generator.visitClass(classNode);
+            visitor = new AsmClassGenerator(source, context, classVisitor, sourceName);
+            visitor.visitClass(classNode);
 
             byte[] bytes = ((ClassWriter) classVisitor).toByteArray();
-            generatedClasses.add(new GroovyClass(classNode.getName(), bytes));
+            getClasses().add(new GroovyClass(classNode.getName(), bytes));
 
             //
             // Handle any callback that's been set
             //
-            if (CompilationUnit.this.classgenCallback != null) {
-                classgenCallback.call(classVisitor, classNode);
-            }
+            Optional.ofNullable(getClassgenCallback())
+                .ifPresent(callback -> callback.call(classVisitor, classNode));
 
             //
             // Recurse for inner classes
             //
-            LinkedList<ClassNode> innerClasses = generator.getInnerClasses();
+            LinkedList<ClassNode> innerClasses = ((AsmClassGenerator) visitor).getInnerClasses();
             while (!innerClasses.isEmpty()) {
                 classgen.call(source, context, innerClasses.removeFirst());
             }
         }
+
         @Override
         public boolean needSortedInput() {
             return true;
@@ -866,10 +811,10 @@ public class CompilationUnit extends ProcessingUnit {
                 // try classes under compilation
                 CompileUnit cu = getAST();
                 ClassNode cn = cu.getClass(name);
-                if (cn!=null) return cn;
+                if (cn != null) return cn;
                 // try inner classes
                 cn = cu.getGeneratedInnerClass(name);
-                if (cn!=null) return cn;
+                if (cn != null) return cn;
                 ClassNodeResolver.LookupResult lookupResult = getClassNodeResolver().resolveName(name, CompilationUnit.this);
                 return lookupResult == null ? null : lookupResult.getClassNode();
             }
@@ -880,8 +825,8 @@ public class CompilationUnit extends ProcessingUnit {
                 if (c.isInterface() || d.isInterface()) return ClassHelper.OBJECT_TYPE;
                 do {
                     c = c.getSuperClass();
-                } while (c!=null && !d.isDerivedFrom(c));
-                if (c==null) return ClassHelper.OBJECT_TYPE;
+                } while (c != null && !d.isDerivedFrom(c));
+                if (c == null) return ClassHelper.OBJECT_TYPE;
                 return c;
             }
             @Override
@@ -900,115 +845,153 @@ public class CompilationUnit extends ProcessingUnit {
      * Updates the phase marker on all sources.
      */
     protected void mark() throws CompilationFailedException {
-        applyToSourceUnits(mark);
-    }
-
-    /**
-     * Marks a single SourceUnit with the current phase,
-     * if it isn't already there yet.
-     */
-    private final SourceUnitOperation mark = new SourceUnitOperation() {
-        @Override
-        public void call(final SourceUnit source) throws CompilationFailedException {
+        ISourceUnitOperation mark = (final SourceUnit source) -> {
             if (source.phase < phase) {
                 source.gotoPhase(phase);
             }
             if (source.phase == phase && phaseComplete && !source.phaseComplete) {
                 source.completePhase();
             }
-        }
-    };
+        };
+        mark.doPhaseOperation(this);
+    }
 
     //---------------------------------------------------------------------------
     // LOOP SIMPLIFICATION FOR SourceUnit OPERATIONS
 
     private interface PhaseOperation {
-        default void doPhaseOperation(final CompilationUnit unit) {
-            if (this instanceof SourceUnitOperation) {
-                unit.applyToSourceUnits((SourceUnitOperation) this);
-            } else if (this instanceof PrimaryClassNodeOperation) {
-                unit.applyToPrimaryClassNodes((PrimaryClassNodeOperation) this);
-            } else {
-                unit.applyToGeneratedGroovyClasses((GroovyClassOperation) this);
+        void doPhaseOperation(CompilationUnit unit);
+    }
+
+    @FunctionalInterface
+    public interface ISourceUnitOperation extends PhaseOperation {
+        void call(SourceUnit source) throws CompilationFailedException;
+
+        /**
+         * A loop driver for applying operations to all SourceUnits.
+         * Automatically skips units that have already been processed
+         * through the current phase.
+         */
+        @Override
+        default void doPhaseOperation(final CompilationUnit unit) throws CompilationFailedException {
+            for (String name : unit.sources.keySet()) {
+                SourceUnit source = unit.sources.get(name);
+                if (source.phase < unit.phase || (source.phase == unit.phase && !source.phaseComplete)) {
+                    try {
+                        this.call(source);
+                    } catch (CompilationFailedException e) {
+                        throw e;
+                    } catch (Exception e) {
+                        GroovyBugError gbe = new GroovyBugError(e);
+                        unit.changeBugText(gbe, source);
+                        throw gbe;
+                    } catch (GroovyBugError e) {
+                        unit.changeBugText(e, source);
+                        throw e;
+                    }
+                }
             }
+            unit.getErrorCollector().failIfErrors();
         }
     }
 
-    /**
-     * A callback interface for use in the applyToSourceUnits loop driver.
-     */
-    // TODO: convert to functional interface
-    public abstract static class SourceUnitOperation implements PhaseOperation {
-        public abstract void call(SourceUnit source) throws CompilationFailedException;
-    }
+    //---------------------------------------------------------------------------
+    // LOOP SIMPLIFICATION FOR PRIMARY ClassNode OPERATIONS
 
-    /**
-     * A loop driver for applying operations to all SourceUnits.
-     * Automatically skips units that have already been processed
-     * through the current phase.
-     */
-    public void applyToSourceUnits(final SourceUnitOperation body) throws CompilationFailedException {
-        for (String name : sources.keySet()) {
-            SourceUnit source = sources.get(name);
-            if ((source.phase < phase) || (source.phase == phase && !source.phaseComplete)) {
+    @FunctionalInterface
+    public interface IPrimaryClassNodeOperation extends PhaseOperation {
+        void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException;
+
+        /**
+         * A loop driver for applying operations to all primary ClassNodes in
+         * our AST.  Automatically skips units that have already been processed
+         * through the current phase.
+         */
+        @Override
+        default void doPhaseOperation(final CompilationUnit unit) throws CompilationFailedException {
+            for (ClassNode classNode : unit.getPrimaryClassNodes(this.needSortedInput())) {
+                SourceUnit context = null;
                 try {
-                    body.call(source);
+                    context = classNode.getModule().getContext();
+                    if (context == null || context.phase < unit.phase || (context.phase == unit.phase && !context.phaseComplete)) {
+                        int offset = 1;
+                        for (Iterator<InnerClassNode> it = classNode.getInnerClasses(); it.hasNext(); ) {
+                            it.next();
+                            offset += 1;
+                        }
+                        this.call(context, new GeneratorContext(unit.getAST(), offset), classNode);
+                    }
                 } catch (CompilationFailedException e) {
-                    throw e;
-                } catch (Exception e) {
-                    GroovyBugError gbe = new GroovyBugError(e);
-                    changeBugText(gbe, source);
+                    // fall through
+                } catch (NullPointerException npe) {
+                    GroovyBugError gbe = new GroovyBugError("unexpected NullPointerException", npe);
+                    unit.changeBugText(gbe, context);
                     throw gbe;
                 } catch (GroovyBugError e) {
-                    changeBugText(e, source);
+                    unit.changeBugText(e, context);
                     throw e;
+                } catch (NoClassDefFoundError | Exception e) {
+                    // effort to get more logging in case a dependency of a class is loaded
+                    // although it shouldn't have
+                    unit.convertUncaughtExceptionToCompilationError(e);
                 }
             }
+            unit.getErrorCollector().failIfErrors();
         }
-        getErrorCollector().failIfErrors();
-    }
 
-    //---------------------------------------------------------------------------
-    // LOOP SIMPLIFICATION FOR PRIMARY ClassNode OPERATIONS
-
-    /**
-     * An callback interface for use in the applyToPrimaryClassNodes loop driver.
-     */
-    // TODO: convert to functional interface
-    public abstract static class PrimaryClassNodeOperation implements PhaseOperation {
-        public abstract void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException;
-
-        public boolean needSortedInput() {
+        default boolean needSortedInput() {
             return false;
         }
     }
 
-    // TODO: convert to functional interface
-    public abstract static class GroovyClassOperation implements PhaseOperation {
-        public abstract void call(GroovyClass groovyClass) throws CompilationFailedException;
+    @FunctionalInterface
+    public interface IGroovyClassOperation extends PhaseOperation {
+        void call(GroovyClass groovyClass) throws CompilationFailedException;
+
+        @Override
+        default void doPhaseOperation(final CompilationUnit unit) throws CompilationFailedException {
+            if (unit.phase != Phases.OUTPUT && !(unit.phase == Phases.CLASS_GENERATION && unit.phaseComplete)) {
+                throw new GroovyBugError("CompilationUnit not ready for output(). Current phase=" + unit.getPhaseDescription());
+            }
+
+            for (GroovyClass groovyClass : unit.getClasses()) {
+                try {
+                    this.call(groovyClass);
+                } catch (CompilationFailedException e) {
+                    // fall through
+                } catch (NullPointerException npe) {
+                    throw npe;
+                } catch (GroovyBugError e) {
+                    unit.changeBugText(e, null);
+                    throw e;
+                } catch (Exception e) {
+                    throw new GroovyBugError(e);
+                }
+            }
+            unit.getErrorCollector().failIfErrors();
+        }
     }
 
-    private static int getSuperClassCount(ClassNode element) {
+    private static int getSuperClassCount(ClassNode classNode) {
         int count = 0;
-        while (element != null) {
+        while (classNode != null) {
             count += 1;
-            element = element.getSuperClass();
+            classNode = classNode.getSuperClass();
         }
         return count;
     }
 
-    private int getSuperInterfaceCount(final ClassNode element) {
+    private static int getSuperInterfaceCount(final ClassNode classNode) {
         int count = 1;
-        ClassNode[] interfaces = element.getInterfaces();
-        for (ClassNode anInterface : interfaces) {
-            count = Math.max(count, getSuperInterfaceCount(anInterface) + 1);
+        for (ClassNode face : classNode.getInterfaces()) {
+            count = Math.max(count, getSuperInterfaceCount(face) + 1);
         }
         return count;
     }
 
     private List<ClassNode> getPrimaryClassNodes(final boolean sort) {
         List<ClassNode> unsorted = getAST().getModules().stream()
-            .flatMap(module -> module.getClasses().stream()).collect(Collectors.toList());
+            .flatMap(module -> module.getClasses().stream()).collect(toList());
 
         if (!sort) return unsorted;
 
@@ -1052,46 +1035,9 @@ public class CompilationUnit extends ProcessingUnit {
         return sorted;
     }
 
-    /**
-     * A loop driver for applying operations to all primary ClassNodes in
-     * our AST.  Automatically skips units that have already been processed
-     * through the current phase.
-     */
-    public void applyToPrimaryClassNodes(final PrimaryClassNodeOperation body) throws CompilationFailedException {
-        for (ClassNode classNode : getPrimaryClassNodes(body.needSortedInput())) {
-            SourceUnit context = null;
-            try {
-                context = classNode.getModule().getContext();
-                if (context == null || context.phase < phase || (context.phase == phase && !context.phaseComplete)) {
-                    int offset = 1;
-                    for (Iterator<InnerClassNode> it = classNode.getInnerClasses(); it.hasNext(); ) {
-                        it.next();
-                        offset += 1;
-                    }
-                    body.call(context, new GeneratorContext(getAST(), offset), classNode);
-                }
-            } catch (CompilationFailedException e) {
-                // fall through, getErrorReporter().failIfErrors() will trigger
-            } catch (NullPointerException npe) {
-                GroovyBugError gbe = new GroovyBugError("unexpected NullPointerException", npe);
-                changeBugText(gbe, context);
-                throw gbe;
-            } catch (GroovyBugError e) {
-                changeBugText(e, context);
-                throw e;
-            } catch (NoClassDefFoundError | Exception e) {
-                // effort to get more logging in case a dependency of a class is loaded
-                // although it shouldn't have
-                convertUncaughtExceptionToCompilationError(e);
-            }
-        }
-
-        getErrorCollector().failIfErrors();
-    }
-
     private void convertUncaughtExceptionToCompilationError(final Throwable e) {
-        // check the exception for a nested compilation exception
         ErrorCollector nestedCollector = null;
+        // check the exception for a nested compilation exception
         for (Throwable next = e.getCause(); next != e && next != null; next = next.getCause()) {
             if (!(next instanceof MultipleCompilationErrorsException)) continue;
             MultipleCompilationErrorsException mcee = (MultipleCompilationErrorsException) next;
@@ -1103,37 +1049,50 @@ public class CompilationUnit extends ProcessingUnit {
             getErrorCollector().addCollectorContents(nestedCollector);
         } else {
             Exception err = e instanceof Exception?((Exception)e):new RuntimeException(e);
-            getErrorCollector().addError(new ExceptionMessage(err, configuration.getDebug(), this));
+            getErrorCollector().addError(new ExceptionMessage(err, debug, this));
         }
     }
 
-    public void applyToGeneratedGroovyClasses(final GroovyClassOperation body) throws CompilationFailedException {
-        if (this.phase != Phases.OUTPUT && !(this.phase == Phases.CLASS_GENERATION && this.phaseComplete)) {
-            throw new GroovyBugError("CompilationUnit not ready for output(). Current phase=" + getPhaseDescription());
-        }
+    private void changeBugText(final GroovyBugError e, final SourceUnit context) {
+        e.setBugText("exception in phase '" + getPhaseDescription() + "' in source unit '" + (context != null ? context.getName() : "?") + "' " + e.getBugText());
+    }
 
-        for (GroovyClass gclass : this.generatedClasses) {
-            //
-            // Get the class and calculate its filesystem name
-            //
-            try {
-                body.call(gclass);
-            } catch (CompilationFailedException e) {
-                // fall through, getErrorReporter().failIfErrors() will trigger
-            } catch (NullPointerException npe) {
-                throw npe;
-            } catch (GroovyBugError e) {
-                changeBugText(e, null);
-                throw e;
-            } catch (Exception e) {
-                throw new GroovyBugError(e);
-            }
-        }
+    //--------------------------------------------------------------------------
 
-        getErrorCollector().failIfErrors();
+    @Deprecated
+    public void addPhaseOperation(final GroovyClassOperation op) {
+        addPhaseOperation((IGroovyClassOperation) op);
     }
 
-    private void changeBugText(final GroovyBugError e, final SourceUnit context) {
-        e.setBugText("exception in phase '" + getPhaseDescription() + "' in source unit '" + (context != null ? context.getName() : "?") + "' " + e.getBugText());
+    @Deprecated
+    public void addPhaseOperation(final SourceUnitOperation op, final int phase) {
+        addPhaseOperation((ISourceUnitOperation) op, phase);
+    }
+
+    @Deprecated
+    public void addPhaseOperation(final PrimaryClassNodeOperation op, final int phase) {
+        addPhaseOperation((IPrimaryClassNodeOperation) op, phase);
+    }
+
+    @Deprecated
+    public void addFirstPhaseOperation(final PrimaryClassNodeOperation op, final int phase) {
+        addFirstPhaseOperation((IPrimaryClassNodeOperation) op, phase);
+    }
+
+    @Deprecated
+    public void addNewPhaseOperation(final SourceUnitOperation op, final int phase) {
+        addNewPhaseOperation((ISourceUnitOperation) op, phase);
+    }
+
+    @Deprecated
+    public abstract static class SourceUnitOperation implements ISourceUnitOperation {
+    }
+
+    @Deprecated
+    public abstract static class GroovyClassOperation implements IGroovyClassOperation {
+    }
+
+    @Deprecated
+    public abstract static class PrimaryClassNodeOperation implements IPrimaryClassNodeOperation {
     }
 }
diff --git a/src/main/java/org/codehaus/groovy/control/CompilerConfiguration.java b/src/main/java/org/codehaus/groovy/control/CompilerConfiguration.java
index 25d8e9c..214bb98 100644
--- a/src/main/java/org/codehaus/groovy/control/CompilerConfiguration.java
+++ b/src/main/java/org/codehaus/groovy/control/CompilerConfiguration.java
@@ -1065,7 +1065,7 @@ public class CompilerConfiguration {
      * META-INF/services/org.codehaus.groovy.transform.ASTTransformation file.
      * If you explicitly add a global AST transformation in your compilation process,
      * for example using the {@link org.codehaus.groovy.control.customizers.ASTTransformationCustomizer} or
-     * using a {@link org.codehaus.groovy.control.CompilationUnit.PrimaryClassNodeOperation},
+     * using a {@link org.codehaus.groovy.control.CompilationUnit.IPrimaryClassNodeOperation},
      * then nothing will prevent the transformation from being loaded.
      *
      * @param disabledGlobalASTTransformations a set of fully qualified class names of global AST transformations
diff --git a/src/main/java/org/codehaus/groovy/control/StaticImportVisitor.java b/src/main/java/org/codehaus/groovy/control/StaticImportVisitor.java
index 1dea516..e1dd084 100644
--- a/src/main/java/org/codehaus/groovy/control/StaticImportVisitor.java
+++ b/src/main/java/org/codehaus/groovy/control/StaticImportVisitor.java
@@ -68,7 +68,7 @@ import static org.codehaus.groovy.ast.tools.ClosureUtils.getParametersSafe;
 public class StaticImportVisitor extends ClassCodeExpressionTransformer {
     private ClassNode currentClass;
     private MethodNode currentMethod;
-    private SourceUnit source;
+    private SourceUnit sourceUnit;
     private boolean inSpecialConstructorCall;
     private boolean inClosure;
     private boolean inPropertyExpression;
@@ -77,10 +77,19 @@ public class StaticImportVisitor extends ClassCodeExpressionTransformer {
     private boolean inAnnotation;
     private boolean inLeftExpression;
 
-    public void visitClass(ClassNode node, SourceUnit source) {
-        this.currentClass = node;
-        this.source = source;
-        super.visitClass(node);
+    public StaticImportVisitor(final ClassNode classNode, final SourceUnit sourceUnit) {
+        this.currentClass = classNode;
+        this.sourceUnit = sourceUnit;
+    }
+
+    /**
+     * Call {@link #StaticImportVisitor(ClassNode,SourceUnit)} then {@link #visitClass(ClassNode)}.
+     */
+    @Deprecated
+    public void visitClass(final ClassNode classNode, final SourceUnit sourceUnit) {
+        this.currentClass = classNode;
+        this.sourceUnit = sourceUnit;
+        visitClass(classNode);
     }
 
     @Override
@@ -565,7 +574,8 @@ public class StaticImportVisitor extends ClassCodeExpressionTransformer {
         return new StaticMethodCallExpression(type.getPlainNodeReference(), name, args);
     }
 
+    @Override
     protected SourceUnit getSourceUnit() {
-        return source;
+        return sourceUnit;
     }
 }
diff --git a/src/main/java/org/codehaus/groovy/control/customizers/CompilationCustomizer.java b/src/main/java/org/codehaus/groovy/control/customizers/CompilationCustomizer.java
index 9d0b565..63583c5 100644
--- a/src/main/java/org/codehaus/groovy/control/customizers/CompilationCustomizer.java
+++ b/src/main/java/org/codehaus/groovy/control/customizers/CompilationCustomizer.java
@@ -29,10 +29,10 @@ import org.codehaus.groovy.control.CompilePhase;
  *
  * @since 1.8.0
  */
-public abstract class CompilationCustomizer extends CompilationUnit.PrimaryClassNodeOperation {
+public abstract class CompilationCustomizer implements CompilationUnit.IPrimaryClassNodeOperation {
     private final CompilePhase phase;
 
-    public CompilationCustomizer(CompilePhase phase) {
+    public CompilationCustomizer(final CompilePhase phase) {
         this.phase = phase;
     }
 
diff --git a/src/main/java/org/codehaus/groovy/tools/javac/JavaAwareCompilationUnit.java b/src/main/java/org/codehaus/groovy/tools/javac/JavaAwareCompilationUnit.java
index de646aa..f43141b 100644
--- a/src/main/java/org/codehaus/groovy/tools/javac/JavaAwareCompilationUnit.java
+++ b/src/main/java/org/codehaus/groovy/tools/javac/JavaAwareCompilationUnit.java
@@ -20,6 +20,7 @@ package org.codehaus.groovy.tools.javac;
 
 import groovy.lang.GroovyClassLoader;
 import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.GroovyClassVisitor;
 import org.codehaus.groovy.ast.ModuleNode;
 import org.codehaus.groovy.classgen.GeneratorContext;
 import org.codehaus.groovy.classgen.VariableScopeVisitor;
@@ -75,35 +76,24 @@ public class JavaAwareCompilationUnit extends CompilationUnit {
         this.stubGenerator = new JavaStubGenerator(generationGoal, false, useJava5, encoding);
         this.keepStubs = Boolean.TRUE.equals(options.get("keepStubs"));
 
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(SourceUnit source, GeneratorContext context, ClassNode node) throws CompilationFailedException {
-                if (!javaSources.isEmpty()) {
-                    VariableScopeVisitor scopeVisitor = new VariableScopeVisitor(source);
-                    scopeVisitor.visitClass(node);
-                    new JavaAwareResolveVisitor(JavaAwareCompilationUnit.this).startResolving(node, source);
-                    AnnotationConstantsVisitor acv = new AnnotationConstantsVisitor();
-                    acv.visitClass(node, source);
-                }
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            if (!javaSources.isEmpty()) {
+                new VariableScopeVisitor(source).visitClass(classNode);
+                new JavaAwareResolveVisitor(this).startResolving(classNode, source);
+                new AnnotationConstantsVisitor().visitClass(classNode, source);
             }
         }, Phases.CONVERSION);
-        addPhaseOperation(new CompilationUnit.PrimaryClassNodeOperation() {
-            @Override
-            public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException {
-                ASTTransformationCollectorCodeVisitor collector =
-                        new ASTTransformationCollectorCodeVisitor(source, JavaAwareCompilationUnit.this.getTransformLoader());
-                collector.visitClass(classNode);
-            }
+
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            GroovyClassVisitor visitor = new ASTTransformationCollectorCodeVisitor(source, getTransformLoader());
+            visitor.visitClass(classNode);
         }, Phases.CONVERSION);
 
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException {
-                try {
-                    if (!javaSources.isEmpty()) stubGenerator.generateClass(classNode);
-                } catch (FileNotFoundException fnfe) {
-                    source.addException(fnfe);
-                }
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            try {
+                if (!javaSources.isEmpty()) stubGenerator.generateClass(classNode);
+            } catch (FileNotFoundException fnfe) {
+                source.addException(fnfe);
             }
         }, Phases.CONVERSION);
     }
diff --git a/src/main/java/org/codehaus/groovy/tools/javac/JavaStubCompilationUnit.java b/src/main/java/org/codehaus/groovy/tools/javac/JavaStubCompilationUnit.java
index e5aefae..46e1d42 100644
--- a/src/main/java/org/codehaus/groovy/tools/javac/JavaStubCompilationUnit.java
+++ b/src/main/java/org/codehaus/groovy/tools/javac/JavaStubCompilationUnit.java
@@ -53,23 +53,17 @@ public class JavaStubCompilationUnit extends CompilationUnit {
         String encoding = configuration.getSourceEncoding();
         stubGenerator = new JavaStubGenerator(destDir, false, useJava5, encoding);
 
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(SourceUnit source, GeneratorContext context, ClassNode node) throws CompilationFailedException {
-                VariableScopeVisitor scopeVisitor = new VariableScopeVisitor(source);
-                scopeVisitor.visitClass(node);
-                new JavaAwareResolveVisitor(JavaStubCompilationUnit.this).startResolving(node, source);
-            }
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            new VariableScopeVisitor(source).visitClass(classNode);
+            new JavaAwareResolveVisitor(this).startResolving(classNode, source);
         }, Phases.CONVERSION);
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode node) throws CompilationFailedException {
-                try {
-                    stubGenerator.generateClass(node);
-                    stubCount++;
-                } catch (FileNotFoundException e) {
-                    source.addException(e);
-                }
+
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            try {
+                stubGenerator.generateClass(classNode);
+                stubCount += 1;
+            } catch (FileNotFoundException e) {
+                source.addException(e);
             }
         }, Phases.CONVERSION);
     }
diff --git a/src/main/java/org/codehaus/groovy/transform/ASTTransformationVisitor.java b/src/main/java/org/codehaus/groovy/transform/ASTTransformationVisitor.java
index c2c508f..ea950a6 100644
--- a/src/main/java/org/codehaus/groovy/transform/ASTTransformationVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/ASTTransformationVisitor.java
@@ -27,10 +27,10 @@ import org.codehaus.groovy.ast.AnnotatedNode;
 import org.codehaus.groovy.ast.AnnotationNode;
 import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
 import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.GroovyClassVisitor;
 import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.classgen.GeneratorContext;
 import org.codehaus.groovy.control.ASTTransformationsContext;
-import org.codehaus.groovy.control.CompilationFailedException;
 import org.codehaus.groovy.control.CompilationUnit;
 import org.codehaus.groovy.control.CompilePhase;
 import org.codehaus.groovy.control.Phases;
@@ -197,18 +197,14 @@ public final class ASTTransformationVisitor extends ClassCodeVisitorSupport {
 
 
     public static void addPhaseOperations(final CompilationUnit compilationUnit) {
-        final ASTTransformationsContext context = compilationUnit.getASTTransformationsContext();
+        ASTTransformationsContext context = compilationUnit.getASTTransformationsContext();
         addGlobalTransforms(context);
 
-        compilationUnit.addPhaseOperation(new CompilationUnit.PrimaryClassNodeOperation() {
-            public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException {
-                ASTTransformationCollectorCodeVisitor collector =
-                    new ASTTransformationCollectorCodeVisitor(source, compilationUnit.getTransformLoader());
-                collector.visitClass(classNode);
-            }
+        compilationUnit.addPhaseOperation((final SourceUnit source, final GeneratorContext ignore, final ClassNode classNode) -> {
+            GroovyClassVisitor visitor = new ASTTransformationCollectorCodeVisitor(source, compilationUnit.getTransformLoader());
+            visitor.visitClass(classNode);
         }, Phases.SEMANTIC_ANALYSIS);
         for (CompilePhase phase : CompilePhase.values()) {
-            final ASTTransformationVisitor visitor = new ASTTransformationVisitor(phase, context);
             switch (phase) {
                 case INITIALIZATION:
                 case PARSING:
@@ -217,11 +213,10 @@ public final class ASTTransformationVisitor extends ClassCodeVisitorSupport {
                     break;
 
                 default:
-                    compilationUnit.addPhaseOperation(new CompilationUnit.PrimaryClassNodeOperation() {
-                        public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException {
-                            visitor.source = source;
-                            visitor.visitClass(classNode);
-                        }
+                    compilationUnit.addPhaseOperation((final SourceUnit source, final GeneratorContext ignore, final ClassNode classNode) -> {
+                        ASTTransformationVisitor visitor = new ASTTransformationVisitor(phase, context);
+                        visitor.source = source;
+                        visitor.visitClass(classNode);
                     }, phase.getPhaseNumber());
                     break;
 
@@ -322,8 +317,8 @@ public final class ASTTransformationVisitor extends ClassCodeVisitorSupport {
         GroovyClassLoader transformLoader = compilationUnit.getTransformLoader();
         for (Map.Entry<String, URL> entry : transformNames.entrySet()) {
             try {
-                Class gTransClass = transformLoader.loadClass(entry.getKey(), false, true, false);
-                GroovyASTTransformation transformAnnotation = (GroovyASTTransformation) gTransClass.getAnnotation(GroovyASTTransformation.class);
+                Class<?> gTransClass = transformLoader.loadClass(entry.getKey(), false, true, false);
+                GroovyASTTransformation transformAnnotation = gTransClass.getAnnotation(GroovyASTTransformation.class);
                 if (transformAnnotation == null) {
                     compilationUnit.getErrorCollector().addWarning(new WarningMessage(
                         WarningMessage.POSSIBLE_ERRORS,
@@ -335,14 +330,12 @@ public final class ASTTransformationVisitor extends ClassCodeVisitorSupport {
                     continue;
                 }
                 if (ASTTransformation.class.isAssignableFrom(gTransClass)) {
-                    final ASTTransformation instance = (ASTTransformation)gTransClass.getDeclaredConstructor().newInstance();
+                    ASTTransformation instance = (ASTTransformation) gTransClass.getDeclaredConstructor().newInstance();
                     if (instance instanceof CompilationUnitAware) {
                         ((CompilationUnitAware)instance).setCompilationUnit(compilationUnit);
                     }
-                    CompilationUnit.SourceUnitOperation suOp = new CompilationUnit.SourceUnitOperation() {
-                        public void call(SourceUnit source) throws CompilationFailedException {
-                            instance.visit(new ASTNode[] {source.getAST()}, source);
-                        }
+                    CompilationUnit.ISourceUnitOperation suOp = source -> {
+                        instance.visit(new ASTNode[] {source.getAST()}, source);
                     };
                     if (isFirstScan) {
                         compilationUnit.addPhaseOperation(suOp, transformAnnotation.phase().getPhaseNumber());
diff --git a/src/main/java/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java b/src/main/java/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java
index 99b72c1..3dec6b8 100644
--- a/src/main/java/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java
@@ -49,14 +49,13 @@ import org.codehaus.groovy.ast.stmt.ExpressionStatement;
 import org.codehaus.groovy.ast.stmt.ForStatement;
 import org.codehaus.groovy.ast.stmt.Statement;
 import org.codehaus.groovy.ast.tools.GeneralUtils;
-import org.codehaus.groovy.classgen.GeneratorContext;
 import org.codehaus.groovy.classgen.asm.InvocationWriter;
 import org.codehaus.groovy.classgen.asm.MopWriter;
 import org.codehaus.groovy.classgen.asm.TypeChooser;
 import org.codehaus.groovy.classgen.asm.WriterControllerFactory;
 import org.codehaus.groovy.classgen.asm.sc.StaticCompilationMopWriter;
 import org.codehaus.groovy.classgen.asm.sc.StaticTypesTypeChooser;
-import org.codehaus.groovy.control.CompilationUnit;
+import org.codehaus.groovy.control.CompilationUnit.IPrimaryClassNodeOperation;
 import org.codehaus.groovy.control.SourceUnit;
 import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport;
 import org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor;
@@ -155,13 +154,10 @@ public class StaticCompilationVisitor extends StaticTypeCheckingVisitor {
     private void addDynamicOuterClassAccessorsCallback(final ClassNode outer) {
         if (outer != null) {
             if (!isStaticallyCompiled(outer) && outer.getNodeMetaData(DYNAMIC_OUTER_NODE_CALLBACK) == null) {
-                outer.putNodeMetaData(DYNAMIC_OUTER_NODE_CALLBACK, new CompilationUnit.PrimaryClassNodeOperation() {
-                    @Override
-                    public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) {
-                        if (classNode == outer) {
-                            addPrivateBridgeMethods(classNode);
-                            addPrivateFieldsAccessors(classNode);
-                        }
+                outer.putNodeMetaData(DYNAMIC_OUTER_NODE_CALLBACK, (IPrimaryClassNodeOperation) (source, context, classNode) -> {
+                    if (classNode == outer) {
+                        addPrivateBridgeMethods(classNode);
+                        addPrivateFieldsAccessors(classNode);
                     }
                 });
             }
diff --git a/src/main/java/org/codehaus/groovy/transform/trait/TraitASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/trait/TraitASTTransformation.java
index a12584c..70952bf 100644
--- a/src/main/java/org/codehaus/groovy/transform/trait/TraitASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/trait/TraitASTTransformation.java
@@ -27,6 +27,7 @@ import org.codehaus.groovy.ast.ClassHelper;
 import org.codehaus.groovy.ast.ClassNode;
 import org.codehaus.groovy.ast.FieldNode;
 import org.codehaus.groovy.ast.GenericsType;
+import org.codehaus.groovy.ast.GroovyClassVisitor;
 import org.codehaus.groovy.ast.InnerClassNode;
 import org.codehaus.groovy.ast.MethodNode;
 import org.codehaus.groovy.ast.Parameter;
@@ -47,7 +48,6 @@ import org.codehaus.groovy.ast.tools.GeneralUtils;
 import org.codehaus.groovy.classgen.GeneratorContext;
 import org.codehaus.groovy.classgen.VariableScopeVisitor;
 import org.codehaus.groovy.classgen.Verifier;
-import org.codehaus.groovy.control.CompilationFailedException;
 import org.codehaus.groovy.control.CompilationUnit;
 import org.codehaus.groovy.control.CompilePhase;
 import org.codehaus.groovy.control.SourceUnit;
@@ -340,19 +340,16 @@ public class TraitASTTransformation extends AbstractASTTransformation implements
     }
 
     private void registerASTTransformations(final ClassNode helper) {
-        ASTTransformationCollectorCodeVisitor collector = new ASTTransformationCollectorCodeVisitor(
-                unit, compilationUnit.getTransformLoader()
-        );
-        collector.visitClass(helper);
+        {
+            GroovyClassVisitor visitor = new ASTTransformationCollectorCodeVisitor(unit, compilationUnit.getTransformLoader());
+            visitor.visitClass(helper);
+        }
         // Perform an additional phase which has to be done *after* type checking
-        compilationUnit.addPhaseOperation(new CompilationUnit.PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                if (classNode==helper) {
-                    PostTypeCheckingExpressionReplacer replacer = new PostTypeCheckingExpressionReplacer(source);
-                    replacer.visitClass(helper);
-                }
-            }
+        compilationUnit.addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            if (classNode != helper) return;
+
+            GroovyClassVisitor visitor = new PostTypeCheckingExpressionReplacer(source);
+            visitor.visitClass(helper);
         }, CompilePhase.INSTRUCTION_SELECTION.getPhaseNumber());
     }
 
diff --git a/src/test/groovy/lang/GroovyClassLoaderTest.groovy b/src/test/groovy/lang/GroovyClassLoaderTest.groovy
index 9466beb..41a41e3 100644
--- a/src/test/groovy/lang/GroovyClassLoaderTest.groovy
+++ b/src/test/groovy/lang/GroovyClassLoaderTest.groovy
@@ -270,13 +270,15 @@ class GroovyClassLoaderTestCustomGCL extends GroovyClassLoader {
     }
 }
 
-class GroovyClassLoaderTestPropertyAdder extends CompilationUnit.PrimaryClassNodeOperation {
+class GroovyClassLoaderTestPropertyAdder implements CompilationUnit.IPrimaryClassNodeOperation {
+    @Override
     void call(SourceUnit source, GeneratorContext context, ClassNode classNode) {
         classNode.addProperty("id", ClassNode.ACC_PUBLIC, ClassHelper.long_TYPE, null, null, null)
     }
 }
 
 class GroovyClassLoaderTestCustomPhaseOperation extends GroovyClassLoader {
+    @Override
     CompilationUnit createCompilationUnit(CompilerConfiguration config, CodeSource source) {
         def cu = super.createCompilationUnit(config, source)
         cu.addPhaseOperation(new GroovyClassLoaderTestPropertyAdder(), Phases.CONVERSION)
diff --git a/src/test/org/codehaus/groovy/ClosureAndInnerClassNodeStructureTest.groovy b/src/test/org/codehaus/groovy/ClosureAndInnerClassNodeStructureTest.groovy
index 9fc89b0..75d3196 100644
--- a/src/test/org/codehaus/groovy/ClosureAndInnerClassNodeStructureTest.groovy
+++ b/src/test/org/codehaus/groovy/ClosureAndInnerClassNodeStructureTest.groovy
@@ -20,7 +20,6 @@ package org.codehaus.groovy
 
 import groovy.test.GroovyTestCase
 import org.codehaus.groovy.control.CompilationUnit
-import org.codehaus.groovy.control.CompilationUnit.PrimaryClassNodeOperation
 import org.codehaus.groovy.classgen.GeneratorContext
 import org.codehaus.groovy.control.SourceUnit
 import org.codehaus.groovy.control.Phases
@@ -53,7 +52,8 @@ class ClosureAndInnerClassNodeStructureTest extends GroovyTestCase {
 
         def classNodes = [:]
 
-        cu.addPhaseOperation(new PrimaryClassNodeOperation() {
+        cu.addPhaseOperation(new CompilationUnit.IPrimaryClassNodeOperation() {
+            @Override
             void call(SourceUnit source, GeneratorContext context, ClassNode cn) {
                 def recurse = { ClassNode node ->
                     classNodes[node.name] = node
diff --git a/src/test/org/codehaus/groovy/tools/gse/DependencyTest.java b/src/test/org/codehaus/groovy/tools/gse/DependencyTest.java
index e903298..5788b39 100644
--- a/src/test/org/codehaus/groovy/tools/gse/DependencyTest.java
+++ b/src/test/org/codehaus/groovy/tools/gse/DependencyTest.java
@@ -18,39 +18,32 @@
  */
 package org.codehaus.groovy.tools.gse;
 
-import java.io.StringBufferInputStream;
-import java.util.Set;
-
+import groovy.test.GroovyTestCase;
 import org.codehaus.groovy.ast.ClassNode;
 import org.codehaus.groovy.classgen.GeneratorContext;
-import org.codehaus.groovy.control.CompilationFailedException;
 import org.codehaus.groovy.control.CompilationUnit;
 import org.codehaus.groovy.control.Phases;
 import org.codehaus.groovy.control.SourceUnit;
 
-import groovy.test.GroovyTestCase;
+import java.io.StringBufferInputStream;
+import java.util.Set;
 
 @SuppressWarnings("deprecation")
 public class DependencyTest extends GroovyTestCase {
     private CompilationUnit cu;
     StringSetMap cache;
-    
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        cu = new CompilationUnit();
         cache = new StringSetMap();
-        cu.addPhaseOperation(new CompilationUnit.PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, GeneratorContext context, ClassNode classNode) 
-                throws CompilationFailedException 
-            {   
-                DependencyTracker dt = new DependencyTracker(source,cache);
-                dt.visitClass(classNode);
-            }
+        cu = new CompilationUnit();
+        cu.addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            DependencyTracker dt = new DependencyTracker(source, cache);
+            dt.visitClass(classNode);
         }, Phases.CLASS_GENERATION);
     }
-    
+
     public void testDep(){
         cu.addSource("testDep.gtest", new StringBufferInputStream(
                 "class C1 {}\n" +
@@ -64,22 +57,22 @@ public class DependencyTest extends GroovyTestCase {
         assertEquals(cache.get("C1").size(),1);
         assertEquals(cache.get("C2").size(),1);
         assertEquals(cache.get("C3").size(),1);
-        
+
         Set<String> dep = cache.get("A1");
         assertEquals(dep.size(),2);
         assertTrue(dep.contains("C1"));
-        
+
         dep = cache.get("A2");
         assertEquals(dep.size(),2);
         assertTrue(dep.contains("C2"));
-        
+
         dep = cache.get("A3");
         assertEquals(dep.size(),4);
         assertTrue(dep.contains("C1"));
         assertTrue(dep.contains("C2"));
         assertTrue(dep.contains("C3"));
     }
-    
+
     public void testTransitiveDep(){
         cu.addSource("testTransitiveDep.gtest", new StringBufferInputStream(
                 "class A1 {}\n" +
@@ -88,19 +81,19 @@ public class DependencyTest extends GroovyTestCase {
         ));
         cu.compile(Phases.CLASS_GENERATION);
         cache.makeTransitiveHull();
-         
+
         Set<String> dep = cache.get("A1");
         assertEquals(dep.size(),1);
-        
+
         dep = cache.get("A2");
         assertEquals(dep.size(),2);
         assertTrue(dep.contains("A1"));
-        
+
         dep = cache.get("A3");
         assertEquals(dep.size(),3);
         assertTrue(dep.contains("A1"));
         assertTrue(dep.contains("A2"));
     }
-    
+
 
 }
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstNodeToScriptAdapter.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstNodeToScriptAdapter.groovy
index 292a8ae..1edda5f 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstNodeToScriptAdapter.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstNodeToScriptAdapter.groovy
@@ -95,7 +95,6 @@ import org.codehaus.groovy.classgen.GeneratorContext
 import org.codehaus.groovy.classgen.Verifier
 import org.codehaus.groovy.control.CompilationFailedException
 import org.codehaus.groovy.control.CompilationUnit
-import org.codehaus.groovy.control.CompilationUnit.PrimaryClassNodeOperation
 import org.codehaus.groovy.control.CompilePhase
 import org.codehaus.groovy.control.CompilerConfiguration
 import org.codehaus.groovy.control.SourceUnit
@@ -189,7 +188,7 @@ and [compilephase] is a valid Integer based org.codehaus.groovy.control.CompileP
  * An adapter from ASTNode tree to source code.
  */
 @CompileStatic
-class AstNodeToScriptVisitor extends PrimaryClassNodeOperation implements GroovyCodeVisitor, GroovyClassVisitor {
+class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperation, GroovyClassVisitor, GroovyCodeVisitor {
 
     private final Writer _out
     Stack<String> classNameStack = new Stack<String>()
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ScriptToTreeNodeAdapter.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ScriptToTreeNodeAdapter.groovy
index d42f3ba..c9e78f4 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ScriptToTreeNodeAdapter.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ScriptToTreeNodeAdapter.groovy
@@ -98,7 +98,6 @@ import org.codehaus.groovy.classgen.GeneratorContext
 import org.codehaus.groovy.classgen.asm.BytecodeHelper
 import org.codehaus.groovy.control.CompilationFailedException
 import org.codehaus.groovy.control.CompilationUnit
-import org.codehaus.groovy.control.CompilationUnit.PrimaryClassNodeOperation
 import org.codehaus.groovy.control.CompilerConfiguration
 import org.codehaus.groovy.control.SourceUnit
 
@@ -275,7 +274,7 @@ class ScriptToTreeNodeAdapter {
 /**
  * This Node Operation builds up a root tree node for the viewer.
  */
-class TreeNodeBuildingNodeOperation extends PrimaryClassNodeOperation {
+class TreeNodeBuildingNodeOperation implements CompilationUnit.IPrimaryClassNodeOperation {
 
     final root
     final sourceCollected = new AtomicBoolean(false)
diff --git a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/util/ScriptVariableAnalyzer.groovy b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/util/ScriptVariableAnalyzer.groovy
index 47e7990..281a31c 100644
--- a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/util/ScriptVariableAnalyzer.groovy
+++ b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/util/ScriptVariableAnalyzer.groovy
@@ -69,10 +69,10 @@ class ScriptVariableAnalyzer {
     }
 
     /**
-     * custom PrimaryClassNodeOperation
+     * custom IPrimaryClassNodeOperation
      * to be able to hook our code visitor
      */
-    static class VisitorSourceOperation extends CompilationUnit.PrimaryClassNodeOperation {
+    static class VisitorSourceOperation implements CompilationUnit.IPrimaryClassNodeOperation {
 
         final GroovyClassVisitor visitor
 
diff --git a/subprojects/groovy-jsr223/src/test/java/org/codehaus/groovy/jsr223/JSR223SecurityTest.java b/subprojects/groovy-jsr223/src/test/java/org/codehaus/groovy/jsr223/JSR223SecurityTest.java
index 52b98a3..561beb2 100644
--- a/subprojects/groovy-jsr223/src/test/java/org/codehaus/groovy/jsr223/JSR223SecurityTest.java
+++ b/subprojects/groovy-jsr223/src/test/java/org/codehaus/groovy/jsr223/JSR223SecurityTest.java
@@ -25,7 +25,6 @@ import org.codehaus.groovy.ast.expr.MethodCallExpression;
 import org.codehaus.groovy.ast.stmt.ExpressionStatement;
 import org.codehaus.groovy.classgen.GeneratorContext;
 import org.codehaus.groovy.control.CompilationUnit;
-import org.codehaus.groovy.control.CompilationUnit.PrimaryClassNodeOperation;
 import org.codehaus.groovy.control.CompilerConfiguration;
 import org.codehaus.groovy.control.Phases;
 import org.codehaus.groovy.control.SourceUnit;
@@ -35,6 +34,7 @@ import org.junit.Test;
 import javax.script.ScriptEngine;
 import javax.script.ScriptEngineManager;
 import javax.script.ScriptException;
+
 import java.lang.reflect.Field;
 import java.security.CodeSource;
 import java.util.HashSet;
@@ -161,11 +161,12 @@ class CustomGroovyClassLoader extends GroovyClassLoader {
     }
 }
 
-class CustomPrimaryClassNodeOperation extends PrimaryClassNodeOperation {
-
+class CustomPrimaryClassNodeOperation implements CompilationUnit.IPrimaryClassNodeOperation {
+    @Override
     public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) {
-        for (Object statement : source.getAST().getStatementBlock().getStatements())
+        for (Object statement : source.getAST().getStatementBlock().getStatements()) {
             ((ExpressionStatement) statement).visit(new CustomCodeVisitorSupport());
+        }
     }
 }
 
diff --git a/subprojects/parser-antlr4/src/test/groovy/org/apache/groovy/parser/antlr4/util/AstDumper.groovy b/subprojects/parser-antlr4/src/test/groovy/org/apache/groovy/parser/antlr4/util/AstDumper.groovy
index a4a5616..8d3b8d4 100644
--- a/subprojects/parser-antlr4/src/test/groovy/org/apache/groovy/parser/antlr4/util/AstDumper.groovy
+++ b/subprojects/parser-antlr4/src/test/groovy/org/apache/groovy/parser/antlr4/util/AstDumper.groovy
@@ -147,7 +147,7 @@ class AstDumper {
  * An adapter from ASTNode tree to source code.
  */
 @CompileStatic
-class AstNodeToScriptVisitor extends CompilationUnit.PrimaryClassNodeOperation implements GroovyCodeVisitor, GroovyClassVisitor {
+class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperation, GroovyClassVisitor, GroovyCodeVisitor {
 
     private final Writer _out
     Stack<String> classNameStack = new Stack<String>()
@@ -164,6 +164,7 @@ class AstNodeToScriptVisitor extends CompilationUnit.PrimaryClassNodeOperation i
         this.scriptHasBeenVisited = false
     }
 
+    @Override
     void call(SourceUnit source, GeneratorContext context, ClassNode classNode) {
 
         visitPackage(source?.getAST()?.getPackage())