Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Since Groovy 2.0, users are allowed to use the optional @TypeChecked optional @TypeChecked annotation to activate type checking. In this mode, the compiler becomes more verbose and throws errors for, example, typos, non-existent methods, ... This comes with a few limitations though, most of them coming from the fact that Groovy remains inherently a dynamic language. For example, you wouldn't be able to use type checking on a markup builder:

...

How does it work?

Since Groovy 2.1.0, the @TypeChecked the @TypeChecked annotation supports an attribute called extensions. This parameter takes an array of strings corresponding to a list of type checking extensions scripts. Those scripts are found at compile time on classpath. For example, you would write:

...

 

Code Block
newMethod('foo') {
   // each time getReturnType on this method node will be called, this closure will be called!
   println 'Type checker called me!'
   classNodeFor(Foo) // return type
}

Should you need more than the name and return type, you can always create a new MethodNode by yourself.

Scoping

Scoping is very important in DSL type checking and is one of the reasons why we couldn't use a pointcut based approach to DSL type checking. Basically, you must be able to define very precisely when your extension applies and when it does not. Moreover, you must be able to handle situations that a regular type checker would not be able to handle, such as forward references:

Code Block
point a(1,1)
line a,b // b is referenced afterwards!
point b(5,2)

Say for example that you want to handle a builder:

Code Block
builder.foo {
   bar
   baz(bar)
}

Your extension, then, should only be active once you've entered the foo method, and inactive outside of this scope. But you could have complex situations like mutiple builders in the same file or embedded builders (builders in builders). While you should not try to fix all this from start (you must accept limitations to type checking), the type checker does offer a nice mechanism to handle this: a scoping stack, using the scopeEnter and scopeExit methods.

  • scopeEnter creates a new scope and puts it on top of the stack
  • scopeExits pops a scope from the stack
A scope consists of:
  • a parent scope
  • a map of custom data
If you want to look at the implementation, it's simply a LinkedHashMap (org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport.TypeCheckingScope), but it's quite powerful. For example, you can use such a scope to store a list of closures to be executed when you exit the scope. This is how you would handle forward references: 

 

Code Block
def scope = scopeEnter()
...
scope.secondPassChecks << { println 'executed later' }
...
scopeExit {
   secondPassChecks*.run() // execute deferred checks
}

That is to say, that if at some point you are not able to determine the type of an expression, or that you are not able to check at this point that an assignment is valid or not, you can still make the check later... This is a very powerful feature. Now, scopeEnter and scopeExit provide some interesting syntactic sugar:

Code Block
scopeEnter { // create a new scope
   secondPassChecks = [:] // initialize custom data in this scope (a maphere, a list of closures to be executed when scopeExit is called)
}

At anytime in the DSL, you can access the current scope using getCurrentScope() or more simply currentScope. The general schema would be, then:

  • determine a "pointcut" where you push a new scope on stack and initialize custom variables within this scope
  • using the various events, you can use the information stored in your custom scope to perform checks, defer checks,...
  • determine a "pointcut" where you exit the scope, call scopeExit and eventually perform additional checks

...

Take a look at Groovy 2.1 annotation aliases.

Where can I find examples?

You can download the source code for Groovy and take a look at the TypeCheckingExtensionsTest class which is linked to various extension scripts.

Is it possible to update the AST using an extension?

As you have access to the AST, there is nothing in theory that prevents you from modifying the AST. However, we do not recommand you to do so. First of all, you would explicitely break the contract of type checking, which is to annotate, and only annotate the AST. Type checking should not modify the AST tree because you wouldn't be able to guarantee anymore that code without the @TypeChecked annotation behaves the same without the annotation. Now, if your extension is meant to work with @CompileStatic, then you can modify the AST because this is indeed what @CompileStatic will eventually do. Static compilation doesn't guarantee the same semantics at dynamic Groovy so there is effectively a difference between code compiled with @CompileStatic and code compiled with @TypeChecked. It's up to you to choose whatever strategy you want to update the AST, but probably using an AST transformation that runs before type checking is easier (because the compiler does some smart bits before reaching bytecode generation).