This is an automated email from the ASF dual-hosted git repository.
paulk-asert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new 6466667142 GROOVY-12039: Graduate RegexChecker and FormatStringChecker
from incubating to stable
6466667142 is described below
commit 64666671425ea8a9658d1a9d5fc698cfe55f0e3b
Author: Paul King <[email protected]>
AuthorDate: Mon May 25 16:23:09 2026 +1000
GROOVY-12039: Graduate RegexChecker and FormatStringChecker from incubating
to stable
---
src/spec/doc/_type-checking-extensions.adoc | 15 +++-
.../src/spec/doc/_monadic-comprehensions.adoc | 18 +++++
.../groovy/groovy/typecheckers/FormatMethod.groovy | 2 +
.../groovy/typecheckers/FormatStringChecker.groovy | 4 +-
.../groovy/typecheckers/MonadicChecker.groovy | 6 +-
.../groovy/groovy/typecheckers/RegexChecker.groovy | 4 +-
.../groovy/groovy/typecheckers/package-info.groovy | 35 ++++++++-
.../src/spec/doc/typecheckers.adoc | 84 +++++++++++++++++++++-
8 files changed, 158 insertions(+), 10 deletions(-)
diff --git a/src/spec/doc/_type-checking-extensions.adoc
b/src/spec/doc/_type-checking-extensions.adoc
index b496d68e20..d74374476b 100644
--- a/src/spec/doc/_type-checking-extensions.adoc
+++ b/src/spec/doc/_type-checking-extensions.adoc
@@ -19,8 +19,18 @@
//////////////////////////////////////////
+[[type-checking-extensions]]
= Type checking extensions
+[TIP]
+====
+This chapter is the SDK reference for *writing* a type checking extension.
+For the extensions Groovy already ships with (regex checking, format-string
+checking, null safety, frame conditions, purity, parallel-reduction
+associativity, and monadic comprehensions), see the
+<<built-in-auxiliary-type-checkers,Built-in auxiliary type checkers>> chapter.
+====
+
== Writing a type checking extension
=== Towards a smarter type checker
@@ -114,7 +124,10 @@ use those type checking extension scripts.
==== Parameterized extensions
-Extensions can accept named parameters using Groovy's named-argument style
within the extension string:
+Extensions can accept named parameters using Groovy's named-argument style
within the extension string.
+The example below uses `NullChecker`, one of the
+<<built-in-auxiliary-type-checkers,bundled checkers>>; the same syntax applies
+to any extension that reads the `options` map.
[source,groovy]
------------------------------------------------------
diff --git
a/subprojects/groovy-macro-library/src/spec/doc/_monadic-comprehensions.adoc
b/subprojects/groovy-macro-library/src/spec/doc/_monadic-comprehensions.adoc
index b3d92dba9d..b7b7169128 100644
--- a/subprojects/groovy-macro-library/src/spec/doc/_monadic-comprehensions.adoc
+++ b/subprojects/groovy-macro-library/src/spec/doc/_monadic-comprehensions.adoc
@@ -156,6 +156,24 @@ checking error reporting that some operation cannot be
found on `Object`
`Object`). Adding `extensions = 'groovy.typecheckers.MonadicChecker'` to
the `@CompileStatic`/`@TypeChecked` annotation resolves it.
+=== Lint for hand-written chains: MonadicShapeChecker
+
+If you write `flatMap` / `map` chains by hand (without `DO`) over the same
+participating carriers, the sister `MonadicShapeChecker` extension provides
+a compile-time lint:
+
+[source,groovy]
+----
+@TypeChecked(extensions = 'groovy.typecheckers.MonadicShapeChecker')
+----
+
+It catches the two classic shape mistakes — a `bind`
(i.e. `flatMap`)
+closure that returns a non-carrier value, and a `map` closure that returns
+the same carrier where `flatMap` was meant. A `strict` option tightens
+what is flagged; the default lenient mode reports only high-confidence
+violations. Both checkers are documented from the type-checker side in the
+<<built-in-auxiliary-type-checkers,Built-in auxiliary type checkers>> chapter.
+
[[monadic-when]]
== When to use `DO`
diff --git
a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/FormatMethod.groovy
b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/FormatMethod.groovy
index 0e3b5410a5..74b5053419 100644
---
a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/FormatMethod.groovy
+++
b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/FormatMethod.groovy
@@ -37,6 +37,8 @@ import java.lang.annotation.Target
*
* The {@code FormatStringChecker} ensures that the format string is valid and
the
* remaining arguments are compatible with the embedded format specifiers.
+ *
+ * @since 5.0.0
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
diff --git
a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/FormatStringChecker.groovy
b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/FormatStringChecker.groovy
index e77bfca59c..a08a7e7c01 100644
---
a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/FormatStringChecker.groovy
+++
b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/FormatStringChecker.groovy
@@ -18,7 +18,6 @@
*/
package groovy.typecheckers
-import org.apache.groovy.lang.annotation.Incubating
import org.apache.groovy.typecheckers.CheckingVisitor
import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.ast.ClassNode
@@ -65,8 +64,9 @@ import static org.codehaus.groovy.ast.ClassHelper.makeCached
* https://checkerframework.org/manual/#formatter-checker
*
https://homes.cs.washington.edu/~mernst/pubs/format-string-issta2014-abstract.html
*
https://github.com/typetools/checker-framework/tree/master/checker/src/main/java/org/checkerframework/checker/formatter
+ *
+ * @since 5.0.0
*/
-@Incubating
class FormatStringChecker extends
GroovyTypeCheckingExtensionSupport.TypeCheckingDSL {
private static final ClassNode LOCALE_TYPE = makeCached(Locale)
diff --git
a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/MonadicChecker.groovy
b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/MonadicChecker.groovy
index 50f94ba77a..2fb510c2e6 100644
---
a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/MonadicChecker.groovy
+++
b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/MonadicChecker.groovy
@@ -18,12 +18,13 @@
*/
package groovy.typecheckers
+import org.apache.groovy.lang.annotation.Incubating
+import org.apache.groovy.runtime.MonadicCarrierRegistry
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.GenericsType
import org.codehaus.groovy.ast.MethodNode
import org.codehaus.groovy.ast.expr.ClosureExpression
import org.codehaus.groovy.ast.expr.MethodCallExpression
-import org.apache.groovy.runtime.MonadicCarrierRegistry
import org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport
import org.codehaus.groovy.transform.stc.StaticTypesMarker
@@ -60,7 +61,10 @@ import static org.objectweb.asm.Opcodes.ACC_BRIDGE
* not being a SAM type.
*
* Activate with {@code
@CompileStatic(extensions='groovy.typecheckers.MonadicChecker')}.
+ *
+ * @since 6.0.0
*/
+@Incubating
class MonadicChecker extends
GroovyTypeCheckingExtensionSupport.TypeCheckingDSL {
private static final String DISPATCHER =
'org.apache.groovy.runtime.Comprehensions'
diff --git
a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/RegexChecker.groovy
b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/RegexChecker.groovy
index 04d671a401..69b8896dce 100644
---
a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/RegexChecker.groovy
+++
b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/RegexChecker.groovy
@@ -18,7 +18,6 @@
*/
package groovy.typecheckers
-import org.apache.groovy.lang.annotation.Incubating
import org.apache.groovy.typecheckers.CheckingVisitor
import org.codehaus.groovy.ast.ClassHelper
import org.codehaus.groovy.ast.ClassNode
@@ -99,8 +98,9 @@ import static
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.checkC
* https://checkerframework.org/manual/#regex-checker
* https://homes.cs.washington.edu/~mernst/pubs/regex-types-ftfjp2012.pdf
*
https://github.com/typetools/checker-framework/tree/master/checker/src/main/java/org/checkerframework/checker/regex
+ *
+ * @since 4.0.0
*/
-@Incubating
class RegexChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckingDSL {
private static final ClassNode MATCHER_TYPE = ClassHelper.make(Matcher)
diff --git
a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/package-info.groovy
b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/package-info.groovy
index 78423b9ef1..6e0faa0210 100644
---
a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/package-info.groovy
+++
b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/package-info.groovy
@@ -18,11 +18,40 @@
*/
/**
- * Custom type checking extensions for compile-time validation.
+ * Custom type checking extensions for compile-time validation, activated via
+ * {@code @TypeChecked(extensions = '...')} or {@code
@CompileStatic(extensions = '...')}.
*
* <p>
- * FormatStringChecker, NullChecker, RegexChecker, PurityChecker extend type
checking
- * capabilities for DSLs and domain-specific validation.
+ * Stable checkers:
* </p>
+ * <ul>
+ * <li>{@link groovy.typecheckers.RegexChecker} – validates regex
patterns and
+ * group counts at compile time</li>
+ * <li>{@link groovy.typecheckers.FormatStringChecker} – validates
+ * {@code printf} / {@code String.format} specifiers against argument
types,
+ * paired with the {@link groovy.typecheckers.FormatMethod
@FormatMethod} marker
+ * annotation for user-defined format methods</li>
+ * </ul>
+ * <p>
+ * Incubating in 6.0.0 (semantics may evolve in a subsequent 6.x release):
+ * </p>
+ * <ul>
+ * <li>{@link groovy.typecheckers.NullChecker} – null-safety analysis
using
+ * {@code @Nullable} / {@code @NonNull} / {@code @MonotonicNonNull}
annotations,
+ * with an optional strict flow-sensitive mode</li>
+ * <li>{@link groovy.typecheckers.ModifiesChecker} – verifies method
bodies
+ * comply with their {@link groovy.contracts.Modifies @Modifies} frame
conditions</li>
+ * <li>{@link groovy.typecheckers.PurityChecker} – enforces that
{@code @Pure}
+ * methods have no side effects, with configurable {@code allows}
categories</li>
+ * <li>{@link groovy.typecheckers.CombinerChecker} – verifies that the
combiner
+ * passed to a parallel reduction ({@code sumParallel}, {@code
injectParallel},
+ * {@code Stream.reduce}) carries the associativity contract those
methods
+ * silently require</li>
+ * <li>{@link groovy.typecheckers.MonadicChecker} and
+ * {@link groovy.typecheckers.MonadicShapeChecker} – type-checking
support
+ * for the {@code DO} macro and hand-written monadic chains over the
standard
+ * carriers ({@code Optional}, {@code Stream}, {@code Awaitable}) and
+ * {@code @Monadic}-annotated types</li>
+ * </ul>
*/
package groovy.typecheckers;
diff --git a/subprojects/groovy-typecheckers/src/spec/doc/typecheckers.adoc
b/subprojects/groovy-typecheckers/src/spec/doc/typecheckers.adoc
index 1d82136bd8..a625747562 100644
--- a/subprojects/groovy-typecheckers/src/spec/doc/typecheckers.adoc
+++ b/subprojects/groovy-typecheckers/src/spec/doc/typecheckers.adoc
@@ -23,6 +23,7 @@ ifndef::reldir_typecheckers[]
endif::[]
:icons: font
+[[built-in-auxiliary-type-checkers]]
= Built-in auxiliary type checkers
== Introduction
@@ -31,7 +32,10 @@ Groovy's static nature includes an extensible type-checking
mechanism.
This mechanism allows users to selectively strengthen or weaken type checking
as needed to cater for scenarios where the standard type checking isn't
sufficient.
-In addition to allowing you to write your own custom checkers,
+This chapter catalogues the type-checking extensions that ship with Groovy.
+For the API used to write your own, see the
+<<type-checking-extensions,Type checking extensions>> chapter.
+
Groovy offers a suite of built-in type checkers that provide additional
compile-time verification for specific concerns:
@@ -96,12 +100,38 @@ a| Verifies a combiner passed to a parallel reduction
carries the associativity
list.injectParallel(0, Maths.&add) [.green]#// icon:check[]
@Associative method#
list.injectParallel(0) { a, b -> a - b } [.red]#// icon:times[]
non-associative: undefined result#
----
+
+| <<Checking the DO Macro (Incubating),MonadicChecker>>
+a| Teaches `@CompileStatic`/`@TypeChecked` about the `DO` macro's desugared
output so monadic comprehensions over `Optional`, `Stream`, `Awaitable`, and
`@Monadic` types type-check correctly
+
+[listing,subs="+quotes,macros"]
+----
+@CompileStatic(extensions = 'groovy.typecheckers.MonadicChecker')
+def m = DO { x <- Optional.of(1); yield x + 1 }
+----
+
+| <<Checking Hand-written Monadic Chains (Incubating),MonadicShapeChecker>>
+a| Lint for hand-written `flatMap`/`map` chains over the standard carriers --
catches `bind` returning a non-carrier or `map` returning the same carrier
+
+[listing,subs="+quotes,macros"]
+----
+Optional.of(1).flatMap { x -> x + 1 } [.red]#// icon:times[] bind
returned Integer, not Optional#
+Optional.of(1).map { x -> Optional.of(x) } [.red]#// icon:times[] map
returned Optional, use flatMap#
+----
|===
These checkers work with annotations from multiple libraries (JSpecify,
JetBrains,
Checker Framework, and others) -- matching by simple name so you can use
whichever
annotation library your project already depends on.
+[NOTE]
+====
+`RegexChecker` (since Groovy 4.0.0) and `FormatStringChecker` (since Groovy
5.0.0)
+are stable. The remaining checkers are new in Groovy 6.0.0 and remain
incubating;
+their options and semantics may evolve in a subsequent 6.x release based on
+usage feedback.
+====
+
=== Why these checkers matter
In a world which seems to be having an ever-increasing focus on AI,
@@ -183,6 +213,8 @@ as just one example.
== Checking Regular Expressions
+_Stable since Groovy 4.0.0._
+
A regular expression (regex) defines a search pattern for text. The pattern
can be as simple as a single character, a fixed string, or a complex expression
containing special characters describing a pattern.
The JDK has special regex classes and Groovy adds some special syntactic sugar
and functionality
on top of the JDK classes.
@@ -341,6 +373,8 @@ Over-and-above these examples, detected errors include:
== Checking Format Strings
+_Stable since Groovy 5.0.0._
+
The `format` methods in `java.util.Formatter`, and other similar methods,
support formatted printing in the style of C's `printf` method with a
format string and zero or more arguments.
@@ -1391,3 +1425,51 @@ This is a safety net, not a guarantee:
A parallel combiner should normally also be side-effect-free; pairing this
checker with `<<Checking @Pure Purity (Incubating),PurityChecker>>` (which
already treats `@Memoized` methods as purity-obligated) gives both guarantees.
+
+== Checking the DO Macro (Incubating)
+
+_New in Groovy 6.0.0._
+
+The `MonadicChecker` type-checking extension supports the `DO` macro for
+monadic comprehensions, teaching `@CompileStatic` / `@TypeChecked` how to
+look through the macro's desugared output (a runtime-dispatched
+`Comprehensions.bind` / `Comprehensions.map` chain). Without it, generator
+bound names erase to `Object` and the body fails to type-check.
+
+[source,groovy]
+----
+@CompileStatic(extensions = 'groovy.typecheckers.MonadicChecker')
+def addOne = DO { x <- Optional.of(1); yield x + 1 }
+----
+
+The checker also rejects a non-participating carrier with a compile error
+naming the missing shape (the carrier must be in the registry allow-list,
+expose structural `flatMap` / `map`, or be annotated with `@Monadic`).
+
+The macro itself, its desugaring strategy, the participating-carrier rules,
+and the `@Monadic` annotation are documented in the
+<<monadic-comprehensions,Monadic Comprehensions>> chapter. This section
+covers only the type-checking surface.
+
+== Checking Hand-written Monadic Chains (Incubating)
+
+_New in Groovy 6.0.0._
+
+The `MonadicShapeChecker` is a lint for monadic chains you write by hand
+(without the `DO` macro) over the standard carriers (`Optional`, `Stream`,
+`Awaitable`). It catches the two classic shape mistakes:
+
+* `bind` (a.k.a. `flatMap`) closure returning a non-carrier value
+* `map` closure returning the same carrier, where `flatMap` was meant
+
+Activate via:
+
+[source,groovy]
+----
+@TypeChecked(extensions = 'groovy.typecheckers.MonadicShapeChecker')
+----
+
+The checker has a `strict` option that tightens what it flags; in lenient
+(default) mode only high-confidence violations are reported. The sister
+`MonadicChecker` (above) covers the `DO`-macro path; this checker covers
+the hand-written path.