| ////////////////////////////////////////// |
| |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| |
| ////////////////////////////////////////// |
| |
| = Semantics |
| |
| This chapter covers the semantics of the Groovy programming language. |
| |
| == Statements |
| |
| === Variable definition |
| |
| Variables can be defined using either their type (like `String`) or by using the keyword `def`: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SemanticsTest.groovy[tags=variable_definition_example,indent=0] |
| ---- |
| |
| `def` is a replacement for a type name. In variable definitions it is used to indicate that you don't care about the type. In variable definitions it is mandatory to either provide a type name explicitly or to use "def" in replacement. This is needed to make variable definitions detectable for the Groovy parser. |
| |
| You can think of `def` as an alias of `Object` and you will understand it in an instant. |
| |
| [NOTE] |
| Variable definition types can be refined by using generics, like in `List<String> names`. |
| To learn more about the generics support, please read the <<generics,generics section>>. |
| |
| === Variable assignment |
| |
| You can assign values to variables for later use. Try the following: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SemanticsTest.groovy[tags=variable_assignment_example,indent=0] |
| ---- |
| |
| ==== Multiple assignment |
| |
| Groovy supports multiple assignment, i.e. where multiple variables can be assigned at once, e.g.: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SemanticsTest.groovy[tags=multiple_assignment_example,indent=0] |
| ---- |
| |
| You can provide types as part of the declaration if you wish: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SemanticsTest.groovy[tags=multiple_assignment_with_types,indent=0] |
| ---- |
| |
| As well as used when declaring variables it also applies to existing variables: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SemanticsTest.groovy[tags=multiple_assignment_with_existing_variables,indent=0] |
| ---- |
| |
| The syntax works for arrays as well as lists, as well as methods that return either of these: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SemanticsTest.groovy[tags=multiple_assignment_with_arrays_and_lists,indent=0] |
| ---- |
| |
| ==== Overflow and Underflow |
| |
| If the left hand side has too many variables, excess ones are filled with null's: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SemanticsTest.groovy[tags=multiple_assignment_overflow,indent=0] |
| ---- |
| |
| If the right hand side has too many variables, the extra ones are ignored: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SemanticsTest.groovy[tags=multiple_assignment_underflow,indent=0] |
| ---- |
| |
| ==== Object destructuring with multiple assignment |
| |
| In the section describing the various <<groovy-operators,Groovy operators>>, |
| the case of the <<subscript-operator,subscript operator>> has been covered, |
| explaining how you can override the `getAt()`/`putAt()` method. |
| |
| With this technique, we can combine multiple assignments and the subscript operator methods to implement _object destructuring_. |
| |
| Consider the following immutable `Coordinates` class, containing a pair of longitude and latitude doubles, |
| and notice our implementation of the `getAt()` method: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SemanticsTest.groovy[tags=coordinates-class,indent=0] |
| ---- |
| |
| Now let's instantiate this class and destructure its longitude and latitude: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SemanticsTest.groovy[tags=destructuring,indent=0] |
| ---- |
| <1> we create an instance of the `Coordinates` class |
| <2> then, we use a multiple assignment to get the individual longitude and latitude values |
| <3> and we can finally assert their values. |
| |
| === Control structures |
| ==== Conditional structures |
| ===== if / else |
| |
| Groovy supports the usual if - else syntax from Java |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SemanticsTest.groovy[tags=if_else_example,indent=0] |
| ---- |
| |
| Groovy also supports the normal Java "nested" if then else if syntax: |
| |
| [source,groovy] |
| ---- |
| if ( ... ) { |
| ... |
| } else if (...) { |
| ... |
| } else { |
| ... |
| } |
| ---- |
| |
| ===== switch / case |
| |
| The switch statement in Groovy is backwards compatible with Java code; so you can fall through cases sharing the same code for multiple matches. |
| |
| One difference though is that the Groovy switch statement can handle any kind of switch value and different kinds of matching can be performed. |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SemanticsTest.groovy[tags=switch_case_example,indent=0] |
| ---- |
| |
| Switch supports the following kinds of comparisons: |
| |
| * Class case values match if the switch value is an instance of the class |
| * Regular expression case values match if the `toString()` representation of the switch value matches the regex |
| * Collection case values match if the switch value is contained in the collection. This also includes ranges (since they are Lists) |
| * Closure case values match if the calling the closure returns a result which is true according to the <<Groovy-Truth,Groovy truth>> |
| * If none of the above are used then the case value matches if the case value equals the switch value |
| |
| NOTE: When using a closure case value, the default `it` parameter is actually the switch value (in our example, variable `x`). |
| |
| ==== Looping structures |
| ===== Classic for loop |
| |
| Groovy supports the standard Java / C for loop: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SemanticsTest.groovy[tags=classic_for_loop_example,indent=0] |
| ---- |
| |
| ===== for in loop |
| |
| The for loop in Groovy is much simpler and works with any kind of array, collection, Map, etc. |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SemanticsTest.groovy[tags=groovy_for_loop_example,indent=0] |
| ---- |
| |
| [NOTE] |
| Groovy also supports the Java colon variation with colons: `for (char c : text) {}`, |
| where the type of the variable is mandatory. |
| |
| ===== while loop |
| |
| Groovy supports the usual while {...} loops like Java: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SemanticsTest.groovy[tags=while_loop_example,indent=0] |
| ---- |
| |
| ==== Exception handling |
| |
| Exception handling is the same as Java. |
| |
| ==== try / catch / finally |
| |
| You can specify a complete `try-catch-finally`, a `try-catch`, or a `try-finally` set of blocks. |
| |
| NOTE: Braces are required around each block's body. |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SemanticsTest.groovy[tags=try_catch_example,indent=0] |
| ---- |
| |
| We can put code within a 'finally' clause following a matching 'try' clause, so that regardless of whether the code in the 'try' clause throws an exception, the code in the finally clause will always execute: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SemanticsTest.groovy[tags=try_catch_finally_example,indent=0] |
| ---- |
| |
| ==== Multi-catch |
| |
| With the multi catch block (since Groovy 2.0), we're able to define several exceptions to be catch and treated by the same catch block: |
| |
| [source,groovy] |
| ---- |
| try { |
| /* ... */ |
| } catch ( IOException | NullPointerException e ) { |
| /* one block to handle 2 exceptions */ |
| } |
| ---- |
| |
| === Power assertion |
| |
| Unlike Java with which Groovy shares the `assert` keyword, the latter in Groovy behaves very differently. First of all, |
| an assertion in Groovy is always executed, independently of the `-ea` flag of the JVM. It makes this a first class choice |
| for unit tests. The notion of "power asserts" is directly related to how the Groovy `assert` behaves. |
| |
| A power assertion is decomposed into 3 parts: |
| |
| ---- |
| assert [left expression] == [right expression] : (optional message) |
| ---- |
| |
| The result of the assertion is very different from what you would get in Java. If the assertion is true, then nothing |
| happens. If the assertion is false, then it provides a visual representation of the value of each sub-expressions of the |
| expression being asserted. For example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/PowerAssertTest.groovy[tags=assert_code_1,indent=0] |
| ---- |
| |
| Will yield: |
| |
| ---- |
| Caught: Assertion failed: |
| |
| include::{projectdir}/src/spec/test/semantics/PowerAssertTest.groovy[tags=assert_error_1,indent=0] |
| ---- |
| |
| Power asserts become very interesting when the expressions are more complex, like in the next example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/PowerAssertTest.groovy[tags=assert_code_2,indent=0] |
| ---- |
| |
| Which will print the value for each sub-expression: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/PowerAssertTest.groovy[tags=assert_error_2,indent=0] |
| ---- |
| |
| In case you don't want a pretty printed error message like above, you can fallback to a custom error message by |
| changing the optional message part of the assertion, like in this example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/PowerAssertTest.groovy[tags=assert_code_3,indent=0] |
| ---- |
| |
| Which will print the following error message: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/PowerAssertTest.groovy[tags=assert_error_3,indent=0] |
| ---- |
| |
| |
| === Labeled statements |
| |
| Any statement can be associated with a label. Labels do not impact the semantics of the code and can be used to make |
| the code easier to read like in the following example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/LabelsTest.groovy[tags=test_labels,indent=0] |
| ---- |
| |
| Despite not changing the semantics of the labelled statement, it is possible to use labels in the `break` instruction |
| as a target for jump, as in the next example. However, even if this is allowed, this coding style is in general considered |
| a bad practice: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/LabelsTest.groovy[tags=label_bad_practice,indent=0] |
| ---- |
| |
| It is important to understand that by default labels have no impact on the semantics of the code, however they belong to the abstract |
| syntax tree (AST) so it is possible for an AST transformation to use that information to perform transformations over |
| the code, hence leading to different semantics. This is in particular what the http://spockframework.github.io/spock/docs/current/index.html[Spock Framework] |
| does to make testing easier. |
| |
| == Expressions |
| |
| (TBD) |
| |
| |
| [[gpath_expressions]] |
| === GPath expressions |
| //// |
| This is covered in {projectdir}/subprojects/groovy-xml/{specfolder}/xml-userguide.adoc, where the legacy codehaus GPath wiki page |
| have been converted. |
| Current section should explain what is an GPath expression (not just example, but more like a formal language specification, but kept simple). |
| |
| //// |
| |
| `GPath` is a path expression language integrated into Groovy which allows parts of nested structured data to be identified. In this |
| sense, it has similar aims and scope as XPath does for XML. GPath is often used in the context of processing XML, but it really applies |
| to any object graph. Where XPath uses a filesystem-like path notation, a tree hierarchy with parts separated by a slash `/`, GPath *use a |
| dot-object notation* to perform object navigation. |
| |
| As an example, you can specify a path to an object or element of interest: |
| |
| * `a.b.c` -> for XML, yields all the `c` elements inside `b` inside `a` |
| * `a.b.c` -> for POJOs, yields the `c` properties for all the `b` properties of `a` (sort of like `a.getB().getC()` in JavaBeans) |
| |
| In both cases, the GPath expression can be viewed as a query on an object graph. For POJOs, the object graph is most often built by the |
| program being written through object instantiation and composition; for XML processing, the object graph is the result of `parsing` |
| the XML text, most often with classes like XmlParser or XmlSlurper. See <<{projectdir}/subprojects/groovy-xml/{specfolder}/xml-userguide.adoc#Processing XML,Processing XML>> |
| for more in-depth details on consuming XML in Groovy. |
| |
| [TIP] |
| ==== |
| When querying the object graph generated from XmlParser or XmlSlurper, a GPath expression can refer to attributes defined on elements with |
| the `@` notation: |
| |
| * `a["@href"]` -> map-like notation : the href attribute of all the a elements |
| * `a.'@href'` -> property notation : an alternative way of expressing this |
| * `a.@href` -> direct notation : yet another alternative way of expressing this |
| ==== |
| |
| ==== Object navigation |
| |
| Let's see an example of a GPath expression on a simple _object graph_, the one obtained using java reflection. Suppose you are in a non-static method of a |
| class having another method named `aMethodFoo` |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/GPathTest.groovy[tags=gpath_on_reflection_1,indent=0] |
| ---- |
| |
| the following GPath expression will get the name of that method: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/GPathTest.groovy[tags=gpath_on_reflection_2,indent=0] |
| ---- |
| |
| _More precisely_, the above GPath expression produces a list of String, each being the name of an existing method on `this` where that name ends with `Foo`. |
| |
| Now, given the following methods also defined in that class: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/GPathTest.groovy[tags=gpath_on_reflection_3,indent=0] |
| ---- |
| |
| then the following GPath expression will get the names of *(1)* and *(3)*, but not *(2)* or *(0)*: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/GPathTest.groovy[tags=gpath_on_reflection_4,indent=0] |
| ---- |
| |
| ==== Expression Deconstruction |
| |
| We can decompose the expression `this.class.methods.name.grep(~/.*Bar/)` to get an idea of how a GPath is evaluated: |
| |
| `this.class`:: property accessor, equivalent to `this.getClass()` in Java, yields a `Class` object. |
| `this.class.methods`:: property accessor, equivalent to `this.getClass().getMethods()`, yields an array of `Method` objects. |
| `this.class.methods.name`:: apply a property accessor on each element of an array and produce a list of the results. |
| `this.class.methods.name.grep(...)`:: call method `grep` on each element of the list yielded by `this.class.methods.name` and produce a list of the results. |
| |
| WARNING: A sub-expression like `this.class.methods` yields an array because this is what calling `this.getClass().getMethods()` in Java |
| would produce. `GPath` expressions do not have a convention where a `s` means a list or anything like that. |
| |
| One powerful feature of GPath expression is that property access on a collection is converted to a _property access on each element of the collection_ with |
| the results collected into a collection. Therefore, the expression `this.class.methods.name` could be expressed as follows in Java: |
| [source,java] |
| ---- |
| List<String> methodNames = new ArrayList<String>(); |
| for (Method method : this.getClass().getMethods()) { |
| methodNames.add(method.getName()); |
| } |
| return methodNames; |
| ---- |
| |
| Array access notation can also be used in a GPath expression where a collection is present : |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/GPathTest.groovy[tags=gpath_array_access_1,indent=0] |
| ---- |
| |
| NOTE: array access are zero-based in GPath expressions |
| |
| ==== GPath for XML navigation |
| |
| Here is an example with a XML document and various form of GPath expressions: |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/GPathTest.groovy[tags=gpath_on_xml_1,indent=0] |
| ---- |
| |
| <1> There is one `level` node under `root` |
| <2> There are two `sublevel` nodes under `root/level` |
| <3> There is one element `sublevel` having an attribute `id` with value `1` |
| <4> Text value of `key` element of first `keyVal` element of second `sublevel` element under `root/level` is 'anotherKey' |
| |
| == Promotion and coercion |
| |
| === Number promotion |
| |
| The rules of number promotion are specified in the section on <<_math_operations,math operations>>. |
| |
| |
| [[closure-coercion]] |
| === Closure to type coercion |
| ==== Assigning a closure to a SAM type |
| A SAM type is a type which defines a single abstract method. This includes: |
| |
| [source,groovy] |
| .Functional interfaces |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=filter_sam_type,indent=0] |
| ---- |
| |
| [source,groovy] |
| .Abstract classes with single abstract method |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=greeter_sam_type,indent=0] |
| ---- |
| |
| Any closure can be converted into a SAM type using the `as` operator: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=assertions_explicit_closure_to_sam,indent=0] |
| ---- |
| |
| However, the `as Type` expression is optional since Groovy 2.2.0. You can omit it and simply write: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=assertions_implicit_closure_to_sam,indent=0] |
| ---- |
| |
| which means you are also allowed to use method pointers, as shown in the following example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=assertions_implicit_closure_to_sam_and_method_pointer,indent=0] |
| ---- |
| |
| ==== Calling a method accepting a SAM type with a closure |
| |
| The second and probably more important use case for closure to SAM type coercion is calling a method which accepts |
| a SAM type. Imagine the following method: |
| |
| [source,groovy] |
| ----- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=method_accepting_filter,indent=0] |
| ----- |
| |
| Then you can call it with a closure, without having to create an explicit implementation of the interface: |
| |
| [source,groovy] |
| ----- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=method_call_with_explicit_coercion,indent=0] |
| ----- |
| |
| But since Groovy 2.2.0, you are also able to omit the explicit coercion and call the method as if it used a closure: |
| |
| [source,groovy] |
| ----- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=method_call_with_implicit_coercion,indent=0] |
| ----- |
| |
| As you can see, this has the advantage of letting you use the closure syntax for method calls, that is to say put the |
| closure outside of the parenthesis, improving the readability of your code. |
| |
| ==== Closure to arbitrary type coercion |
| |
| In addition to SAM types, a closure can be coerced to any type and in particular interfaces. Let's define the |
| following interface: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=foobar_interface,indent=0] |
| ---- |
| |
| You can coerce a closure into the interface using the `as` keyword: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=foobar2closure_coercion,indent=0] |
| ---- |
| |
| This produces a class for which all methods are implemented using the closure: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=foobarintf_assertions,indent=0] |
| ---- |
| |
| But it is also possible to coerce a closure to any class. For example, we can replace the `interface` that we defined |
| with `class` without changing the assertions: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=closure2foobarclass,indent=0] |
| ---- |
| |
| === Map to type coercion |
| |
| Usually using a single closure to implement an interface or a class with multiple methods is not the way to go. As an |
| alternative, Groovy allows you to coerce a map into an interface or a class. In that case, keys of the map are |
| interpreted as method names, while the values are the method implementation. The following example illustrates the |
| coercion of a map into an `Iterator`: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=coerce_map_to_iterator,indent=0] |
| ---- |
| |
| Of course this is a rather contrived example, but illustrates the concept. You only need to implement those methods |
| that are actually called, but if a method is called that doesn't exist in the map a `MissingMethodException` or an |
| `UnsupportedOperationException` is thrown, depending on the arguments passed to the call, |
| as in the following example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=define_x_interface,indent=0] |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=call_existing_method,indent=0] |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=call_non_existing_method,indent=0] |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=call_notimplemented_method,indent=0] |
| ---- |
| |
| The type of the exception depends on the call itself: |
| |
| * `MissingMethodException` if the arguments of the call do not match those from the interface/class |
| * `UnsupportedOperationException` if the arguments of the call match one of the overloaded methods of the interface/class |
| |
| === String to enum coercion |
| |
| Groovy allows transparent `String` (or `GString`) to enum values coercion. Imagine you define the following enum: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=state_enum,indent=0] |
| ---- |
| |
| then you can assign a string to the enum without having to use an explicit `as` coercion: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=enum_coerce_assignment,indent=0] |
| ---- |
| |
| It is also possible to use a `GString` as the value: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=enum_coerce_assignment_gstring,indent=0] |
| ---- |
| |
| |
| However, this would throw a runtime error (`IllegalArgumentException`): |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=enum_coerce_assignment_wrong,indent=0] |
| ---- |
| |
| Note that it is also possible to use implicit coercion in switch statements: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=enum_switch_method,indent=0] |
| ---- |
| |
| in particular, see how the `case` use string constants. But if you call a method that uses an enum with a `String` |
| argument, you still have to use an explicit `as` coercion: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=enum_switch_test,indent=0] |
| ---- |
| |
| === Custom type coercion |
| |
| It is possible for a class to define custom coercion strategies by implementing the `asType` method. Custom coercion |
| is invoked using the `as` operator and is never implicit. As an example, |
| imagine you defined two classes, `Polar` and `Cartesian`, like in the following example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=polar_class_header,indent=0] |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=polar_class_footer,indent=0] |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=cartesian_class,indent=0] |
| ---- |
| |
| And that you want to convert from polar coordinates to cartesian coordinates. One way of doing this is to define |
| the `asType` method in the `Polar` class: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=polar_class_astype,indent=0] |
| ---- |
| |
| which allows you to use the `as` coercion operator: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=polar_astype_assert,indent=0] |
| ---- |
| |
| Putting it all together, the `Polar` class looks like this: |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=polar_class_header,indent=0] |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=polar_class_astype,indent=4] |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=polar_class_footer,indent=0] |
| ---- |
| |
| but it is also possible to define `asType` outside of the `Polar` class, which can be practical if you want to define |
| custom coercion strategies for "closed" classes or classes for which you don't own the source code, for example using |
| a metaclass: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=polar_metaclass_astype,indent=0] |
| ---- |
| |
| === Class literals vs variables and the as operator |
| |
| Using the `as` keyword is only possible if you have a static reference to a class, like in the following code: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=as_keyword,indent=0] |
| ---- |
| |
| But what if you get the class by reflection, for example by calling `Class.forName`? |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=clazz_greeter_header,indent=0] |
| ---- |
| |
| Trying to use the reference to the class with the `as` keyword would fail: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=incorrect_as_usage,indent=0] |
| ---- |
| |
| It is failing because the `as` keyword only works with class literals. Instead, you need to call the `asType` method: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CoercionTest.groovy[tags=fixed_as_usage,indent=0] |
| ---- |
| |
| == Optionality |
| |
| === Optional parentheses |
| |
| Method calls can omit the parentheses if there is at least one parameter and there is no ambiguity: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/OptionalityTest.groovy[tags=optional_parentheses,indent=0] |
| ---- |
| |
| Parentheses are required for method calls without parameters or ambiguous method calls: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/OptionalityTest.groovy[tags=required_parentheses,indent=0] |
| ---- |
| |
| === Optional semicolons |
| |
| In Groovy semicolons at the end of the line can be omitted, if the line contains only a single statement. |
| |
| This means that: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/OptionalityTest.groovy[tags=single_statement_with_semicolon,indent=0] |
| ---- |
| |
| can be more idiomatically written as: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/OptionalityTest.groovy[tags=single_statement_without_semicolon,indent=0] |
| ---- |
| |
| Multiple statements in a line require semicolons to separate them: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/OptionalityTest.groovy[tags=statements_separated_by_semicolon,indent=0] |
| ---- |
| |
| === Optional return keyword |
| |
| In Groovy, the last expression evaluated in the body of a method or a closure is returned. This means that the `return` keyword is optional. |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/OptionalityTest.groovy[tags=return_keyword,indent=0] |
| ---- |
| |
| Can be shortened to: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/OptionalityTest.groovy[tags=omitted_return_keyword,indent=0] |
| ---- |
| |
| === Optional public keyword |
| |
| By default, Groovy classes and methods are `public`. Therefore this class: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/OptionalityTest.groovy[tags=public_keyword,indent=0] |
| ---- |
| |
| is identical to this class: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/OptionalityTest.groovy[tags=omitted_public,indent=0] |
| ---- |
| |
| [[Groovy-Truth]] |
| == The Groovy Truth |
| |
| Groovy decides whether a expression is true or false by applying the rules given below. |
| |
| === Boolean expressions |
| True if the corresponding Boolean value is `true`. |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/TheGroovyTruthTest.groovy[tags=boolean_truth,indent=0] |
| ---- |
| |
| === Collections and Arrays |
| Non-empty Collections and arrays are true. |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/TheGroovyTruthTest.groovy[tags=collection_truth,indent=0] |
| ---- |
| |
| === Matchers |
| True if the Matcher has at least one match. |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/TheGroovyTruthTest.groovy[tags=matcher_truth,indent=0] |
| ---- |
| |
| === Iterators and Enumerations |
| Iterators and Enumerations with further elements are coerced to true. |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/TheGroovyTruthTest.groovy[tags=iterator_enumeration_truth,indent=0] |
| ---- |
| |
| === Maps |
| Non-empty Maps are evaluated to true. |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/TheGroovyTruthTest.groovy[tags=map_truth,indent=0] |
| ---- |
| |
| === Strings |
| Non-empty Strings, GStrings and CharSequences are coerced to true. |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/TheGroovyTruthTest.groovy[tags=string_truth,indent=0] |
| ---- |
| |
| === Numbers |
| Non-zero numbers are true. |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/TheGroovyTruthTest.groovy[tags=number_truth,indent=0] |
| ---- |
| |
| === Object References |
| Non-null object references are coerced to true. |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/TheGroovyTruthTest.groovy[tags=object_truth,indent=0] |
| ---- |
| |
| === Customizing the truth with asBoolean() methods |
| |
| In order to customize whether groovy evaluates your object to `true` or `false` implement the `asBoolean()` method: |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/TheGroovyTruthTest.groovy[tags=asBoolean_object,indent=0] |
| ---- |
| |
| Groovy will call this method to coerce your object to a boolean value, e.g.: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/semantics/TheGroovyTruthTest.groovy[tags=asBoolean_usage,indent=0] |
| ---- |
| |
| == Typing |
| |
| === Optional typing |
| |
| Optional typing is the idea that a program can work even if you don't put an explicit type on a variable. Being a dynamic |
| language, Groovy naturally implements that feature, for example when you declare a variable: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/OptionalTypingTest.groovy[tags=optionaltyping_var,indent=0] |
| ---- |
| <1> `foo` is declared using an explicit type, `String` |
| <2> we can call the `toUpperCase` method on a `String` |
| |
| Groovy will let you write this instead: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/OptionalTypingTest.groovy[tags=optionaltyping_var_def,indent=0] |
| ---- |
| <1> `foo` is declared using `def` |
| <2> we can still call the `toUpperCase` method, because the type of `aString` is resolved at runtime |
| |
| So it doesn't matter that you use an explicit type here. It is in particular interesting when you combine this feature |
| with <<static-type-checking,static type checking>>, because the type checker performs type inference. |
| |
| Likewise, Groovy doesn't make it mandatory to declare the types of a parameter in a method: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/OptionalTypingTest.groovy[tags=optionaltyping_orig,indent=0] |
| ---- |
| |
| can be rewritten using `def` as both return type and parameter types, in order to take advantage of duck typing, as |
| illustrated in this example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/OptionalTypingTest.groovy[tags=optionaltyping_def,indent=0] |
| ---- |
| <1> both the return type and the parameter types use `def` |
| <2> it makes it possible to use the method with `String` |
| <3> but also with `int` since the `plus` method is defined |
| |
| TIP: Using the `def` keyword here is recommended to describe the intent of a method which is supposed to work on any |
| type, but technically, we could use `Object` instead and the result would be the same: `def` is, in Groovy, strictly |
| equivalent to using `Object`. |
| |
| Eventually, the type can be removed altogether from both the return type and the descriptor. But if you want to remove |
| it from the return type, you then need to add an explicit modifier for the method, so that the compiler can make a difference |
| between a method declaration and a method call, like illustrated in this example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/OptionalTypingTest.groovy[tags=optionaltyping_notype,indent=0] |
| ---- |
| <1> if we want to omit the return type, an explicit modifier has to be set. |
| <2> it is still possible to use the method with `String` |
| <3> and also with `int` |
| |
| TIP: Omitting types is in general considered a bad practice in method parameters or method return types for public APIs. |
| While using `def` in a local variable is not really a problem because the visibility of the variable is limited to the |
| method itself, while set on a method parameter, `def` will be converted to `Object` in the method signature, making it |
| difficult for users to know which is the expected type of the arguments. This means that you should limit this to cases |
| where you are explicitly relying on duck typing. |
| |
| [[static-type-checking]] |
| === Static type checking |
| |
| By default, Groovy performs minimal type checking at compile time. Since it is primarily a dynamic language, |
| most checks that a static compiler would normally do aren't possible at compile time. A method added via runtime |
| metaprogramming might alter a class or object's runtime behavior. Let's illustrate why in the |
| following example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_intro,indent=0] |
| ---- |
| <1> the `Person` class only defines two properties, `firstName` and `lastName` |
| <2> we can create an instance of Person |
| <3> and call a method named `formattedName` |
| |
| It is quite common in dynamic languages for code such as the above example not to throw any error. How can this be? |
| In Java, this would typically fail at compile time. However, in Groovy, it will not fail at compile time, and if coded |
| correctly, will also not fail at runtime. In fact, to make this work at runtime, *one* possibility is to rely on |
| runtime metaprogramming. So just adding this line after the declaration of the `Person` class is enough: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_intro_magic,indent=0] |
| ---- |
| |
| This means that in general, in Groovy, you can't make any assumption about the type of an object beyond its declaration |
| type, and even if you know it, you can't determine at compile time what method will be called, or which property will |
| be retrieved. It has a lot of interest, going from writing DSLs to testing, which is discussed in other sections of this |
| manual. |
| |
| However, if your program doesn't rely on dynamic features and that you come from the static world (in particular, from |
| a Java mindset), not catching such "errors" at compile time can be surprising. As we have seen in the previous example, |
| the compiler cannot be sure this is an error. To make it aware that it is, you have to explicitly instruct the compiler |
| that you are switching to a type checked mode. This can be done by annotating a class or a method with `@groovy.transform.TypeChecked`. |
| |
| When type checking is activated, the compiler performs much more work: |
| |
| * type inference is activated, meaning that even if you use `def` on a local variable for example, the type checker will be |
| able to infer the type of the variable from the assignments |
| * method calls are resolved at compile time, meaning that if a method is not declared on a class, the compiler will throw an error |
| * in general, all the compile time errors that you are used to find in a static language will appear: method not found, property not found, |
| incompatible types for method calls, number precision errors, ... |
| |
| In this section, we will describe the behavior of the type checker in various situations and explain the limits of using |
| `@TypeChecked` on your code. |
| |
| ==== The `@TypeChecked` annotation |
| |
| ===== Activating type checking at compile time |
| |
| The `groovy.transform.TypeChecked` annotation enables type checking. It can be placed on a class: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=typechecked_class,indent=0] |
| ---- |
| |
| Or on a method: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=typechecked_method,indent=0] |
| ---- |
| |
| In the first case, all methods, properties, fields, inner classes, ... of the annotated class will be type checked, whereas |
| in the second case, only the method and potential closures or anonymous inner classes that it contains will be type checked. |
| |
| ===== Skipping sections |
| |
| The scope of type checking can be restricted. For example, if a class is type checked, you can instruct the type checker |
| to skip a method by annotating it with `@TypeChecked(TypeCheckingMode.SKIP)`: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_skip,indent=0] |
| ---- |
| <1> the `GreetingService` class is marked as type checked |
| <2> so the `greeting` method is automatically type checked |
| <3> but `doGreet` is marked with `SKIP` |
| <4> the type checker doesn't complain about missing properties here |
| |
| In the previous example, `SentenceBuilder` relies on dynamic code. There's no real `Hello` method or property, so the |
| type checker would normally complain and compilation would fail. Since the method that uses the builder is marked with |
| `TypeCheckingMode.SKIP`, type checking is _skipped_ for this method, so the code will compile, even if the rest of the |
| class is type checked. |
| |
| The following sections describe the semantics of type checking in Groovy. |
| |
| ==== Type checking assignments |
| |
| An object `o` of type `A` can be assigned to a variable of type `T` if and only if: |
| |
| * `T` equals `A` |
| |
| + |
| |
| [.result] |
| ==== |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_assign_equals,indent=0] |
| ---- |
| ==== |
| |
| * _or_ `T` is one of `String`, `boolean`, `Boolean` or `Class` |
| |
| + |
| |
| [.result] |
| ==== |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_assign_specialcase,indent=0] |
| ---- |
| ==== |
| |
| * _or_ `o` is null and `T` is not a primitive type |
| |
| + |
| |
| [.result] |
| ==== |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_assign_null,indent=0] |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_assign_null2prim,indent=0] |
| ---- |
| ==== |
| |
| * _or_ `T` is an array and `A` is an array and the component type of `A` is assignable to the component type of `T` |
| |
| + |
| |
| [.result] |
| ==== |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_assign_array,indent=0] |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_assign_array_fail,indent=0] |
| ---- |
| ==== |
| |
| * _or_ `T` is an array and `A` is a list and the component type of `A` is assignable to the component type of `T` |
| |
| + |
| |
| [.result] |
| ==== |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_assign_array_list,indent=0] |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_assign_array_list_fail,indent=0] |
| ---- |
| ==== |
| |
| * _or_ `T` is a superclass of `A` |
| |
| + |
| |
| [.result] |
| ==== |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_assign_superclass,indent=0] |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_assign_superclass_fail,indent=0] |
| ---- |
| ==== |
| |
| * _or_ `T` is an interface implemented by `A` |
| |
| |
| + |
| |
| [.result] |
| ==== |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_assign_interface,indent=0] |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_assign_interface_fail,indent=0] |
| ---- |
| ==== |
| |
| * _or_ `T` or `A` are a primitive type and their boxed types are assignable |
| |
| + |
| |
| [.result] |
| ==== |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_assign_prim,indent=0] |
| ---- |
| ==== |
| |
| * _or_ `T` extends `groovy.lang.Closure` and `A` is a SAM-type (single abstract method type) |
| |
| + |
| |
| [.result] |
| ==== |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_closure_coercion,indent=0] |
| ---- |
| ==== |
| |
| * _or_ `T` and `A` derive from `java.lang.Number` and conform to the following table |
| |
| |
| [cols="1,1,2a"] |
| .Number types (java.lang.XXX) |
| [[number-assignment]] |
| |=== |
| |T |A |Examples |
| |
| |Double |
| |Any but BigDecimal or BigInteger |
| | [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_assign_to_double,indent=0] |
| ---- |
| |
| |Float |
| |Any type but BigDecimal, BigInteger or Double |
| | [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_assign_to_float,indent=0] |
| ---- |
| |
| |Long |
| |Any type but BigDecimal, BigInteger, Double or Float |
| | [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_assign_to_long,indent=0] |
| ---- |
| |
| |Integer |
| |Any type but BigDecimal, BigInteger, Double, Float or Long |
| | [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_assign_to_int,indent=0] |
| ---- |
| |
| |Short |
| |Any type but BigDecimal, BigInteger, Double, Float, Long or Integer |
| | [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_assign_to_short,indent=0] |
| ---- |
| |
| |Byte |
| |Byte |
| | [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_assign_to_byte,indent=0] |
| ---- |
| |
| |=== |
| |
| ==== List and map constructors |
| |
| In addition to the assignment rules above, if an assignment is deemed invalid, in type checked mode, a _list_ literal or a _map_ literal `A` can be assigned |
| to a variable of type `T` if: |
| |
| * the assignment is a variable declaration and `A` is a list literal and `T` has a constructor whose parameters match the types of the elements in the list literal |
| * the assignment is a variable declaration and `A` is a map literal and `T` has a no-arg constructor and a property for each of the map keys |
| |
| For example, instead of writing: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_ctor_point_classic,indent=0] |
| ---- |
| |
| You can use a "list constructor": |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_ctor_point_list,indent=0] |
| ---- |
| |
| or a "map constructor": |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_ctor_point_map,indent=0] |
| ---- |
| |
| If you use a map constructor, additional checks are done on the keys of the map to check if a property of the same name |
| is defined. For example, the following will fail at compile time: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_ctor_fail,indent=0] |
| ---- |
| <1> The type checker will throw an error `No such property: age for class: Person` at compile time |
| |
| ==== Method resolution |
| |
| In type checked mode, methods are resolved at compile time. Resolution works by name and arguments. The return type is |
| irrelevant to method selection. Types of arguments are matched against the types of the parameters following those rules: |
| |
| An argument `o` of type `A` can be used for a parameter of type `T` if and only if: |
| |
| * `T` equals `A` |
| |
| + |
| |
| [.result] |
| ==== |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_argparam_equals,indent=0] |
| ---- |
| ==== |
| |
| * _or_ `T` is a `String` and `A` is a `GString` |
| |
| + |
| |
| [.result] |
| ==== |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_argparam_specialcase,indent=0] |
| ---- |
| ==== |
| |
| * _or_ `o` is null and `T` is not a primitive type |
| |
| + |
| |
| [.result] |
| ==== |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_argparam_null,indent=0] |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_argparam_null2prim,indent=0] |
| ---- |
| ==== |
| |
| * _or_ `T` is an array and `A` is an array and the component type of `A` is assignable to the component type of `T` |
| |
| + |
| |
| [.result] |
| ==== |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_argparam_array,indent=0] |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_argparam_array_fail,indent=0] |
| ---- |
| ==== |
| |
| * _or_ `T` is a superclass of `A` |
| |
| + |
| |
| [.result] |
| ==== |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_argparam_superclass,indent=0] |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_argparam_superclass_fail,indent=0] |
| ---- |
| ==== |
| |
| * _or_ `T` is an interface implemented by `A` |
| |
| + |
| |
| [.result] |
| ==== |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_argparam_interface,indent=0] |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_argparam_interface_fail,indent=0] |
| ---- |
| ==== |
| |
| * _or_ `T` or `A` are a primitive type and their boxed types are assignable |
| |
| + |
| |
| [.result] |
| ==== |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_argparam_prim,indent=0] |
| ---- |
| ==== |
| |
| * _or_ `T` extends `groovy.lang.Closure` and `A` is a SAM-type (single abstract method type) |
| |
| + |
| |
| [.result] |
| ==== |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=stc_arg_closure_coercion,indent=0] |
| ---- |
| ==== |
| |
| * _or_ `T` and `A` derive from `java.lang.Number` and conform to the same rules as <<number-assignment,assignment of numbers>> |
| |
| If a method with the appropriate name and arguments is not found at compile time, an error is thrown. The difference with "normal" Groovy is |
| illustrated in the following example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=method_not_type_checked,indent=0] |
| ---- |
| <1> `printLine` is an error, but since we're in a dynamic mode, the error is not caught at compile time |
| |
| The example above shows a class that Groovy will be able to compile. However, if you try to create an instance of `MyService` and call the |
| `doSomething` method, then it will fail *at runtime*, because `printLine` doesn't exist. Of course, we already showed how Groovy could make |
| this a perfectly valid call, for example by catching `MethodMissingException` or implementing a custom meta-class, but if you know you're |
| not in such a case, `@TypeChecked` comes handy: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=method_type_checked,indent=0] |
| ---- |
| <1> `printLine` is this time a compile-time error |
| |
| Just adding `@TypeChecked` will trigger compile time method resolution. The type checker will try to find a method `printLine` accepting |
| a `String` on the `MyService` class, but cannot find one. It will fail compilation with the following message: |
| |
| `Cannot find matching method MyService#printLine(java.lang.String)` |
| |
| ==== |
| IMPORTANT: It is important to understand the logic behind the type checker: it is a compile-time check, so by definition, the type checker |
| is not aware of any kind of *runtime* metaprogramming that you do. This means that code which is perfectly valid without `@TypeChecked` will |
| *not* compile anymore if you activate type checking. This is in particular true if you think of duck typing: + |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=ducktyping_failure,indent=0] |
| ---- |
| <1> we define a `Duck` class which defines a `quack` method |
| <2> we define another `QuackingBird` class which also defines a `quack` method |
| <3> `quacker` is loosely typed, so since the method is `@TypeChecked`, we will obtain a compile-time error |
| <4> even if in non type-checked Groovy, this would have passed |
| |
| There are possible workarounds, like introducing an interface, but basically, by activating type checking, you gain type safety |
| but you loose some features of the language. Hopefully, Groovy introduces some features like flow typing to reduce the gap between |
| type-checked and non type-checked Groovy. |
| ==== |
| |
| [[type-inference]] |
| ==== Type inference |
| |
| ===== Principles |
| |
| When code is annotated with `@TypeChecked`, the compiler performs type inference. It doesn't simply rely on static types, but also uses various |
| techniques to infer the types of variables, return types, literals, ... so that the code remains as clean as possible even if you activate the |
| type checker. |
| |
| The simplest example is inferring the type of a variable: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=simple_var_type_inference,indent=0] |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=simple_var_type_inference_fail,indent=0] |
| ---- |
| <1> a variable is declared using the `def` keyword |
| <2> calling `toUpperCase` is allowed by the type checker |
| <3> calling `upper` will fail at compile time |
| |
| The reason the call to `toUpperCase` works is because the type of `message` was _inferred_ as being a `String`. |
| |
| ===== Variables vs fields in type inference |
| |
| It is worth noting that although the compiler performs type inference on local variables, it does *not* perform any kind |
| of type inference on fields, always falling back to the *declared type* of a field. To illustrate this, let's take a |
| look at this example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=typeinference_field_vs_local_variable,indent=0] |
| ---- |
| <1> `someUntypedField` uses `def` as a declaration type |
| <2> `someTypedField` uses `String` as a declaration type |
| <3> we can assign *anything* to `someUntypedField` |
| <4> yet calling `toUpperCase` fails at compile time because the field is not typed properly |
| <5> we can assign a `String` to a field of type `String` |
| <6> and this time `toUpperCase` is allowed |
| <7> if we assign a `String` to a local variable |
| <8> then calling `toUpperCase` is allowed on the local variable |
| |
| Why such a difference? The reason is _thread safety_. At compile time, we can't make *any* guarantee about the type of |
| a field. Any thread can access any field at any time and between the moment a field is assigned a variable of some |
| type in a method and the time is is used the line after, another thread may have changed the contents of the field. This |
| is not the case for local variables: we know if they "escape" or not, so we can make sure that the type of a variable is |
| constant (or not) over time. Note that even if a field is final, the JVM makes no guarantee about it, so the type checker |
| doesn't behave differently if a field is final or not. |
| |
| TIP: This is one of the reasons why we recommend to use *typed* fields. While using `def` for local variables is perfectly |
| fine thanks to type inference, this is not the case for fields, which also belong to the public API of a class, hence the |
| type is important. |
| |
| ===== Collection literal type inference |
| |
| Groovy provides a syntax for various type literals. There are three native collection literals in Groovy: |
| |
| * lists, using the `[]` literal |
| * maps, using the `[:]` literal |
| * ranges, using `from..to` (inclusive) and `from..<to` (exclusive) |
| |
| The inferred type of a literal depends on the elements of the literal, as illustrated in the following table: |
| |
| [cols="2a,3a"] |
| |=== |
| |Literal| Inferred type |
| |
| |[source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=empty_list_literal_inference,indent=0] |
| ---- |
| |`java.util.List` |
| |
| |[source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=list_literal_inference_simple,indent=0] |
| ---- |
| |`java.util.List<String>` |
| |
| |[source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=list_literal_inference_gstring,indent=0] |
| ---- |
| |`java.util.List<GString>` be careful, a `GString` is *not* a `String`! |
| |
| |[source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=empty_map_literal_inference,indent=0] |
| ---- |
| |`java.util.LinkedHashMap` |
| |
| |[source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=map_literal_inference_simple,indent=0] |
| ---- |
| |`java.util.LinkedHashMap<String,String>` |
| |
| |[source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=map_literal_inference_gstring,indent=0] |
| ---- |
| |`java.util.LinkedHashMap<GString,String>` be careful, the key is a `GString`! |
| |
| |[source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=intRange_literal_inference,indent=0] |
| ---- |
| |`groovy.lang.IntRange` |
| |
| |[source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=charRange_literal_inference,indent=0] |
| ---- |
| |`groovy.lang.Range<String>` : uses the type of the bounds to infer the component type of the range |
| |
| |=== |
| |
| As you can see, with the noticeable exception of the `IntRange`, the inferred type makes use of generics types to describe |
| the contents of a collection. In case the collection contains elements of different types, the type checker still performs |
| type inference of the components, but uses the notion of <<section-lub,least upper bound>>. |
| |
| [[section-lub]] |
| ===== Least upper bound |
| |
| In Groovy, the _least upper bound_ of two types `A` and `B` is defined as a type which: |
| |
| * superclass corresponds to the common super class of `A` and `B` |
| * interfaces correspond to the interfaces implemented by both `A` and `B` |
| * if `A` or `B` is a primitive type and that `A` isn't equal to `B`, the least upper bound of `A` and `B` is the least |
| upper bound of their wrapper types |
| |
| If `A` and `B` only have one (1) interface in common and that their common superclass is `Object`, then the LUB of both |
| is the common interface. |
| |
| The least upper bound represents the minimal type to which both `A` and `B` can be assigned. So for example, if `A` and `B` |
| are both `String`, then the LUB (least upper bound) of both is also `String`. |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=least_upper_bound_simple,indent=0] |
| ---- |
| <1> the LUB of `String` and `String` is `String` |
| <2> the LUB of `ArrayList` and `LinkedList` is their common super type, `AbstractList` |
| <3> the LUB of `ArrayList` and `List` is their only common interface, `List` |
| <4> the LUB of two identical interfaces is the interface itself |
| <5> the LUB of `Bottom1` and `Bottom2` is their superclass `Top` |
| <6> the LUB of two types which have nothing in common is `Object` |
| |
| In those examples, the LUB is always representable as a normal, JVM supported, type. But Groovy internally represents the LUB |
| as a type which can be more complex, and that you wouldn't be able to use to define a variable for example. To illustrate this, |
| let's continue with this example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=least_upper_bound_complex,indent=0] |
| ---- |
| |
| What is the least upper bound of `Bottom` and `SerializableFooImpl`? They don't have a common super class (apart from `Object`), |
| but they do share 2 interfaces (`Serializable` and `Foo`), so their least upper bound is a type which represents the union of |
| two interfaces (`Serializable` and `Foo`). This type cannot be defined in the source code, yet Groovy knows about it. |
| |
| In the context of collection type inference (and generic type inference in general), this becomes handy, because the type of the |
| components is inferred as the least upper bound. We can illustrate why this is important in the following example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=least_upper_bound_collection_inference,indent=0] |
| ---- |
| <1> the `Greeter` interface defines a single method, `greet` |
| <2> the `Salute` interface defines a single method, `salute` |
| <3> class `A` implements both `Greeter` and `Salute` but there's no explicit interface extending both |
| <4> same for `B` |
| <5> but `B` defines an additional `exit` method |
| <6> the type of `list` is inferred as "list of the LUB of `A` and `B`" |
| <7> so it is possible to call `greet` which is defined on both `A` and `B` through the `Greeter` interface |
| <8> and it is possible to call `salute` which is defined on both `A` and `B` through the `Salute` interface |
| <9> yet calling `exit` is a compile time error because it doesn't belong to the LUB of `A` and `B` (only defined in `B`) |
| |
| The error message will look like: |
| |
| ---- |
| [Static type checking] - Cannot find matching method Greeter or Salute#exit() |
| ---- |
| |
| which indicates that the `exit` method is neither defines on `Greeter` nor `Salute`, which are the two interfaces defined |
| in the least upper bound of `A` and `B`. |
| |
| ===== instanceof inference |
| |
| In normal, non type checked, Groovy, you can write things like: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=instanceof_inference,indent=0] |
| ---- |
| <1> guard the method call with an `instanceof` check |
| <2> make the call |
| |
| The method call works because of dynamic dispatch (the method is selected at runtime). The equivalent code in Java would |
| require to cast `o` to a `Greeter` before calling the `greeting` method, because methods are selected at compile time: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=instanceof_java_equiv,indent=0] |
| ---- |
| |
| However, in Groovy, even if you add `@TypeChecked` (and thus activate type checking) on the `doSomething` method, the |
| cast is *not* necessary. The compiler embeds _instanceof_ inference that makes the cast optional. |
| |
| [[section-flow-typing]] |
| ===== Flow typing |
| |
| Flow typing is an important concept of Groovy in type checked mode and an extension of type inference. The idea is that |
| the compiler is capable of inferring the type of variables in the flow of the code, not just at initialization: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=flowtyping_basics,indent=0] |
| ---- |
| <1> first, `o` is declared using `def` and assigned a `String` |
| <2> the compiler inferred that `o` is a `String`, so calling `toUpperCase` is allowed |
| <3> `o` is reassigned with a `double` |
| <4> calling `Math.sqrt` passes compilation because the compiler knows that at this point, `o` is a `double` |
| |
| So the type checker is _aware_ of the fact that the concrete type of a variable is different over time. In particular, |
| if you replace the last assignment with: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=flowtyping_basics_fail,indent=0] |
| ---- |
| |
| The type checker will now fail at compile time, because it knows that `o` is a `double` when `toUpperCase` is called, |
| so it's a type error. |
| |
| It is important to understand that it is not the fact of declaring a variable with `def` that triggers type inference. |
| Flow typing works for *any* variable of any type. Declaring a variable with an explicit type only constrains what you |
| can assign to the variable: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=flowtyping_typeconstraints,indent=0] |
| ---- |
| <1> `list` is declared as an unchecked `List` and assigned a list literal of `String`s |
| <2> this line passes compilation because of flow typing: the type checker knows that `list` is at this point a `List<String>` |
| <3> but you can't assign a `String` to a `List` so this is a type checking error |
| |
| You can also note that even if the variable is declared *without* generics information, the type checker knows what is |
| the component type. Therefore, such code would fail compilation: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=flowtyping_typeconstraints_failure,indent=0] |
| ---- |
| <1> `list` is inferred as `List<String>` |
| <2> so adding an `int` to a `List<String>` is a compile-time error |
| |
| Fixing this requires adding an explicit generic type to the declaration: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=flowtyping_typeconstraints_fixed,indent=0] |
| ---- |
| <1> `list` declared as `List<? extends Serializable>` and initialized with an empty list |
| <2> elements added to the list conform to the declaration type of the list |
| <3> so adding an `int` to a `List<? extends Serializable>` is allowed |
| |
| Flow typing has been introduced to reduce the difference in semantics between classic and static Groovy. In particular, |
| consider the behavior of this code in Java: |
| |
| [source,java] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingJavaTest.java[tags=java_method_selection_head,indent=0] |
| include::{projectdir}/src/spec/test/typing/TypeCheckingJavaTest.java[tags=java_method_selection_body,indent=0] |
| ---- |
| <1> `o` is declared as an `Object` and assigned a `String` |
| <2> we call the `compute` method with `o` |
| <3> and print the result |
| |
| In Java, this code will output `Nope`, because method selection is done at compile time and based on the *declared* types. |
| So even if `o` is a `String` at runtime, it is still the `Object` version which is called, because `o` has been declared |
| as an `Object`. To be short, in Java, declared types are most important, be it variable types, parameter types or return |
| types. |
| |
| In Groovy, we could write: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=groovy_method_selection,indent=0] |
| ---- |
| |
| But this time, it will return `6`, because the method which is chosen is chosen *at runtime*, based on the _actual_ |
| argument types. So at runtime, `o` is a `String` so the `String` variant is used. Note that this behavior has nothing |
| to do with type checking, it's the way Groovy works in general: dynamic dispatch. |
| |
| In type checked Groovy, we want to make sure the type checker selects the same method *at compile time*, that the runtime |
| would choose. It is not possible in general, due to the semantics of the language, but we can make things better with flow |
| typing. With flow typing, `o` is _inferred_ as a `String` when the `compute` method is called, so the version which takes |
| a `String` and returns an `int` is chosen. This means that we can infer the return type of the method to be an `int`, and |
| not a `String`. This is important for subsequent calls and type safety. |
| |
| So in type checked Groovy, flow typing is a very important concept, which also implies that if `@TypeChecked` is applied, |
| methods are selected based on the _inferred types_ of the arguments, not on the declared types. This doesn't ensure 100% |
| type safety, because the type checker _may_ select a wrong method, but it ensures the closest semantics to dynamic Groovy. |
| |
| |
| ===== Advanced type inference |
| |
| A combination of <<section-flow-typing,flow typing>> and <<section-lub,least upper bound inference>> is used to perform |
| advanced type inference and ensure type safety in multiple situations. In particular, program control structures are |
| likely to alter the inferred type of a variable: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=flow_lub_ifelse_header,indent=0] |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=flow_lub_ifelse_test,indent=0] |
| ---- |
| <1> if `someCondition` is true, `o` is assigned a `Top` |
| <2> if `someCondition` is false, `o` is assigned a `Bottom` |
| <3> calling `methodFromTop` is safe |
| <4> but calling `methodFromBottom` is not, so it's a compile time error |
| |
| When the type checker visits an `if/else` control structure, it checks all variables which are assigned in `if/else` branches |
| and computes the <<section-lub,least upper bound>> of all assignments. This type is the type of the inferred variable |
| after the `if/else` block, so in this example, `o` is assigned a `Top` in the `if` branch and a `Bottom` in the `else` |
| branch. The <<section-lub,LUB>> of those is a `Top`, so after the conditional branches, the compiler infers `o` as being |
| a `Top`. Calling `methodFromTop` will therefore be allowed, but not `methodFromBottom`. |
| |
| The same reasoning exists with closures and in particular closure shared variables. A closure shared variable is a variable |
| which is defined outside of a closure, but used inside a closure, as in this example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=closure_shared_variable_definition,indent=0] |
| ---- |
| <1> a variable named `text` is declared |
| <2> `text` is used from inside a closure. It is a _closure shared variable_. |
| |
| Groovy allows developers to use those variables without requiring them to be final. This means that a closure shared |
| variable can be reassigned inside a closure: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=closure_shared_variable_ex1,indent=0] |
| ---- |
| |
| The problem is that a closure is an independent block of code that can be executed (or not) at *any* time. In particular, |
| `doSomething` may be asynchronous, for example. This means that the body of a closure doesn't belong to the main control |
| flow. For that reason, the type checker also computes, for each closure shared variable, the <<section-lub,LUB>> of all |
| assignments of the variable, and will use that `LUB` as the inferred type outside of the scope of the closure, like in |
| this example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=closure_shared_variable_ex2,indent=0] |
| ---- |
| <1> a closure-shared variable is first assigned a `Top` |
| <2> inside the closure, it is assigned a `Bottom` |
| <3> `methodFromTop` is allowed |
| <4> `methodFromBottom` is a compilation error |
| |
| Here, it is clear that when `methodFromBottom` is called, there's no guarantee, at compile-time or runtime that the |
| type of `o` will _effectively_ be a `Bottom`. There are chances that it will be, but we can't make sure, because it's |
| asynchronous. So the type checker will only allow calls on the <<section-lub,least upper bound>>, which is here a `Top`. |
| |
| ==== Closures and type inference |
| |
| The type checker performs special inference on closures, resulting on additional checks on one side and improved fluency |
| on the other side. |
| |
| ===== Return type inference |
| |
| The first thing that the type checker is capable of doing is inferring the _return type_ of a closure. This is simply |
| illustrated in the following example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=closure_return_type_inf,indent=0] |
| ---- |
| <1> a closure is defined, and it returns a string (more precisely a `GString`) |
| <2> we call the closure and assign the result to a variable |
| <3> the type checker inferred that the closure would return a string, so calling `length()` is allowed |
| |
| As you can see, unlike a method which declares its return type explicitly, there's no need to declare the return type |
| of a closure: its type is inferred from the body of the closure. |
| |
| .Closures vs methods |
| **** |
| It's worth noting that return type inference is only applicable to closures. While the type checker could do the |
| same on a method, it is in practice not desirable: _in general_, methods can be overridden and it is not statically |
| possible to make sure that the method which is called is not an overridden version. So flow typing would actually |
| think that a method returns something, while in reality, it could return something else, like illustrated in the |
| following example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=method_return_type_matters,indent=0] |
| ---- |
| <1> class `A` defines a method `compute` which effectively returns a `String` |
| <2> this will fail compilation because the return type of `compute` is `def`(aka `Object`) |
| <3> class `B` extends `A` and redefines `compute`, this type returning an `int` |
| |
| As you can see, if the type checker relied on the inferred return type of a method, with <<section-flow-typing,flow typing>>, |
| the type checker could determine that it is ok to call `toUpperCase`. It is in fact an *error*, because a subclass can |
| override `compute` and return a different object. Here, `B#compute` returns an `int`, so someone calling `computeFully` |
| on an instance of `B` would see a runtime error. The compiler prevents this from happening by using the declared return |
| type of methods instead of the inferred return type. |
| |
| For consistency, this behavior is the same for *every* method, even if they are static or final. |
| **** |
| |
| ===== Parameter type inference |
| |
| In addition to the return type, it is possible for a closure to infer its parameter types from the context. There are |
| two ways for the compiler to infer the parameter types: |
| |
| * through _implicit SAM type coercion_ |
| * through API metadata |
| |
| To illustrate this, lets start with an example that will fail compilation due to the inability for the type checker |
| to infer the parameter types: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=cl_pt_failure,indent=0] |
| ---- |
| <1> the `inviteIf` method accepts a `Person` and a `Closure` |
| <2> we call it with a `Person` and a `Closure` |
| <3> yet `it` is not statically known as being a `Person` and compilation fails |
| |
| In this example, the closure body contains `it.age`. With dynamic, not type checked code, this would work, because the |
| type of `it` would be a `Person` at runtime. Unfortunately, at compile-time, there's no way to know what is the type |
| of `it`, just by reading the signature of `inviteIf`. |
| |
| ====== Explicit closure parameters |
| |
| To be short, the type checker doesn't have enough contextual information on the `inviteIf` method to determine statically |
| the type of `it`. This means that the method call needs to be rewritten like this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=cl_pt_workaround,indent=0] |
| ---- |
| <1> the type of `it` needs to be declared explicitly |
| |
| By explicitly declaring the type of the `it` variable, you can workaround the problem and make this code statically |
| checked. |
| |
| ====== Parameters inferred from single-abstract method types |
| |
| For an API or framework designer, there are two ways to make this more elegant for users, so that they don't have to |
| declare an explicit type for the closure parameters. The first one, and easiest, is to replace the closure with a |
| SAM type: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=cl_pt_workaround_sam,indent=0] |
| ---- |
| <1> declare a `SAM` interface with an `apply` method |
| <2> `inviteIf` now uses a `Predicate<Person>` instead of a `Closure<Boolean>` |
| <3> there's no need to declare the type of the `it` variable anymore |
| <4> `it.age` compiles properly, the type of `it` is inferred from the `Predicate#apply` method signature |
| |
| TIP: By using this technique, we leverage the _automatic coercion of closures to SAM types_ feature of Groovy. The |
| question whether you should use a _SAM type_ or a _Closure_ really depends on what you need to do. In a lot of cases, |
| using a SAM interface is enough, especially if you consider functional interfaces as they are found in Java 8. However, |
| closures provide features that are not accessible to functional interfaces. In particular, closures can have a delegate, |
| and owner and can be manipulated as objects (for example, cloned, serialized, curried, ...) before being called. They can |
| also support multiple signatures (polymorphism). So if you need that kind of manipulation, it is preferable to switch to |
| the most advanced type inference annotations which are described below. |
| |
| The original issue that needs to be solved when it comes to closure parameter type inference, that is to say, statically |
| determining the types of the arguments of a closure _without_ having to have them explicitly declared, is that the Groovy |
| type system inherits the Java type system, which is insufficient to describe the types of the arguments. |
| |
| ====== The `@ClosureParams` annotation |
| |
| Groovy provides an annotation, `@ClosureParams` which is aimed at completing type information. This annotation is primarily |
| aimed at framework and API developers who want to extend the capabilities of the type checker by providing type inference |
| metadata. This is important if your library makes use of closures and that you want the maximum level of tooling support |
| too. |
| |
| Let's illustrate this by fixing the original example, introducing the `@ClosureParams` annotation: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=cl_pt_workaround_closureparams_imports,indent=0] |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=cl_pt_workaround_closureparams_method,indent=0] |
| include::{projectdir}/src/spec/test/typing/TypeCheckingTest.groovy[tags=cl_pt_workaround_closureparams_call,indent=0] |
| ---- |
| <1> the closure parameter is annotated with `@ClosureParams` |
| <2> it's not necessary to use an explicit type for `it`, which is inferred |
| |
| The `@ClosureParams` annotation minimally accepts one argument, which is named a _type hint_. A type hint is a class which |
| is responsible for completing type information at compile time for the closure. In this example, the type hint being used |
| is `groovy.transform.stc.FirstParam` which indicated to the type checker that the closure will accept one parameter |
| whose type is the type of the first parameter of the method. In this case, the first parameter of the method is `Person`, |
| so it indicates to the type checker that the first parameter of the closure is in fact a `Person`. |
| |
| A second optional argument is named _options_. It's semantics depend on the _type hint_ class. Groovy comes with |
| various bundled type hints, illustrated in the table below: |
| |
| [cols="1a,1,4a"] |
| .Predefined type hints |
| |=== |
| |Type hint |Polymorphic? |Description and examples |
| |
| |`FirstParam` + |
| `SecondParam` + |
| `ThirdParam` |
| |No |
| |The first (resp. second, third) parameter type of the method + |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingHintsTest.groovy[tags=typehint_firstparam,indent=0] |
| ---- |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingHintsTest.groovy[tags=typehint_secondparam,indent=0] |
| ---- |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingHintsTest.groovy[tags=typehint_thirdparam,indent=0] |
| ---- |
| |
| |`FirstParam.FirstGenericType` + |
| `SecondParam.FirstGenericType` + |
| `ThirdParam.FirstGenericType` |
| |No |
| |The first generic type of the first (resp. second, third) parameter of the method + |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingHintsTest.groovy[tags=typehint_firstgt,indent=0] |
| ---- |
| |
| Variants for `SecondGenericType` and `ThirdGenericType` exist for all `FirstParam`, `SecondParam` and `ThirdParam` |
| type hints. |
| |
| |`SimpleType` |
| |No |
| |A type hint for which the type of closure parameters comes from the options string. + |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingHintsTest.groovy[tags=typehint_simpletype,indent=0] |
| ---- |
| |
| This type hint supports a *single* signature and each of the parameter is specified as a value of the _options_ array |
| using a fully-qualified type name or a primitive type. |
| |
| |`MapEntryOrKeyValue` |
| |Yes |
| |A dedicated type hint for closures that either work on a `Map.Entry` single parameter, or two parameters corresponding |
| to the key and the value. + |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingHintsTest.groovy[tags=typehint_mapentry,indent=0] |
| ---- |
| |
| This type hint *requires* that the first argument is a `Map` type, and infers the closure parameter types from the map |
| actual key/value types. |
| |
| |`FromAbstractTypeMethods` |
| |Yes |
| |Infers closure parameter types from the abstract method of some type. A signature is inferred for *each* abstract method. + |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingHintsTest.groovy[tags=typehint_from_abstract_type,indent=0] |
| ---- |
| |
| If there are multiple signatures like in the example above, the type checker will *only* be able to infer the types of |
| the arguments if the arity of each method is different. In the example above, `firstSignature` takes 2 arguments and |
| `secondSignature` takes 1 argument, so the type checker can infer the argument types based on the number of arguments. |
| But see the optional resolver class attribute discussed next. |
| |
| |`FromString` |
| |Yes |
| |Infers the closure parameter types from the `options` argument. The `options` argument consists of an array of comma-separated |
| non-primitive types. Each element of the array corresponds to a single signature, and each comma in an element separate |
| parameters of the signature. In short, this is the most generic type hint, and each string of the `options` map is *parsed* |
| as if it was a signature literal. While being very powerful, this type hint must be avoided if you can because it increases |
| the compilation times due to the necessity of parsing the type signatures. |
| |
| A single signature for a closure accepting a `String`: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingHintsTest.groovy[tags=typehint_from_string_1,indent=0] |
| ---- |
| |
| A polymorphic closure, accepting either a `String` or a `String, Integer`: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingHintsTest.groovy[tags=typehint_from_string_2,indent=0] |
| ---- |
| |
| A polymorphic closure, accepting either a `T` or a pair `T,T`: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/TypeCheckingHintsTest.groovy[tags=typehint_from_string_3,indent=0] |
| ---- |
| |
| |=== |
| |
| TIP: Even though you use `FirstParam`, `SecondParam` or `ThirdParam` as a type hint, it doesn't strictly mean that the |
| argument which will be passed to the closure *will* be the first (resp. second, third) argument of the method call. It |
| only means that the *type* of the parameter of the closure will be the *same* as the type of the first (resp. second, |
| third) argument of the method call. |
| |
| In short, the lack of the `@ClosureParams` annotation on a method accepting a `Closure` will *not* fail compilation. If |
| present (and it can be present in Java sources as well as Groovy sources), then the type checker has *more* information |
| and can perform additional type inference. This makes this feature particularly interesting for framework developers. |
| |
| A third optional argument is named _conflictResolutionStrategy_. It can reference a class (extending from |
| `ClosureSignatureConflictResolver`) that can perform additional resolution of parameter types if more than |
| one are found after initial inference calculations are complete. Groovy comes with the a default type resolver |
| which does nothing, and another which selects the first signature if multiple are found. The resolver is |
| only invoked if more than one signature is found and is by design a post processor. Any statements which need |
| injected typing information must pass one of the parameter signatures determined through type hints. The |
| resolver then picks among the returned candidate signatures. |
| |
| ===== `@DelegatesTo` |
| |
| The `@DelegatesTo` annotation is used by the type checker to infer the type of the delegate. It allows the API designer |
| to instruct the compiler what is the type of the delegate and the delegation strategy. The `@DelegatesTo` annotation is |
| discussed in a link:core-domain-specific-languages.html#section-delegatesto[specific section]. |
| |
| === Static compilation |
| |
| ==== Dynamic vs static |
| |
| In the <<static-type-checking,type checking section>>, we have seen that Groovy provides optional type checking thanks to the |
| `@TypeChecked` annotation. The type checker runs at compile time and performs a static analysis of dynamic code. The |
| program will behave exactly the same whether type checking has been enabled or not. This means that the `@TypeChecked` |
| annotation is neutral with regards to the semantics of a program. Even though it may be necessary to add type information |
| in the sources so that the program is considered type safe, in the end, the semantics of the program are the same. |
| |
| While this may sound fine, there is actually one issue with this: type checking of dynamic code, done at compile time, is |
| by definition only correct if no runtime specific behavior occurs. For example, the following program passes type checking: |
| |
| [[typechecked-defeated]] |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/StaticCompilationIntroTest.groovy[tags=intro_typesafe,indent=0] |
| ---- |
| |
| There are two `compute` methods. One accepts a `String` and returns an `int`, the other accepts an `int` and returns |
| a `String`. If you compile this, it is considered type safe: the inner `compute('foobar')` call will return an `int`, |
| and calling `compute` on this `int` will in turn return a `String`. |
| |
| Now, before calling `test()`, consider adding the following line: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/StaticCompilationIntroTest.groovy[tags=intro_typesafe_magic,indent=0] |
| ---- |
| |
| Using runtime metaprogramming, we're actually modifying the behavior of the `compute(String)` method, so that instead of |
| returning the length of the provided argument, it will return a `Date`. If you execute the program, it will fail at |
| runtime. Since this line can be added from anywhere, in any thread, there's absolutely no way for the type checker to |
| statically make sure that no such thing happens. In short, the type checker is vulnerable to monkey patching. This is |
| just one example, but this illustrates the concept that doing static analysis of a dynamic program is inherently wrong. |
| |
| The Groovy language provides an alternative annotation to `@TypeChecked` which will actually make sure that the methods |
| which are inferred as being called *will* effectively be called at runtime. This annotation turns the Groovy compiler |
| into a *static compiler*, where all method calls are resolved at compile time *and* the generated bytecode makes sure |
| that this happens: the annotation is `@groovy.transform.CompileStatic`. |
| |
| [[compilestatic-annotation]] |
| ==== The `@CompileStatic` annotation |
| |
| The `@CompileStatic` annotation can be added anywhere the `@TypeChecked` annotation can be used, that is to say on |
| a class or a method. It is not necessary to add both `@TypeChecked` and `@CompileStatic`, as `@CompileStatic` performs |
| everything `@TypeChecked` does, but in addition triggers static compilation. |
| |
| Let's take the <<typechecked-defeated,example which failed>>, but this time let's replace the `@TypeChecked` annotation |
| with `@CompileStatic`: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/typing/StaticCompilationIntroTest.groovy[tags=intro_typesafe_compilestatic,indent=0] |
| include::{projectdir}/src/spec/test/typing/StaticCompilationIntroTest.groovy[tags=intro_typesafe_magic,indent=0] |
| test() |
| ---- |
| |
| This is the *only* difference. If we execute this program, this time, there is no runtime error. The `test` method |
| became immune to monkey patching, because the `compute` methods which are called in its body are linked at compile |
| time, so even if the metaclass of `Computer` changes, the program still behaves *as expected by the type checker*. |
| |
| ==== Key benefits |
| |
| There are several benefits of using `@CompileStatic` on your code: |
| |
| * type safety |
| * immunity to <<compilestatic-annotation,monkey patching>> |
| * performance improvements |
| |
| The performance improvements depend on the kind of program you are executing. If it is I/O bound, the difference between |
| statically compiled code and dynamic code is barely noticeable. On highly CPU intensive code, since the bytecode which |
| is generated is very close, if not equal, to the one that Java would produce for an equivalent program, the performance |
| is greatly improved. |
| |
| TIP: Using the _invokedynamic_ version of Groovy, which is accessible to people using JDK 7 and above, the performance |
| of the dynamic code should be very close to the performance of statically compiled code. Sometimes, it can even be faster! |
| There is only one way to determine which version you should choose: measuring. The reason is that depending on your program |
| *and* the JVM that you use, the performance can be significantly different. In particular, the _invokedynamic_ version of |
| Groovy is very sensitive to the JVM version in use. |
| |
| include::type-checking-extensions.adoc[leveloffset=+1] |
| |