I'm using Groovy to allow users to script and customize certain parts of my Java application. I am using static compilation of all user-provided scripts.
My question is: if my code expects to receive e.g. a value of type boolean as the result of the user script, is there any way for me to check whether or not the provided user script will always return a boolean without actually executing the script? In other words, how can I access the results of the type inference performed by the static groovy compiler? I would like to be able to tell the user "hey, your script does not always return a boolean" while they are editing the script content.
There is no straight-forward way, but it's possible. Boolean is especially hard as the compiler will happily apply all sorts of type-coersions (e.g. transform an int to boolean without complaining). I've had the exact same problem and did the following steps:
ASTTransformation to set the return type to boolean(not required, was an artifact of a half-working earlier iteration)For 1:
Extend
ClassCodeVisitorSupport, invisitMethodidentify all methods you want to return boolean (e.g. check for matching naming conventions)Set the
returnTypeof theMethodNodetoClassHelper.boolean_TYPEFor 2:
For the same methods as above call
org.codehaus.groovy.classgen.ReturnAdder.visitMethodFor 3:
Extend
AbstractTypeCheckingExtension, overrideafterVisitMethod. At this point theAbstractTypeCheckingExtensionwill have inferred the lower bound of all expressions inside the method. Use aClassCodeVisitorSupportsubclass and overridevisitReturnStatement. UsegetType(statement.expression)to get the inferred type. Note that this can be different fromstatement.expression.type(the type according to the AST). CalladdStaticTypeErroron non boolean type.For 4:
Extend
StaticTypesTransformationOverridenewVisitorand create a newStaticTypeCheckingVisitorand calladdTypeCheckingExtensionon it to add yourTypeCheckingExtensionGitHub Project
https://github.com/MeneDev/groovy-dsl-building-blocks-enforce-return-type
It may even be reusable as a dependency ;)