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

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

commit f579edf96de43c196ae6c4f9c35a535503e33dd2
Author: Paul King <[email protected]>
AuthorDate: Wed May 20 09:14:30 2026 +1000

    add MonadicShapeChecker
---
 site/src/site/wiki/GEP-23.adoc | 88 +++++++++++++++++++++++++++++++++++-------
 1 file changed, 75 insertions(+), 13 deletions(-)

diff --git a/site/src/site/wiki/GEP-23.adoc b/site/src/site/wiki/GEP-23.adoc
index 9717627..b69ef13 100644
--- a/site/src/site/wiki/GEP-23.adoc
+++ b/site/src/site/wiki/GEP-23.adoc
@@ -7,13 +7,13 @@
 [horizontal,options="compact"]
 *Number*:: GEP-23
 *Title*:: Monadic comprehensions
-*Version*:: 1
+*Version*:: 2
 *Type*:: Feature
 *Status*:: Final
 *Comment*:: Delivered in Groovy 6.0 via the `DO` macro; the feature is 
incubating and subject to change
 *Leader*:: (to be assigned)
 *Created*:: 2026-05-19
-*Last modification*:: 2026-05-19
+*Last modification*:: 2026-05-20
 ****
 
 == Abstract: Monadic comprehensions
@@ -26,15 +26,18 @@ Haskell-style do-notation ergonomics for any type with 
monadic shape —
 `DataflowVariable`, common Functional Java types, and user-defined carriers
 that opt in.
 
-Three artefacts are introduced:
+The following artefacts are introduced:
 
 * the `DO` macro in the macro library, performing a purely syntactic tree
 rewrite at compile time;
 * the `@groovy.transform.Monadic` annotation, by which a user type opts in
 and may declare non-conventional bind/map method names;
-* a type-checking extension that enforces the monadic shape under
-`@CompileStatic`/`@TypeChecked` and supplies the static types that flow
-through the rewritten chain.
+* `groovy.typecheckers.MonadicChecker` — a type-checking extension that
+enforces the monadic shape under `@CompileStatic`/`@TypeChecked` and supplies
+the static types that flow through the rewritten chain;
+* `groovy.typecheckers.MonadicShapeChecker` — a sibling type-checking
+extension that lints hand-written `flatMap`/`map` chains over the same
+carrier set, independent of `DO`.
 
 The proposal is deliberately narrow. It does not introduce higher-kinded
 types, a `Functor`/`Applicative`/`Monad` interface hierarchy, monad
@@ -204,6 +207,8 @@ Under dynamic Groovy, the rewritten code is ordinary method 
dispatch; a
 value that does not respond to the resolved bind method fails at runtime
 with the usual `MissingMethodException`.
 
+==== `DO` under static compilation: `MonadicChecker`
+
 Under `@CompileStatic`/`@TypeChecked`, the `groovy.typecheckers.MonadicChecker`
 type-checking extension (GEP-8) is activated via the `extensions` member:
 
@@ -220,12 +225,54 @@ shape;
 * types each generator's bound name as the carrier's element type, so the
 body type-checks;
 * restores the comprehension's result type, so chained and nested use
-type-checks rather than degrading to `Object`.
+type-checks rather than degrading to `Object`;
+* for *trusted* carriers (allow-list or `@Monadic`), enforces the
+closure-return contract that the dispatcher's erased
+`(Object, Closure):Object` signature hides from STC: a `bind` closure must
+yield the same carrier (catching a bare-value body and a cross-carrier
+body, including in nested `DO`), and a hand-written `Comprehensions.map`
+closure must not yield the same carrier (catching the `M<M<T>>` foot-gun).
+Structural-only carriers are not asserted against, matching the permissive
+treatment of participation.
 
 Dependent generators (a later generator whose source expression uses an
 earlier bound name) and nested comprehensions are supported under static
 compilation.
 
+==== Native chains: `MonadicShapeChecker`
+
+A sibling extension `groovy.typecheckers.MonadicShapeChecker` lints
+hand-written `flatMap`/`map`/`thenCompose`/`thenApply` chains over the same
+carrier set. It is independent of `DO`; use it on codebases that mix or
+favour native chains.
+
+[source,groovy]
+----
+@CompileStatic(extensions = 'groovy.typecheckers.MonadicShapeChecker')
+----
+
+It flags three high-confidence problems:
+
+* `bind` returning a non-carrier — e.g. `Optional.flatMap { it + 1 }`,
+where Groovy's SAM coercion can let an Integer-returning closure slip past
+STC;
+* `bind` returning a different carrier — e.g.
+`Stream.flatMap { Optional.of(it) }`;
+* `map` returning the same carrier — e.g.
+`Optional.map { Optional.of(it) }`, the classic `M<M<T>>` foot-gun.
+
+Carriers and method-name conventions are read from the same registry as
+`MonadicChecker`; `@Monadic`-annotated types also participate. Calls routed
+through the bind/map dispatcher are skipped (that is `MonadicChecker`'s
+domain). A strict mode additionally flags chains whose function-return type
+cannot be statically resolved.
+
+The two extensions are complementary but independent: `MonadicChecker`
+repairs erasure on the `DO`-macro dispatcher and asserts the dispatcher's
+closure-return shape; `MonadicShapeChecker` asserts the same shape on
+native chains the dispatcher never sees. Code that mixes `DO` with native
+chains may opt into both.
+
 === Scoping, `return`, `break`, `continue`
 
 The desugared form turns the body into a chain of closure bodies. The
@@ -366,11 +413,13 @@ and structural matching alone is too narrow.
 | 6.0 | 2026
 | Initial release (incubating). The `DO` macro; the bind/map dispatcher and
 carrier registry in the core runtime; `@groovy.transform.Monadic`; the
-`groovy.typecheckers.MonadicChecker` type-checking extension; the standard
-allow-list (`Optional`, `Stream`, `CompletableFuture`, `CompletionStage`,
-`Awaitable`, `DataflowVariable`) and the by-name recognition of common
-Functional Java carriers; general single-abstract-method coercion of the
-generator closure.
+`groovy.typecheckers.MonadicChecker` type-checking extension (participation,
+inference, and closure-return shape under `@CompileStatic`); the sibling
+`groovy.typecheckers.MonadicShapeChecker` extension for native
+`flatMap`/`map` chains; the standard allow-list (`Optional`, `Stream`,
+`CompletableFuture`, `CompletionStage`, `Awaitable`, `DataflowVariable`)
+and the by-name recognition of common Functional Java carriers; general
+single-abstract-method coercion of the generator closure.
 
 | 7.0 | TBD
 | Candidate: user-configurable carrier registration, symmetric across the
@@ -440,7 +489,11 @@ the type checker.
 * `groovy.transform.Monadic` — the opt-in annotation; a pure marker with
 optional `bind`/`map` string attributes and no AST transformation.
 * `groovy.typecheckers.MonadicChecker` — the type-checking extension that
-enforces the monadic shape and supplies static types under `@CompileStatic`.
+enforces the receiver and closure-return monadic shape and supplies static
+types under `@CompileStatic`.
+* `groovy.typecheckers.MonadicShapeChecker` — sibling type-checking
+extension that lints hand-written native `flatMap`/`map` chains against the
+same carrier set; independent of `DO`.
 
 Public API:
 
@@ -448,6 +501,8 @@ Public API:
 * the `DO` macro, in the `groovy-macro-library` module (since 6.0.0)
 * `groovy.typecheckers.MonadicChecker`, in the `groovy-typecheckers`
 module (since 6.0.0)
+* `groovy.typecheckers.MonadicShapeChecker`, in the `groovy-typecheckers`
+module (since 6.0.0)
 
 === Representative JIRA issues
 
@@ -455,6 +510,13 @@ module (since 6.0.0)
 
 == Update history
 
+2 (2026-05-20) Adds closure-return shape enforcement to `MonadicChecker`
+(rejects bare-value and cross-carrier `DO` bodies for trusted carriers via
+the dispatcher); introduces the sibling
+`groovy.typecheckers.MonadicShapeChecker` extension for native
+`flatMap`/`map`/`thenCompose`/`thenApply` chains across the same carrier
+set.
+
 1 (2026-05-19) Initial draft. Specifies the `DO` macro, the `@Monadic`
 annotation, the type-checking extension, the standard carrier allow-list
 (including by-name recognition of Functional Java), the runtime model and

Reply via email to