...
| Event name | Description |
|---|
| setup | | Called When | Called after the type checker finished initialization |
|---|
| Arguments | | | Usage | | Code Block |
|---|
setup {
// initialization
} |
Can be used to perform setup of your extension |
|
| finish | | Called When | Called after the type checker completed type checking |
|---|
| Arguments | | | Usage | | Code Block |
|---|
finish {
// finalization block
} |
Can be used to perform additional checks after the type checker has finished its job. |
|
| unresolvedVariable | | Called When | Called when the type checker finds an unresolved variable |
|---|
| Arguments | VariableExpression var | | Usage | | Code Block |
|---|
unresolvedVariable { var ->
if ('foo'==var.name) {
storeType(var, classNodeFor(List))
handled = true
}
} |
Allows the developer to help the type checker with user-injected variables. |
|
| unresolvedProperty | | Called When | Called when the type checker cannot find a property on the receiver |
|---|
| Arguments | PropertyExpression pexp | | Usage | | Code Block |
|---|
unresolvedProperty { pexp ->
if (getType(pexp.objectExpression)==classNodeFor(Foo)) {
storeType(pexp, classNodeFor(String)
handled = true
}
} |
Allows the developer to handle "dynamic" properties |
|
| unresolvedAttribute | | Called When | Called when the type checker cannot find an attribute on the receiver |
|---|
| Arguments | AttributeExpression aex | | Usage | | Code Block |
|---|
unresolvedAttribute { aex ->
if (getType(aex.objectExpression)==classNodeFor(Foo)) {
storeType(aex, classNodeFor(String)
handled = true
}
} |
Allows the developer to handle missing attributes |
|
| beforeMethodCall | | Called When | Called before the type checker starts type checking a method call expression |
|---|
| Arguments | MethodCall call | | Usage | | Code Block |
|---|
beforeMethodCall { call ->
if (call instanceof StaticMethodCall) {
// ...
handled = true
}
} |
Allows you to intercept method calls before the type checker performs its own checks. This is useful if you want to replace the default type checking with a custom one for a limited scope. In that case, you must set the handled flag to true, so that the type checker skips its own checks. |
|
| afterMethodCall | | Called When | Called once the type checker has finished type checking a method call |
|---|
| Arguments | MethodCall call | | Usage | | Code Block |
|---|
afterMethodCall { call ->
// ...
} |
Allow you to perform additional checks after the type checker has done its own checks. This is in particular useful if you want to perform the standard type checking tests but also want to ensure additional type safety, for example checking the arguments against each other. Note that afterMethodCall is called even if you did beforeMethodCall and set the handled flag to true. |
|
| onMethodSelection | | Called When | Called by the type checker when it finds a method appropriate for a method call |
|---|
| Arguments | Expression expr, MethodNode node | | Usage | | Code Block |
|---|
onMethodSelection { expr, node ->
if (node.declaringClass.name == 'Foo') {
// calling a method on 'Foo', let's perform additional checks!
}
} |
The type checker works by inferring argument types of a method call, then chooses a target method. If it finds one that corresponds, then it triggers this event. It is for example interesting if you want to react on a specific method call, such as entering the scope of a method that takes a closure as argument (as in builders). Please note that this event may be thrown for various types of expressions, not only method calls (binary expressions for example). |
|
| methodNotFound | | Called When | Called by the type checker when it fails to find an appropriate method for a method call |
|---|
| Arguments | ClassNode receiver, String name, ArgumentListExpression argList, ClassNode[] argTypes, MethodCall call | | Usage | | Code Block |
|---|
methodNotFound { receiver, name, argList, argTypes, call ->
// receiver is the inferred type of the receiver
// name is the name of the called method
// argList is the list of arguments the method was called with
// argTypes is the array of inferred types for each argument
// call is the method call for which we couldn't find a target method
} |
Unlike onMethodSelection, this event is sent when the type checker cannot find a target method for a method call (instance or static). It gives you the chance to intercept the error before it is sent to the user, but also set the target method. For this, you need to return a list of MethodNode. In most situations, you would either return: - an empty list, meaning that you didn't find a corresponding method
- a list with exactly one element, saying that there's no doubt about the target method
If you return more than one MethodNode, then the compiler would throw an error to the user stating that the method call is ambiguous, listing the possible methods. For convenience, if you want to return only one method, you are allowed to return it directly instead of wrapping it into a list. |
|
| beforeVisitMethod | | Called When | Called by the type checker before type checking a method body |
|---|
| Arguments | MethodNode node | | Usage | | Code Block |
|---|
beforeVisitMethod { methodNode ->
handled = true // tell the type checker we will handle the body by ourselves
} |
The type checker will call this method before starting to type check a method body. If you want, for example, to perform type checking by yourself instead of letting the type checker do it, you have to set the handled flag to true. This event can also be used to help defining the scope of your extension (for example, applying it only if you are inside method foo). |
|
| afterVisitMethod | | Called When | Called by the type checker after type checking a method body |
|---|
| Arguments | MethodNode node | | Usage | | Code Block |
|---|
afterVisitMethod { methodNode ->
// ...
} |
Gives you the opportunity to perform additional checks after a method body is visited by the type checker. This is useful if you collect information, for example, and want to perform additional checks once everything has been collected. |
|
| beforeVisitClass | | Called When | Called by the type checker before type checking a class |
|---|
| Arguments | ClassNode node | | Usage | | Code Block |
|---|
beforeVisitClass { classNode ->
// ...
} |
If a class is annotated with @TypeChecked, then before visiting the class, this event will be sent. It is also the case for inner classes defined inside a class annotated with @TypeChecked. It can help you define the scope of your extension, or you can even totally replace the visit of the type checker with a custom type checking implementation. For that, you would have to set the handled flag to true. |
|
| afterVisitClass | | Called When | Called by the type checker after having finished the visit of a type checked class |
|---|
| Arguments | ClassNode node | | Usage | | Code Block |
|---|
afterVisitClass { classNode ->
// perform additional checks
} |
Called for every class being type checked after the type checker finished its work. This includes classes annotated with @TypeChecked and any inner/anonymous class defined in the same class with is not skipped. |
|
| incompatibleAssignment | | Called When | Called when the type checker thinks that an assignment is incorrect, meaning that the right hand side of an assignment is incompatible with the left hand side. | | Arguments | ClassNode lhsType, ClassNode rhsType, Expression assignment | | Usage | | Code Block |
|---|
incompatibleAssignment { lhsType, rhsType, expr ->
if (isBinaryExpression(expr) && isAssignment(expr)) { ... }
} |
Gives the developper the ability to handle incorrect assignments. This is for example useful if a class overrides setProperty, because in that case it is possible that assigning a variable of one type to a property of another type is handled through that runtime mechanism. In that case, you can help the type checker just by telling it that the assignment is valid (using handled set to true). |
|
Of course, an extension script may consist of several blocks, and you can have multiple blocks responding to the same event. This makes the DSL look nicer and easier to write. However, reacting to events is far from sufficient. If you know you can react to events, you also need to deal with the errors, which implies several "helper" methods that will make things easier.
...