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 18:54:53 UTC

[groovy] branch GROOVY_4_0_X updated (f6c6bf4431 -> 2907fe67b6)

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

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


 discard f6c6bf4431 GROOVY-10771: STC: extension documentation
     new 2907fe67b6 GROOVY-10771: STC: extension documentation

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (f6c6bf4431)
            \
             N -- N -- N   refs/heads/GROOVY_4_0_X (2907fe67b6)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../transform/stc/GroovyTypeCheckingExtensionSupport.java    | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)


[groovy] 01/01: GROOVY-10771: STC: extension documentation

Posted by em...@apache.org.
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

commit 2907fe67b696290ca97e5a11ae64f524cba0fff1
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..e741e108cb 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 before type checking a class</dd>
+     *   <dt>afterVisitClass</dt>                <dd>Registers closure that runs after having finished the visit of a type checked class</dd>
+     *   <dt>beforeVisitMethod</dt>              <dd>Registers closure that runs before type checking a method body</dd>
+     *   <dt>afterVisitMethod</dt>               <dd>Registers closure that runs 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 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 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) {}
     }
 }