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 <[email protected]> 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) {} } }
