Written on
Groovy 2.1: Type Checking Extensions
As of Groovy 2.0, the static type checking and compilation annotations @CompileStatic and @TypeChecked [0][1] can be applied on types and methods. Type checking helps us identify typing errors, or references to undeclared methods as shown in the following code example: @groovy.transform.TypeChecked
class Greeter {
void greet(String other) {
println "Hello, $oher".toUppercase()
}
}
[Static type checking] - The variable [oher] is undeclared.
at line: 4, column: 26
[Static type checking] - Cannot find matching method groovy.lang.GString#toUppercase(). Please check if the declared type is right and if the method exists.
at line: 4, column: 17
Custom Type Checking Extensions
Groovy 2.1 takes the conecpt of type checking even further and introduces a way to add custom type checking extensions. Custom type checking extensions are a great way to add compile-time checks that go beyond standard compile validations. As an example, let's assume we have a type checked Sql utility class to access our relational database. The Sql class has a method named eachRow receiving a SQL query of type String as first argument: sql.eachRow( 'select * from tableName' ) { println "$it.id -- ${it.firstName} --" }
@Grab(group='net.sf.jsqlparser', module='jsqlparser', version='0.8.0')
import net.sf.jsqlparser.parser.CCJSqlParserManager
afterMethodCall { mc ->
def receiver = mc.receiver
if (!isVariableExpression(receiver)) return
def method = getTargetMethod(mc)
if (classNodeFor(groovy.sql.Sql) == getType(receiver) && method.name == 'eachRow') {
def argList = getArguments(mc)
if (argList && isConstantExpression(argList[0])) {
def pm = new CCJSqlParserManager();
def sqlQuery = argList[0].text
try {
pm.parse(new StringReader(sqlQuery))
} catch (e) {
addStaticTypeError("SQL query is not valid: " + e, argList[0])
}
}
}
}
import groovy.sql.Sql
@groovy.transform.TypeChecked(extensions = ['SqlExtension.groovy'])
class SqlTest {
def test() {
def db = [url:'jdbc:hsqldb:mem:testDB', user:'sa', password:'', driver:'org.hsqldb.jdbc.JDBCDriver']
def sql = Sql.newInstance(db.url, db.user, db.password, db.driver)
sql.eachRow('select * frm PROJECT') { row ->
println row
}
}
}
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
SqlTest.groovy: 10: [Static type checking] - SQL query is not valid: net.sf.jsqlparser.JSQLParserException
@ line 10, column 16.
sql.eachRow('select * frm PROJECT') { row ->
^
GTCES
GroovyTypeCheckingExtensionSupport [3] is the class that handles type extension script and therefore defines all available event hooks. During its initialization phase, GTCES adds a custom script base class to our extension script and adds some default imports: CompilerConfiguration config = new CompilerConfiguration();
config.setScriptBaseClass("org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport.TypeCheckingDSL");
ImportCustomizer ic = new ImportCustomizer();
ic.addStarImports("org.codehaus.groovy.ast.expr");
ic.addStaticStars("org.codehaus.groovy.ast.ClassHelper");
ic.addStaticStars("org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport");
config.addCompilationCustomizers(ic);
private final static Map METHOD_ALIASES = Collections.unmodifiableMap(
new HashMap()
put("onMethodSelection", "onMethodSelection");
put("afterMethodCall", "afterMethodCall");
put("beforeMethodCall", "beforeMethodCall");
put("unresolvedVariable", "handleUnresolvedVariableExpression");
put("unresolvedProperty", "handleUnresolvedProperty");
put("unresolvedAttribute", "handleUnresolvedAttribute");
put("methodNotFound", "handleMissingMethod");
put("afterVisitMethod", "afterVisitMethod");
put("beforeVisitMethod", "beforeVisitMethod");
put("afterVisitClass", "afterVisitClass");
put("beforeVisitClass", "beforeVisitClass");
put("incompatibleAssignment", "handleIncompatibleAssignment");
put("setup","setup");
put("finish", "finish");
);
Conclusion
Groovy 2.1 adds custom type checking extensions to its @TypeChecked annotation. Type checking extensions can be used for additional type checks that go beyond standard compiler validations and offer a great way to perform domain-specific compile-time checks in DSLs or, more general, Groovy code! UPDATE Cédric Champeau released the type checking extensions documentation. If you want to implement a type checking extension, this is the place where to get a deeper insight into that topic.[3] org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport