You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2022/10/11 17:43:10 UTC

[groovy] branch GROOVY_4_0_X updated: GROOVY-10771: STC: extension documentation

This is an automated email from the ASF dual-hosted git repository.

emilles pushed a commit to branch GROOVY_4_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/GROOVY_4_0_X by this push:
     new f6c6bf4431 GROOVY-10771: STC: extension documentation
f6c6bf4431 is described below

commit f6c6bf4431db080fa521c56aba085b74f2bd0a5a
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Tue Oct 11 12:28:10 2022 -0500

    GROOVY-10771: STC: extension documentation
---
 .../stc/GroovyTypeCheckingExtensionSupport.java    |  80 ++++-
 src/spec/doc/_type-checking-extensions.adoc        |  26 +-
 src/spec/test-resources/aftervisitclass.groovy     |   2 -
 src/spec/test-resources/beforevisitclass.groovy    |   2 -
 ...tclass.groovy => incompatiblereturntype.groovy} |   9 +-
 src/spec/test-resources/selfcheck.groovy           |   1 -
 .../typing/TypeCheckingExtensionSpecTest.groovy    | 345 +++++++++++----------
 7 files changed, 277 insertions(+), 188 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/transform/stc/GroovyTypeCheckingExtensionSupport.java b/src/main/java/org/codehaus/groovy/transform/stc/GroovyTypeCheckingExtensionSupport.java
index 5a72984917..70a6269869 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/GroovyTypeCheckingExtensionSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/GroovyTypeCheckingExtensionSupport.java
@@ -392,14 +392,78 @@ public class GroovyTypeCheckingExtensionSupport extends AbstractTypeCheckingExte
         return methodList;
     }
 
-    // -------------------------------------
-    // delegate to the type checking context
-    // -------------------------------------
-
-    // --------------------------------------------
-    // end of delegate to the type checking context
-    // --------------------------------------------
-
+    /**
+     * Event handler registration:
+     * <dl>
+     *   <dt>setup</dt>                          <dd>Registers closure that runs after the type checker finishes initialization</dd>
+     *   <dt>finish</dt>                         <dd>Registers closure that runs after the type checker completes type checking</dd>
+     *   <dt>beforeVisitClass</dt>               <dd>Registers closure that runs by the type checker before type checking a class</dd>
+     *   <dt>afterVisitClass</dt>                <dd>Registers closure that runs by the type checker after having finished the visit of a type checked class</dd>
+     *   <dt>beforeVisitMethod</dt>              <dd>Registers closure that runs by the type checker before type checking a method body</dd>
+     *   <dt>afterVisitMethod</dt>               <dd>Registers closure that runs by the type checker after type checking a method body</dd>
+     *   <dt>beforeMethodCall</dt>               <dd>Registers closure that runs before the type checker starts type checking a method call</dd>
+     *   <dt>afterMethodCall</dt>                <dd>Registers closure that runs once the type checker has finished type checking a method call</dd>
+     *   <dt>methodNotFound</dt>                 <dd>Registers closure that runs by the type checker when it fails to find an appropriate method for a method call</dd>
+     *   <dt>ambiguousMethods</dt>               <dd>Registers closure that runs when the type checker cannot choose between several candidate methods</dd>
+     *   <dt>onMethodSelection</dt>              <dd>Registers closure that runs by the type checker when it finds a method appropriate for a method call</dd>
+     *   <dt>unresolvedVariable</dt>             <dd>Registers closure that runs when the type checker finds an unresolved variable</dd>
+     *   <dt>unresolvedProperty</dt>             <dd>Registers closure that runs when the type checker cannot find a property on the receiver</dd>
+     *   <dt>unresolvedAttribute</dt>            <dd>Registers closure that runs when the type checker cannot find an attribute on the receiver</dd>
+     *   <dt>incompatibleAssignment</dt>         <dd>Registers closure that runs when the type checker thinks that the right-hand side of an assignment is incompatible with the left-hand side</dd>
+     *   <dt>incompatibleReturnType</dt>         <dd>Registers closure that runs when the type checker thinks that a return value is incompatibe with the return type</dd>
+     * </dl>
+     *
+     * Expression categorization:
+     * <dl>
+     *   <dt>isAnnotationConstantExpression</dt> <dd>Determines if argument is an {@link org.codehaus.groovy.ast.expr.AnnotationConstantExpression AnnotationConstantExpression}</dd>
+     *   <dt>isArgumentListExpression</dt>       <dd>Determines if argument is an {@link org.codehaus.groovy.ast.expr.ArgumentListExpression ArgumentListExpression}</dd>
+     *   <dt>isArrayExpression</dt>              <dd>Determines if argument is an {@link org.codehaus.groovy.ast.expr.ArrayExpression ArrayExpression}</dd>
+     *   <dt>isAttributeExpression</dt>          <dd>Determines if argument is an {@link org.codehaus.groovy.ast.expr.AttributeExpression AttributeExpression}</dd>
+     *   <dt>isBinaryExpression</dt>             <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.BinaryExpression BinaryExpression}</dd>
+     *   <dt>isBitwiseNegationExpression</dt>    <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.BitwiseNegationExpression BitwiseNegationExpression}</dd>
+     *   <dt>isBooleanExpression</dt>            <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.BooleanExpression BooleanExpression}</dd>
+     *   <dt>isCastExpression</dt>               <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.CastExpression CastExpression}</dd>
+     *   <dt>isClassExpression</dt>              <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.ClassExpression ClassExpression}</dd>
+     *   <dt>isClosureExpression</dt>            <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.ClosureExpression ClosureExpression}</dd>
+     *   <dt>isConstantExpression</dt>           <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.ConstantExpression ConstantExpression}</dd>
+     *   <dt>isConstructorCallExpression</dt>    <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.ConstructorCallExpression ConstructorCallExpression}</dd>
+     *   <dt>isDeclarationExpression</dt>        <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.DeclarationExpression DeclarationExpression}</dd>
+     *   <dt>isElvisOperatorExpression</dt>      <dd>Determines if argument is an {@link org.codehaus.groovy.ast.expr.ElvisOperatorExpression ElvisOperatorExpression}</dd>
+     *   <dt>isEmptyExpression</dt>              <dd>Determines if argument is an {@link org.codehaus.groovy.ast.expr.EmptyExpression EmptyExpression}</dd>
+     *   <dt>isFieldExpression</dt>              <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.FieldExpression FieldExpression}</dd>
+     *   <dt>isGStringExpression</dt>            <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.GStringExpression GStringExpression}</dd>
+     *   <dt>isLambdaExpression</dt>             <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.LambdaExpression LambdaExpression}</dd>
+     *   <dt>isListExpression</dt>               <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.ListExpression ListExpression}</dd>
+     *   <dt>isMapExpression</dt>                <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.MapExpression MapExpression}</dd>
+     *   <dt>isMapEntryExpression</dt>           <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.MapEntryExpression MapEntryExpression}</dd>
+     *   <dt>isMethodCallExpression</dt>         <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.MethodCallExpression MethodCallExpression}</dd>
+     *   <dt>isMethodPointerExpression</dt>      <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.MethodPointerExpression MethodPointerExpression}</dd>
+     *   <dt>isMethodReferenceExpression</dt>    <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.MethodReferenceExpression MethodReferenceExpression}</dd>
+     *   <dt>isNamedArgumentListExpression</dt>  <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.NamedArgumentListExpression NamedArgumentListExpression}</dd>
+     *   <dt>isNotExpression</dt>                <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.NotExpression NotExpression}</dd>
+     *   <dt>isPostfixExpression</dt>            <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.PostfixExpression PostfixExpression}</dd>
+     *   <dt>isPrefixExpression</dt>             <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.PrefixExpression PrefixExpression}</dd>
+     *   <dt>isPropertyExpression</dt>           <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.PropertyExpression PropertyExpression}</dd>
+     *   <dt>isRangeExpression</dt>              <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.RangeExpression RangeExpression}</dd>
+     *   <dt>isSpreadExpression</dt>             <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.SpreadExpression SpreadExpression}</dd>
+     *   <dt>isSpreadMapExpression</dt>          <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.SpreadMapExpression SpreadMapExpression}</dd>
+     *   <dt>isStaticMethodCallExpression</dt>   <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.StaticMethodCallExpression StaticMethodCallExpression}</dd>
+     *   <dt>isTernaryExpression</dt>            <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.TernaryExpression TernaryExpression}</dd>
+     *   <dt>isTupleExpression</dt>              <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.TupleExpression TupleExpression}</dd>
+     *   <dt>isUnaryMinusExpression</dt>         <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.UnaryMinusExpression UnaryMinusExpression}</dd>
+     *   <dt>isUnaryPlusExpression</dt>          <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.UnaryPlusExpression UnaryPlusExpression}</dd>
+     *   <dt>isVariableExpression</dt>           <dd>Determines if argument is a  {@link org.codehaus.groovy.ast.expr.VariableExpression VariableExpression}</dd>
+     * </dl>
+     *
+     * General utility:
+     * <ul>
+     *   <li>Delegates to {@link AbstractTypeCheckingExtension}</li>
+     *   <li>Imports static members of {@link org.codehaus.groovy.ast.ClassHelper ClassHelper}</li>
+     *   <li>Imports static members of {@link org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport StaticTypeCheckingSupport}</li>
+     * </ul>
+     *
+     * @see <a href="https://docs.groovy-lang.org/latest/html/documentation/#_a_dsl_for_type_checking">Groovy Language Documentation</a>
+     */
     public abstract static class TypeCheckingDSL extends Script {
         private GroovyTypeCheckingExtensionSupport extension;
 
diff --git a/src/spec/doc/_type-checking-extensions.adoc b/src/spec/doc/_type-checking-extensions.adoc
index fa0b668424..0cda7b3cb5 100644
--- a/src/spec/doc/_type-checking-extensions.adoc
+++ b/src/spec/doc/_type-checking-extensions.adoc
@@ -508,7 +508,7 @@ inner/anonymous class defined in the same class with is not skipped.
     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
+| ClassNode lhsType, ClassNode rhsType, Expression assignment
 | *Usage*
 |
 [source,groovy]
@@ -525,6 +525,30 @@ can help the type checker just by telling it that the assignment is
 valid (using `handled` set to `true`).
 |===
 
+[[event-incompatibleReturnType]]
+[cols="1,3a",width="100%"]
+|===
+| *Event name*
+| incompatibleReturnType
+| *Called When*
+| Called when the type checker thinks that a return value is incompatibe with
+    the return type of the enclosing closure or method
+| *Arguments*
+| ReturnStatement statement, ClassNode valueType
+| *Usage*
+|
+[source,groovy]
+----
+include::../test-resources/incompatiblereturntype.groovy[tags=event,indent=0]
+----
+
+Gives the developer the ability to handle incorrect return values. This is for
+example useful when the return value will undergo implicit conversion or the
+enclosing closure's target type is difficult to infer properly. In that case,
+you can help the type checker just by telling it that the assignment is valid
+(by setting the `handled` property).
+|===
+
 [[event-ambiguousMethods]]
 [cols="1,3a",width="100%"]
 |===
diff --git a/src/spec/test-resources/aftervisitclass.groovy b/src/spec/test-resources/aftervisitclass.groovy
index 6417770fd4..faccbd6721 100644
--- a/src/spec/test-resources/aftervisitclass.groovy
+++ b/src/spec/test-resources/aftervisitclass.groovy
@@ -16,8 +16,6 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-import org.codehaus.groovy.ast.ClassNode
-
 // tag::event[]
 afterVisitClass { ClassNode classNode ->
     def name = classNode.nameWithoutPackage
diff --git a/src/spec/test-resources/beforevisitclass.groovy b/src/spec/test-resources/beforevisitclass.groovy
index 90ee3b93bf..5dd0f53431 100644
--- a/src/spec/test-resources/beforevisitclass.groovy
+++ b/src/spec/test-resources/beforevisitclass.groovy
@@ -16,8 +16,6 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-import org.codehaus.groovy.ast.ClassNode
-
 // tag::event[]
 beforeVisitClass { ClassNode classNode ->
     def name = classNode.nameWithoutPackage
diff --git a/src/spec/test-resources/aftervisitclass.groovy b/src/spec/test-resources/incompatiblereturntype.groovy
similarity index 75%
copy from src/spec/test-resources/aftervisitclass.groovy
copy to src/spec/test-resources/incompatiblereturntype.groovy
index 6417770fd4..5872bf07df 100644
--- a/src/spec/test-resources/aftervisitclass.groovy
+++ b/src/spec/test-resources/incompatiblereturntype.groovy
@@ -16,13 +16,10 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-import org.codehaus.groovy.ast.ClassNode
-
 // tag::event[]
-afterVisitClass { ClassNode classNode ->
-    def name = classNode.nameWithoutPackage
-    if (!(name[0] in 'A'..'Z')) {
-        addStaticTypeError("Class '${name}' doesn't start with an uppercase letter",classNode)
+incompatibleReturnType { stmt, type ->
+    if (type == STRING_TYPE) {
+        handled = true
     }
 }
 // end::event[]
\ No newline at end of file
diff --git a/src/spec/test-resources/selfcheck.groovy b/src/spec/test-resources/selfcheck.groovy
index 0e0150b060..fa032d2414 100644
--- a/src/spec/test-resources/selfcheck.groovy
+++ b/src/spec/test-resources/selfcheck.groovy
@@ -16,7 +16,6 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-import org.codehaus.groovy.ast.ClassNode
 import org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor
 import org.codehaus.groovy.transform.stc.TypeCheckingContext
 
diff --git a/src/spec/test/typing/TypeCheckingExtensionSpecTest.groovy b/src/spec/test/typing/TypeCheckingExtensionSpecTest.groovy
index 7da5b6ff7f..baab7e4c24 100644
--- a/src/spec/test/typing/TypeCheckingExtensionSpecTest.groovy
+++ b/src/spec/test/typing/TypeCheckingExtensionSpecTest.groovy
@@ -21,6 +21,8 @@ package typing
 import groovy.test.GroovyAssert
 import groovy.test.GroovyTestCase
 import groovy.transform.TypeChecked
+import groovy.transform.stc.ClosureParams
+import groovy.transform.stc.SimpleType
 import groovy.xml.MarkupBuilder
 import org.codehaus.groovy.control.MultipleCompilationErrorsException
 
@@ -43,95 +45,39 @@ final class TypeCheckingExtensionSpecTest extends GroovyTestCase {
         // end::intro_stc_extensions[]
     }
 
-    void testRobotExample() {
-
-        def err = shouldFail(MultipleCompilationErrorsException, '''import groovy.transform.TypeChecked
-import org.codehaus.groovy.control.CompilerConfiguration
-import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
-import typing.Robot
-
-def script = """
-// tag::example_robot_script[]
-robot.move 100
-// end::example_robot_script[]
-"""
-
-// tag::example_robot_setup[]
-def config = new CompilerConfiguration()
-config.addCompilationCustomizers(
-    new ASTTransformationCustomizer(TypeChecked)            // <1>
-)
-def shell = new GroovyShell(config)                         // <2>
-def robot = new Robot()
-shell.setVariable('robot', robot)
-shell.evaluate(script)                                      // <3>
-// end::example_robot_setup[]
-''')
-        assert err.contains(stripAsciidocMarkup('''
-// tag::example_robot_expected_err[]
-[Static type checking] - The variable [robot] is undeclared.
-// end::example_robot_expected_err[]
-'''))
-    }
-
-    void testRobotExampleFixed() {
-        assertScript '''import groovy.transform.TypeChecked
-import org.codehaus.groovy.control.CompilerConfiguration
-import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
-import typing.Robot
-
-def script = """
-robot.move 100
-"""
-
-def config = new CompilerConfiguration()
-// tag::example_robot_fixed_conf[]
-config.addCompilationCustomizers(
-    new ASTTransformationCustomizer(
-        TypeChecked,
-        extensions:['robotextension.groovy'])
-)
-// end::example_robot_fixed_conf[]
-def shell = new GroovyShell(config)
-def robot = new Robot()
-shell.setVariable('robot', robot)
-shell.evaluate(script)
-'''
-    }
-
     void testSetup() {
-        assertScriptWithExtension('setup.groovy', '''
+        assertScriptWithExtension 'setup.groovy', '''
             1+1
-        ''')
+        '''
     }
 
     void testFinish() {
-        assertScriptWithExtension('finish.groovy', '''
+        assertScriptWithExtension 'finish.groovy', '''
             1+1
-        ''')
+        '''
     }
 
     void testUnresolvedVariable() {
-        assertScriptWithExtension('unresolvedvariable.groovy', '''
+        assertScriptWithExtension 'unresolvedvariable.groovy', '''
             assert people.size() == 2
-        ''') {
+        ''', {
             it.setVariable('people', ['John','Meg'])
         }
     }
 
     void testUnresolvedProperty() {
         use (SpecSupport) {
-            assertScriptWithExtension('unresolvedproperty.groovy', '''
-            assert 'string'.longueur == 6
-        ''')
+            assertScriptWithExtension 'unresolvedproperty.groovy', '''
+                assert 'string'.longueur == 6
+            '''
         }
     }
 
     void testUnresolvedAttribute() {
         try {
-            assertScriptWithExtension('unresolvedattribute.groovy', '''
-            assert 'string'.@longueur == 6
-        ''')
+            assertScriptWithExtension 'unresolvedattribute.groovy', '''
+                assert 'string'.@longueur == 6
+            '''
             assert false
         } catch (MissingFieldException mfe) {
             // ok
@@ -140,9 +86,9 @@ shell.evaluate(script)
 
     void testBeforeMethodCall() {
         try {
-            assertScriptWithExtension('beforemethodcall.groovy', '''
-            'string'.toUpperCase()
-        ''')
+            assertScriptWithExtension 'beforemethodcall.groovy', '''
+                'string'.toUpperCase()
+            '''
             assert false
         } catch (MultipleCompilationErrorsException err) {
             assert err.message.contains('[Static type checking] - Not allowed')
@@ -151,9 +97,9 @@ shell.evaluate(script)
 
     void testAfterMethodCall() {
         try {
-            assertScriptWithExtension('aftermethodcall.groovy', '''
-            'string'.toUpperCase()
-        ''')
+            assertScriptWithExtension 'aftermethodcall.groovy', '''
+                'string'.toUpperCase()
+            '''
             assert false
         } catch (MultipleCompilationErrorsException err) {
             assert err.message.contains('[Static type checking] - Not allowed')
@@ -162,11 +108,11 @@ shell.evaluate(script)
 
     void testOnMethodSelection() {
         try {
-            assertScriptWithExtension('onmethodselection.groovy', '''
-            'string'.toUpperCase()
-            'string 2'.toLowerCase()
-            'string 3'.length()
-        ''')
+            assertScriptWithExtension 'onmethodselection.groovy', '''
+                'string'.toUpperCase()
+                'string 2'.toLowerCase()
+                'string 3'.length()
+            '''
             assert false
         } catch (MultipleCompilationErrorsException err) {
             assert err.message.contains('[Static type checking] - You can use only 2 calls on String in your source code')
@@ -175,33 +121,33 @@ shell.evaluate(script)
 
     void testMethodNotFound() {
         use (SpecSupport) {
-            assertScriptWithExtension('methodnotfound.groovy', '''
-            assert 'string'.longueur() == 6
-        ''')
+            assertScriptWithExtension 'methodnotfound.groovy', '''
+                assert 'string'.longueur() == 6
+            '''
         }
     }
 
     void testBeforeVisitMethod() {
         use (SpecSupport) {
-            assertScriptWithExtension('beforevisitmethod.groovy', '''
-            void skipIt() {
-                'blah'.doesNotExist()
-            }
-            skipIt()
-        ''')
+            assertScriptWithExtension 'beforevisitmethod.groovy', '''
+                void skipIt() {
+                    'blah'.doesNotExist()
+                }
+                skipIt()
+            '''
         }
     }
 
     void testAfterVisitMethod() {
         try {
-            assertScriptWithExtension('aftervisitmethod.groovy', '''
-            void foo() {
-               'string'.toUpperCase()
-               'string 2'.toLowerCase()
-               'string 3'.length()
-            }
-            foo()
-        ''')
+            assertScriptWithExtension 'aftervisitmethod.groovy', '''
+                void foo() {
+                   'string'.toUpperCase()
+                   'string 2'.toLowerCase()
+                   'string 3'.length()
+                }
+                foo()
+            '''
             assert false
         } catch (MultipleCompilationErrorsException err) {
             assert err.message.contains('[Static type checking] - Method foo contains more than 2 method calls')
@@ -210,10 +156,10 @@ shell.evaluate(script)
 
     void testBeforeVisitClass() {
         try {
-            assertScriptWithExtension('beforevisitclass.groovy', '''
-            class someclass {
-            }
-        ''')
+            assertScriptWithExtension 'beforevisitclass.groovy', '''
+                class someclass {
+                }
+            '''
             assert false
         } catch (MultipleCompilationErrorsException err) {
             assert err.message.contains("[Static type checking] - Class 'someclass' doesn't start with an uppercase letter")
@@ -222,10 +168,10 @@ shell.evaluate(script)
 
     void testAfterVisitClass() {
         try {
-            assertScriptWithExtension('aftervisitclass.groovy', '''
-            class someclass {
-            }
-        ''')
+            assertScriptWithExtension 'aftervisitclass.groovy', '''
+                class someclass {
+                }
+            '''
             assert false
         } catch (MultipleCompilationErrorsException err) {
             assert err.message.contains("[Static type checking] - Class 'someclass' doesn't start with an uppercase letter")
@@ -233,89 +179,104 @@ shell.evaluate(script)
     }
 
     void testIncompatibleAssignment() {
-        use (SpecSupport) {
-            assertScriptWithExtension('incompatibleassignment.groovy', '''import groovy.transform.TypeChecked
-import groovy.transform.TypeCheckingMode
+        assertScriptWithExtension 'incompatibleassignment.groovy', '''
+            import groovy.transform.TypeChecked
+            import groovy.transform.TypeCheckingMode
+
+            @TypeChecked(TypeCheckingMode.SKIP)
+            class Point {
+                int x, y
 
-@TypeChecked(TypeCheckingMode.SKIP)
-class Point {
-    int x, y = 1
+                void setProperty(String name, value) {
+                    def v = value instanceof Closure ? value() : value
+                    this.@(name) *= v // set field to prevent recursion
+                }
+            }
 
-    void setProperty(String name, value) {
-        def v = value instanceof Closure ? value() : value
-        this.@"$name" *= v
+            def p = new Point(x: 3, y: 4)
+            p.x = { 2 } // allowed by setProperty
+            assert p.x == 6
+        '''
     }
-}
 
-def p = new Point(x: 3, y: 4)
-p.x = { 2 }
-assert p.x == 6
-        ''')
-        }
+    void testIncompatibleReturnType() {
+        assertScriptWithExtension 'incompatiblereturntype.groovy', '''
+            Closure<Date> c = { '1' }
+            Date m() { '1' }
+        '''
     }
 
     void testAmbiguousMethods() {
         def err = shouldFail {
-            assertScriptWithExtension('ambiguousmethods.groovy', '''
-            int foo(Integer x) { 1 }
-            int foo(String s) { 2 }
-            int foo(Date d) { 3 }
-            assert foo(null) == 2
-        ''')
+            assertScriptWithExtension 'ambiguousmethods.groovy', '''
+                int foo(Integer x) { 1 }
+                int foo(String s) { 2 }
+                int foo(Date d) { 3 }
+                assert foo(null) == 2
+            '''
         }
-        assert err.contains(/Cannot resolve which method to invoke for [null] due to overlapping prototypes/)
+        assert err =~ /Cannot resolve which method to invoke for \[null\] due to overlapping prototypes/
     }
 
     void testSupportMethods() {
-        assertScriptWithExtension('selfcheck.groovy','''
+        assertScriptWithExtension 'selfcheck.groovy', '''
             class Foo {}
             1+1
-        ''')
+        '''
     }
 
     void testNewMethod() {
-        assertScriptWithExtension('newmethod.groovy','''
+        assertScriptWithExtension 'newmethod.groovy','''
             class Foo {
                 def methodMissing(String name, args) { this }
             }
             def f = new Foo()
             f.foo().bar()
-        ''')
+        '''
     }
 
     void testScopingMethods() {
-        assertScriptWithExtension('scoping.groovy','''
+        assertScriptWithExtension 'scoping.groovy','''
             1+1
-        ''')
-        assertScriptWithExtension('scoping_alt.groovy','''
+        '''
+        assertScriptWithExtension 'scoping_alt.groovy','''
             1+1
-        ''')
+        '''
     }
 
-    void testPrecompiledExtensions() {
-        assertScript '''import groovy.transform.TypeChecked
+    //--------------------------------------------------------------------------
+
+    void testRobotExample() {
+        def err = shouldFail(MultipleCompilationErrorsException, '''import groovy.transform.TypeChecked
 import org.codehaus.groovy.control.CompilerConfiguration
 import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
 import typing.Robot
 
 def script = """
+// tag::example_robot_script[]
 robot.move 100
+// end::example_robot_script[]
 """
 
+// tag::example_robot_setup[]
 def config = new CompilerConfiguration()
-// tag::setup_precompiled[]
 config.addCompilationCustomizers(
-    new ASTTransformationCustomizer(
-        TypeChecked,
-        extensions:['typing.PrecompiledExtension'])
+    new ASTTransformationCustomizer(TypeChecked)            // <1>
 )
-// end::setup_precompiled[]
-def shell = new GroovyShell(config)
+def shell = new GroovyShell(config)                         // <2>
 def robot = new Robot()
 shell.setVariable('robot', robot)
-shell.evaluate(script)
-'''
+shell.evaluate(script)                                      // <3>
+// end::example_robot_setup[]
+''')
+        assert err.contains(stripAsciidocMarkup('''
+// tag::example_robot_expected_err[]
+[Static type checking] - The variable [robot] is undeclared.
+// end::example_robot_expected_err[]
+'''))
+    }
 
+    void testRobotExampleFixed() {
         assertScript '''import groovy.transform.TypeChecked
 import org.codehaus.groovy.control.CompilerConfiguration
 import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
@@ -326,11 +287,13 @@ robot.move 100
 """
 
 def config = new CompilerConfiguration()
+// tag::example_robot_fixed_conf[]
 config.addCompilationCustomizers(
     new ASTTransformationCustomizer(
         TypeChecked,
-        extensions:['typing.PrecompiledJavaExtension'])
+        extensions:['robotextension.groovy'])
 )
+// end::example_robot_fixed_conf[]
 def shell = new GroovyShell(config)
 def robot = new Robot()
 shell.setVariable('robot', robot)
@@ -339,7 +302,6 @@ shell.evaluate(script)
     }
 
     void testRobotExamplePassWithCompileStatic() {
-
         assertScript '''import groovy.transform.CompileStatic
 import org.codehaus.groovy.control.CompilerConfiguration
 import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
@@ -365,7 +327,6 @@ shell.evaluate(script)
     }
 
     void testRobotExampleDelegatingScript() {
-
         assertScript '''import groovy.transform.CompileStatic
 import org.codehaus.groovy.control.CompilerConfiguration
 import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
@@ -389,7 +350,6 @@ runner.run()                                                // <4>
     }
 
     void testRobotExampleFailsWithCompileStatic() {
-
         def err = GroovyAssert.shouldFail '''import groovy.transform.CompileStatic
 import org.codehaus.groovy.control.CompilerConfiguration
 import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
@@ -422,7 +382,6 @@ java.lang.NoSuchMethodError: java.lang.Object.move()Ltyping/Robot;
     }
 
     void testRobotExamplePassesWithCompileStatic() {
-
         assertScript '''import groovy.transform.CompileStatic
 import org.codehaus.groovy.control.CompilerConfiguration
 import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
@@ -446,6 +405,54 @@ runner.run()
 '''
     }
 
+    void testPrecompiledExtensions() {
+        assertScript '''import groovy.transform.TypeChecked
+import org.codehaus.groovy.control.CompilerConfiguration
+import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
+import typing.Robot
+
+def script = """
+robot.move 100
+"""
+
+def config = new CompilerConfiguration()
+// tag::setup_precompiled[]
+config.addCompilationCustomizers(
+    new ASTTransformationCustomizer(
+        TypeChecked,
+        extensions:['typing.PrecompiledExtension'])
+)
+// end::setup_precompiled[]
+def shell = new GroovyShell(config)
+def robot = new Robot()
+shell.setVariable('robot', robot)
+shell.evaluate(script)
+'''
+
+        assertScript '''import groovy.transform.TypeChecked
+import org.codehaus.groovy.control.CompilerConfiguration
+import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
+import typing.Robot
+
+def script = """
+robot.move 100
+"""
+
+def config = new CompilerConfiguration()
+config.addCompilationCustomizers(
+    new ASTTransformationCustomizer(
+        TypeChecked,
+        extensions:['typing.PrecompiledJavaExtension'])
+)
+def shell = new GroovyShell(config)
+def robot = new Robot()
+shell.setVariable('robot', robot)
+shell.evaluate(script)
+'''
+    }
+
+    //--------------------------------------------------------------------------
+
     void doDelegateResolutionForPropertyReadTest(String strategy, String expected) {
         assertScript """import groovy.transform.CompileStatic
 class ADelegate {
@@ -477,7 +484,6 @@ assert new AClass().test() == "$expected"
 """
     }
 
-
     void doDelegateResolutionForPropertyWriteTest(String strategy, String expected) {
         assertScript """import groovy.transform.CompileStatic
 class ADelegate {
@@ -587,36 +593,39 @@ new DelegateTest().delegate()
 
     void testDelegateVariableFromDifferentOwningClass() {
         assertScript '''
-        @groovy.transform.CompileStatic
-        class A {
-            static private int MAX_LINES = 2
-            static class B {
-                @Delegate
-                private Map<String, Object> delegate = [:]
-                void m(int c) {
-                    if (c > MAX_LINES) {
-                        return
+            @groovy.transform.CompileStatic
+            class A {
+                static private int MAX_LINES = 2
+                static class B {
+                    @Delegate
+                    private Map<String, Object> delegate = [:]
+                    void m(int c) {
+                        if (c > MAX_LINES) {
+                            return
+                        }
                     }
                 }
             }
-        }
-        null
+            null
         '''
     }
 
-    private static class SpecSupport {
-        static int getLongueur(String self) { self.length() }
-        static int longueur(String self) { self.length() }
-        static void doesNotExist(String self) {}
-    }
+    //--------------------------------------------------------------------------
 
-    private def assertScriptWithExtension(String extensionName, String code, Closure<Void> configurator=null) {
+    private static assertScriptWithExtension(String extensionName, String script,
+            @ClosureParams(value=SimpleType, options='groovy.lang.Binding') Closure<Void> configurator=null) {
         def shell = GroovyShell.withConfig {
             ast(TypeChecked, extensions: [extensionName])
         }
         if (configurator) {
             configurator.call(shell.context)
         }
-        shell.evaluate(code)
+        shell.evaluate(script)
+    }
+
+    private static class SpecSupport {
+        static int getLongueur(String self) { self.length() }
+        static int longueur(String self) { self.length() }
+        static void doesNotExist(String self) {}
     }
 }