Search code examples
grailsgroovy

Why would Groovy apply an AST transformation twice if a method has parameters?


I'm running a small test using Groovy/Grails v5 to apply an AST transformation to a controller method. The method takes a single parameter, to represent a typical command object. I've added some logging to the AST transform class so within the visit() method, I can see that it is being applied.

If the controller method has no parameters, then I see the transform is being applied, no issue. However, if I add the parameter, the command object, to the method then I see my logging output twice.

The first time it's applied I can see the expected output that tells me it's being applied to the method, and that it's detected the parameter.

It then gets called a second time and tells me that it's being applied BUT this time it doesn't see any parameters.

This is the visit() method:

@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
public class AjaxResponseASTTransformation extends AbstractAstTransformation {

  private static final Logger LOG = LoggerFactory.getLogger(AjaxResponseASTTransformation.class);

  @Override
  public void visit(ASTNode[] nodes, SourceUnit source) {

    final MethodNode methodNode = (MethodNode) nodes[1];
    log("detected AjaxResponse annotation for method " + methodNode.getName());

    final Parameter[] parameters = methodNode.getParameters();

    if (parameters != null) {
      for (Parameter parameter : parameters) {
        log("Found Parameter : " + parameter.getName());
      }
      if (parameters.length == 0) {
        log("No parameters present - what gives??");
      }
    }
  }

and this is the controller method:

 @AjaxResponse
    def index(MyCommand myCommand) { render "" }

Building this project yields the following output:

20:01:00.537 [/127.0.0.1:51713 to /127.0.0.1:51712 workers] INFO mypackage.AjaxResponseASTTransformation - detected AjaxResponse annotation for method index
20:01:00.538 [/127.0.0.1:51713 to /127.0.0.1:51712 workers] INFO mypackage.AjaxResponseASTTransformation - Found Parameter : myCommand
20:01:00.538 [/127.0.0.1:51713 to /127.0.0.1:51712 workers] INFO mypackage.AjaxResponseASTTransformation - detected AjaxResponse annotation for method index
20:01:00.538 [/127.0.0.1:51713 to /127.0.0.1:51712 workers] INFO mypackage.AjaxResponseASTTransformation - No parameters present - what gives??

Can anybody explain this behaviour as to why it's being applied twice?


Solution

  • When you write a controller action like this:

    def index(MyCommand myCommand) { 
      render "" 
    }
    

    At compile time we generate a no-arg version of that method which looks something like this:

    def index() { 
      MyCommand c = new MyCommand()
      bindData c, request
      // subject c to dependency injection here, code ommitted
    
      // this call to validate is only generated if `MyCommand` is validateable...
      c.validate()
    
      // this will call your method
      index(c)
    }
    

    Because of that, there are 2 methods in the class named index even though you only wrote one.

    At grails-plugin-controllers/src/main/groovy/org/grails/compiler/web/ControllerActionTransformer.java#L395 is the part of the controller action transformer which is copying your annotation to the no-arg method.

    This by the way is the reason that method overloading is not supported for controller action methods. We have to generate the no-arg one with the same name, so you can't have 2 action methods with the same name that accept different parameters. We generate a compiler error indicating that when that scenario is encountered.