This is an automated email from the ASF dual-hosted git repository.
paulk pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/groovy-website.git
The following commit(s) were added to refs/heads/asf-site by this push:
new 8023c04 latest additions
8023c04 is described below
commit 8023c047872e3d32bb494d286454d1b624ee68fe
Author: Paul King <[email protected]>
AuthorDate: Tue Mar 31 21:21:54 2026 +1000
latest additions
---
site/src/site/releasenotes/groovy-6.0.adoc | 210 ++++++++++++++++++++++-------
1 file changed, 162 insertions(+), 48 deletions(-)
diff --git a/site/src/site/releasenotes/groovy-6.0.adoc
b/site/src/site/releasenotes/groovy-6.0.adoc
index c9bd910..12d6ea4 100644
--- a/site/src/site/releasenotes/groovy-6.0.adoc
+++ b/site/src/site/releasenotes/groovy-6.0.adoc
@@ -25,7 +25,9 @@ TBD
Groovy provides over 2000 extension methods to 150+ JDK classes to enhance JDK
functionality, with new methods added in Groovy 6. These methods reduce
dependency on third-party libraries for common tasks, and make code more
intuitive. Let's explore some highlights from those new methods.
-Several variants of `groupByMany` exist for grouping lists and maps of items.
+Several variants of `groupByMany`
+(https://issues.apache.org/jira/browse/GROOVY-11808[GROOVY-11808])
+exist for grouping lists and maps of items.
The most common form takes a closure (or lambda) which converts from some item
into a list of keys to group by (in this case group words by the vowels they
contain):
@@ -98,7 +100,9 @@ assert firstLetters.groupByMany() == [o:[1], t:[2, 3], f:[4,
5]]
== Grape: Dual Engine Support
-Groovy 6 introduces a major evolution of the Grape dependency management
system with the addition of a second built-in engine implementation alongside
the existing Apache Ivy-based engine.
+Groovy 6 introduces a major evolution of the Grape dependency management system
+(https://issues.apache.org/jira/browse/GROOVY-11871[GROOVY-11871])
+with the addition of a second built-in engine implementation alongside the
existing Apache Ivy-based engine.
=== What's new
@@ -262,11 +266,83 @@ Since the JVM does not support annotations on statements
in bytecode,
these are purely source-level transforms: the annotation drives
compile-time code generation and is then discarded.
-Two initial transforms take advantage of this capability:
+Several transforms take advantage of this capability.
+The `@Invariant` and `@Decreases` annotations from `groovy-contracts`
+can be placed on loops to assert invariant conditions and termination:
+
+[source,groovy]
+----
+int product = 1
+@Invariant({ product >= 1 })
+@Decreases({ 6 - i })
+for (int i = 1; i <= 5; i++) {
+ product *= i
+}
+assert product == 120
+----
+
+See the <<groovy-contracts>> section for more details and examples.
+
+=== `@Parallel` on for-in loops
+
+The `@Parallel` transform runs each iteration of a for-in loop
+in its own thread:
+
+[source,groovy]
+----
+@Parallel
+for (int i in 1..4) {
+ println i ** 2
+}
+// Output (non-deterministic order): 1, 16, 9, 4
+----
+
+When running on JDK 21+, `@Parallel` will use virtual threads
+for lightweight concurrency; on earlier JDKs it falls back to platform threads.
+
+NOTE: `@Parallel` is an incubating transform favoring simplicity.
+Production use should consider proper concurrency mechanisms.
+
+=== Writing custom statement-level transforms
+
+Any source-retention AST transform can now target `STATEMENT_TARGET`.
+The transform's `visit` method receives the `AnnotationNode` and the
+`Statement` (a `LoopingStatement`) as its AST node pair, following the
+same contract as existing class/method/field-level transforms.
+
+[[groovy-contracts]]
+== Groovy-Contracts Enhancements (incubating)
+
+The `groovy-contracts` module receives several enhancements in Groovy 6.
+
+=== Contracts in scripts
+
+Contract annotations now work in Groovy scripts, not just inside classes
+(https://issues.apache.org/jira/browse/GROOVY-11885[GROOVY-11885]).
+`@Requires` and `@Ensures` can be placed on script methods,
+and `@Invariant` can be placed on an import statement to apply
+as a class-level invariant for the script:
+
+[source,groovy]
+----
+@Invariant({ balance >= 0 })
+import groovy.transform.Field
+import groovy.contracts.Invariant
+
+@Field Integer balance = 5
+
+@Requires({ balance >= amount })
+def withdraw(int amount) { balance -= amount }
+
+def deposit(int amount) { balance += amount }
+
+deposit(5) // balance = 10, OK
+withdraw(20) // throws ClassInvariantViolation (balance would be -5)
+----
=== `@Invariant` on loops
-The existing `@Invariant` annotation from `groovy-contracts` can now be
+The existing `@Invariant` annotation can now be
placed on loops to assert a condition at the start of each iteration.
Violations throw a `LoopInvariantViolation`.
@@ -280,74 +356,91 @@ for (int i in 1..5) {
assert sum == 15
----
-This works with all loop types:
+This works with all loop types (for-in, classic for, while, do-while),
+and multiple invariants can be stacked on a single loop:
[source,groovy]
----
-// classic for loop
-int product = 1
-@Invariant({ product >= 1 })
-for (int i = 1; i <= 5; i++) {
- product *= i
+int sum = 0
+@Invariant({ sum >= 0 })
+@Invariant({ sum <= 100 })
+for (int i in 1..5) {
+ sum += i
}
-assert product == 120
+assert sum == 15
+----
-// while loop
+=== `@Decreases` on loops
+
+The new `@Decreases` annotation
+(https://issues.apache.org/jira/browse/GROOVY-11890[GROOVY-11890])
+specifies a loop termination measure
+(also known as a loop _variant_ in formal methods).
+The annotation takes a closure returning a value (or list of values)
+that must strictly decrease on every iteration while remaining non-negative.
+Since a non-negative integer that strictly decreases on every iteration
+must eventually reach zero, this gives confidence that the loop terminates.
+
+[source,groovy]
+----
int n = 10
-@Invariant({ n >= 0 })
+@Decreases({ n })
while (n > 0) {
n--
}
-assert n == 0
-
-// do-while loop
-int count = 0
-@Invariant({ count >= 0 })
-do {
- count++
-} while (count < 3)
-assert count == 3
----
-Multiple invariants can be stacked on a single loop:
+For loops with multiple quantities that change, a list can be returned
+for lexicographic comparison -- the first position where values differ
+must show a decrease:
[source,groovy]
----
-int sum = 0
-@Invariant({ sum >= 0 })
-@Invariant({ sum <= 100 })
-for (int i in 1..5) {
- sum += i
+int outer = 2, inner = 3
+@Decreases({ [outer, inner] })
+while (outer > 0) {
+ if (inner > 0) {
+ inner--
+ } else {
+ outer--
+ inner = 3
+ }
}
-assert sum == 15
----
-=== `@Parallel` on for-in loops
+If any iteration fails to decrease the measure,
+a `LoopVariantViolation` is thrown immediately.
-The `@Parallel` transform runs each iteration of a for-in loop
-in its own thread:
+=== Combining contracts
+
+These annotations can be combined to build strong confidence
+in the correctness of an algorithm. Consider this insertion sort
+that merges two pre-sorted lists:
[source,groovy]
----
-@Parallel
-for (int i in 1..4) {
- println i ** 2
+@Ensures({ result == result.sort() })
+List insertionSort(List in1, List in2) {
+ var out = []
+ var count = in1.size() + in2.size()
+ @Invariant({ in1.size() + in2.size() + out.size() == count })
+ @Decreases({ [in1.size(), in2.size()] })
+ while (in1 || in2) {
+ if (!in1) return out + in2
+ if (!in2) return out + in1
+ out += (in1[0] < in2[0]) ? in1.pop() : in2.pop()
+ }
+ out
}
-// Output (non-deterministic order): 1, 16, 9, 4
----
-When running on JDK 21+, `@Parallel` will use virtual threads
-for lightweight concurrency; on earlier JDKs it falls back to platform threads.
-
-NOTE: `@Parallel` is an incubating transform favoring simplicity.
-Production use should consider proper concurrency mechanisms.
-
-=== Writing custom statement-level transforms
-
-Any source-retention AST transform can now target `STATEMENT_TARGET`.
-The transform's `visit` method receives the `AnnotationNode` and the
-`Statement` (a `LoopingStatement`) as its AST node pair, following the
-same contract as existing class/method/field-level transforms.
+The `@Ensures` postcondition verifies that the result is sorted.
+The `@Invariant` asserts that no elements are lost or gained --
+the total number of elements across all three lists stays constant
+throughout the loop. The `@Decreases` annotation uses a lexicographic
+measure over the two input list sizes, giving us confidence that
+the loop terminates: on each iteration at least one input list
+shrinks, and they can never grow.
== Under exploration
@@ -396,6 +489,27 @@ JSON, Servlet, Swing, Testing) -- see the
https://groovy-lang.org/logging.html[logging guide]
for the full list of logger names and their levels.
+== Improved Annotation Validation
+
+Groovy 6 closes gaps in annotation target validation
+(https://issues.apache.org/jira/browse/GROOVY-11884[GROOVY-11884]).
+Previously, annotations could be placed on import statements and
+loop statements without validation -- for example, `@Deprecated import
java.lang.String`
+would compile without error even though `@Deprecated` does not target imports.
+
+The compiler now enforces that only annotations explicitly declaring
+Groovy-specific targets are permitted in these positions.
+A new `@ExtendedTarget` meta-annotation with an `ExtendedElementType` enum
+defines two Groovy-only targets:
+
+- `IMPORT` -- for annotations valid on import statements (e.g. `@Grab`,
`@Newify`, `@BaseScript`)
+- `LOOP` -- for annotations valid on loop statements (e.g. `@Invariant`,
`@Decreases`, `@Parallel`)
+
+Annotations without the appropriate `@ExtendedTarget` declaration
+are now flagged as compile errors when applied to these constructs.
+This may be a breaking change for code that previously relied on
+the lenient behavior.
+
[[Groovy6.0-breaking]]
== Breaking changes