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

paulk-asert pushed a commit to branch groovy12039
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit a604e3e7abe4a970778f42439ed88bfe589ad4f5
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      |  4 +-
 .../groovy/groovy/typecheckers/RegexChecker.groovy |  4 +-
 .../groovy/groovy/typecheckers/package-info.groovy | 35 ++++++++-
 .../src/spec/doc/typecheckers.adoc                 | 84 +++++++++++++++++++++-
 8 files changed, 156 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 &mdash; a `bind` 
(i.e.&nbsp;`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..94437327de 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
 
@@ -61,6 +62,7 @@ import static org.objectweb.asm.Opcodes.ACC_BRIDGE
  *
  * Activate with {@code 
@CompileStatic(extensions='groovy.typecheckers.MonadicChecker')}.
  */
+@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..e8a38d84fc 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} &ndash; validates regex 
patterns and
+ *       group counts at compile time</li>
+ *   <li>{@link groovy.typecheckers.FormatStringChecker} &ndash; 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} &ndash; null-safety analysis 
using
+ *       {@code @Nullable} / {@code @NonNull} / {@code @MonotonicNonNull} 
annotations,
+ *       with an optional strict flow-sensitive mode</li>
+ *   <li>{@link groovy.typecheckers.ModifiesChecker} &ndash; verifies method 
bodies
+ *       comply with their {@code groovy.contracts.@Modifies} frame 
conditions</li>
+ *   <li>{@link groovy.typecheckers.PurityChecker} &ndash; enforces that 
{@code @Pure}
+ *       methods have no side effects, with configurable {@code allows} 
categories</li>
+ *   <li>{@link groovy.typecheckers.CombinerChecker} &ndash; 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} &ndash; 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.

Reply via email to