Hi Paul,

(AI helped formatting)
I read GEP-28 v2 in some more detail. The new version is much clearer, especially the context-type table. I still have a number of questions around the semantics and, in particular, around the difference between STC and dynamic Groovy.

**1. Context type / basic resolution rule**

I think it would be useful to make one basic rule explicit: the context type of a shorthand should be determinable independently of resolving that shorthand itself. In other words, we should not need to resolve a shorthand in order to determine the context in which that same shorthand is resolved, directly or indirectly.

The Dart model seems useful as a comparison here. The initial shorthand is resolved from an already available context type, and subsequent operations are then checked from there. This makes, for example,

```groovy
X x = .RED.next(.BAR)
```

conceptually straightforward in STC: `X` gives the context for the first `.RED`, which gives us the receiver type, which lets us resolve `next`, which then gives us the context for `.BAR`.

The DCM article on Dart's dot shorthand https://dcm.dev/blog/2025/11/17/deeper-look-darts-dot-shorthand-hidden-complexity/ points out a related issue from a readability perspective: nested shorthands can become difficult to understand even when the compiler can resolve them.

**2. Method arguments and overload resolution**

For

```groovy
foo(.RED)
```

I think the STC approach is reasonable: candidate parameter types can provide candidate context types.

I also assume that if we have

```groovy
foo(A)
foo(B)
```

and both `A` and `B` provide a `RED`, this is an ambiguity and therefore a compilation error. The current GEP seems to say that.

There is another complication here, though. Groovy's notion of compatibility is not limited to simple subtype relationships. Numeric widening/conversion, boxing/unboxing and other Groovy conversions mean that an expression can be applicable to multiple parameter types even when those types are not in a simple subtype relation. The rules are also not exactly the same between STC and dynamic method selection.

So what exactly does it mean for `.RED` to "resolve under a candidate context type"? Do we only ask whether the member exists on that type, or do Groovy's existing conversion rules also participate? If several overloads provide a valid context, does existing method-selection ranking still distinguish them, or does the shorthand make them equally applicable?

This seems important because the GEP makes shorthand resolution part of overload selection.

For dynamic Groovy the problem is larger. There is no parameter type until method selection has happened. To make

```groovy
foo(.RED)
```

work dynamically, `.RED` would have to participate in method selection itself. We would somehow need to carry an unresolved contextual value through the dynamic call machinery. That is much more than a syntax transformation.

The fact that Dart has dot shorthand is not directly comparable here: Dart does not have Groovy's overload-resolution situation.

**3. Operators**

`c == .RED` seems more manageable. If `c` has a declared type, that type can provide the context and we can then lower the shorthand to an ordinary qualified reference. I understand that this is now also the intended behaviour in dynamic Groovy.

For STC, however, I think it is still worth describing the rule explicitly because Groovy's `==` ultimately involves operator/method semantics.

For dynamic Groovy there is a different question. `==` already has a more complicated runtime path than ordinary static member access. Making this work is probably possible because the shorthand can be resolved from the statically known type before runtime dispatch, but I think it is worth making the distinction from the `foo(.RED)` case explicit.

I am much less clear about `in`. I don't immediately see the existing Groovy expression for which

```groovy
x in .RED
```

would be the shorthand equivalent. What exactly is the context type for the RHS here? Is there a concrete use case that motivates supporting this?

Dart has a separate rule for `==`/`!=`, rather than treating them as a completely generic application of contextual typing, so I wonder whether the operator cases in GEP-28 should also be specified individually.

**4. Static fields**

For

```groovy
Duration d = .ZERO
```

the context is clearly `Duration`.

For

```groovy
def d = .ZERO
```

I assume this does not work because there is no independently known context type. I think this should be explicit. The fact that a local variable may later acquire a flow type should not provide the type needed to resolve its own initializer.

Likewise, for

```groovy
static final A X = .RED
```

the context is simply `A`. I don't see why the enclosing class should have any role in determining the shorthand.

For dynamic Groovy this seems fine as long as the context is available from syntax and we resolve the shorthand at compile time to an ordinary qualified access.

**5. Value compared with static imports**

I am also not completely convinced by the namespace-pollution argument for static imports.

We can write:

```groovy
import static Color.RED as MY_RED
```

so a static import can be renamed and made explicit. `.RED` cannot be renamed.

The real advantages of `.RED` seem to be that the qualification stays local, and that in some cases the type itself does not need to be imported because it is only present in the surrounding context. The same-package case seems particularly relevant here.

That may well be enough value. I am just not sure how important those cases are compared with the additional semantics.

**6. Stage 3 and chaining**

I find Stage 3 substantially more ambitious than the basic enum/member case.

For example:

```groovy
Duration d = .of(3)
```

is understandable if `Duration` is already known from the context.

But this adds another method-resolution step, and the GEP itself points out the circularity in argument positions. I think we should keep this separate from the basic feature until the simpler cases have been established.

Similarly,

```groovy
String s = .RED.next()
```

should, to me, simply fail in STC if the context is `String`. There is no reason to infer the type of `.RED` from `next()`.

A more interesting case is:

```groovy
X x = .RED.next(.BAR)
```

Here the compiler can resolve things in a straightforward order as long as `X` is the independently known context: first `X.RED`, then `next`, then `.BAR` from the selected parameter type.

The problem is more readability than compiler capability. With more nested shorthands, the reader has to reconstruct which type each dot refers to. The DCM article on Dart's dot shorthand is interesting in this respect because Dart tooling already has a lint for nested shorthands for exactly this kind of readability problem.

This seems to be a good candidate for "Groovy Puzzlers" type of problems. The compiler may be able to resolve the code quite unambiguously while a human reader has to perform a small type-inference exercise to understand it.

**7. Operators more generally**

This is also why I would be careful with the Dart comparison. Dart operators such as `+` are method-based as well, but the method-selection model is not Groovy's.

For example, something like

```groovy
X x = .FOO + .BAR
```

could involve resolving the receiver shorthand, selecting `plus(...)`, and then resolving the second shorthand from its parameter type. In Groovy, overload selection can make the type of the second shorthand relevant to selecting the method itself.

That is manageable for STC if the rules are well-defined, but it is a different problem from the Dart case.

**8. Static versus dynamic Groovy**

This is probably my main question about the proposal.

There are simple cases where the shorthand has essentially the same semantics in both modes because the context is directly available from syntax:

```groovy
Color c = .RED
```

There are other cases, most notably method arguments, where the useful context only exists because STC has performed method resolution.

The GEP acknowledges this split. My question is whether we really want to introduce general language syntax whose applicability depends so strongly on whether static compilation is being used.

I don't mean that every feature has to behave identically in dynamic and static Groovy. But for expression syntax I think we should be particularly careful here. The simple cases where the context is independently known seem much easier to justify than cases where the shorthand becomes part of type inference and overload resolution.

So overall, I think the next useful step would be to make the resolution rules and these boundaries very explicit in the GEP, in particular:

* how context types are established;
* how Groovy's existing conversion and overload-selection rules interact with shorthand resolution;
* which operator cases have special rules;
* where resolution is deliberately STC-only;
* and whether there is a general acyclicity rule for contextual resolution.

Cheers,
Jochen

On 8/4/26 05:40, Paul King wrote:
Hi Matt,


Thanks for your thoughtful reply. Sorry for the delay - I've been busy working on releases.


On the generative tooling discussion. Yes there has been some AI assistance on the GEP. We "more or less" follow the ASF guidelines[1]. "More or less" because there is a lot of debate at the moment:


  * Does it apply to docs and code?
  * Should we use "Generated-by" or "Assisted-by" to properly reflect
    the guideline which indicates the committer takes responsibility for
    the content.
  * If the intended purpose is to potentially produce a "machine
    parsable Tooling-Provenance file", why is AI that different to which
    compiler I used, what editor did I code in, what checkstyle rules
    did I use, and so forth. TBH, knowing which tools/models were used
    has some benefit, but some folks see all the commit messages as
    being little more than free advertising to the LLM companies.


These comments aren't to indicate that we should take guidelines lightly, just that we have some time while things settle down. Most people seem quite good at telling when content was AI-generated/assisted.


I think the most important thing for us is knowing that heavy AI assistance should lead to heavy human review. And we should apply a risk-based approach. AI assist on docs/tests is less risky than prod code, and prod code itself has different risk profiles, e.g. a simple DGM method addition vs multithreading, some of our metaprogramming code, some of our type checking and bytecode gen code.


On to your comments on the GEP itself, you have a pretty good understanding. You pointed out some unclear bits, so I created a new version which improves some of those points and added some inline clarifications below.


You have the general idea. Java programmers have the mental model that "I already told the compiler the type of my enum in the switch clause, so why should I have to tell it again in the case label".


The PR extends that mental model to other places where we have already told the compiler about the type (or where it could infer it), so we avoid repeating that information. The leading dot also caters to dynamic Groovy, which would otherwise interpret some of these cases as properties.


Cheers, Paul.


[1] https://www.apache.org/legal/generative-tooling.html <https:// www.apache.org/legal/generative-tooling.html>


On Thu, Jul 30, 2026 at 3:17 AM Matt M [email protected] <mailto:[email protected]> wrote:


    Hello,

    I am not sure of the GEP process, however, I figured I could chime-
    in with some, hopefully, helpful feedback.

    I agree with the sentiment thus far: adding the dot shorthand is a
    relatively easy improvement to the language ergonomics in some very
    specific narrow cases. However, the syntax can/does seem a bit
    eccentric or uncomfortable. Though, I can imagine myself getting
    used to it over time as I am exposed to it more and more. There's a
    certain beauty in the symmetry of it versus using a separate
    designator or signifier instead (e.g. |:RED|).

    That said, I thought I had read through the document fairly well and
    understood what it was getting at. However, that said, I am not
    intimately familiar with the other languages mentioned in the GEP
    (i.e. Dart and Swift primarily) such that I have a firm grasp of/on
    the semantics being proposed. Therefore, I wanted to perhaps re-
    state the GEP with my understanding of the GEP as a way to clarify
    and perhaps provide some feedback.

    —

    One of the fundamental benefits of this feature would be avoiding
    the need to import enum/enum-like types (static or otherwise) if/
    when they can be inferred from their localized usage. For example:

    |LogLevel.groovy public enum LogLevel { … } Loggers.groovy public
    void log_print(String message, LogLevel level) { … } App.groovy
    log_print("Hello world", .INFO); |

    Such usage of the feature, in the context of the method call, is
    only possible because of the method signature allowing the compiler
    to know about and clearly infer the type being used and thus allows
    the user to avoid importing the type explicitly and qualifying the
    usage of the value with the aforementioned type.


Spot on. I added a clarification in the GEP on this.


    Similarly the feature only works in contexts where the type is
    obvious and clear to the compiler (e.g. explicit declaration in/on
    LHS expressions): |Color my_color = .RED|. The benefit here being
    less about imports and more about avoiding/reducing redundant code
    duplication/over specification. Coupled with compile time type-safe
    literals over the string coercion evaluation at runtime.

    Assuming this understanding is correct, this makes sense and I can
    see the value in each case. Albeit I am less inclined to see a /lot/
    of value in the second, but meh neither here or there around being
    more expressive/less verbose being an a bad thing in and of itself
    (i.e. I would rather have the option to be less verbose than always
    forced to be verbose).


Sure, but Groovy coerces String values to enum values too, so it isn't just about a better score in code golf, but actual errors that would occur at runtime like: Color myColor = 'READ' could be caught at compile time.


    —

    Any who, I wasn't /super/ clear on all of this in the examples/cases
    of the GEP, so it might be worth elaborating and expanding a bit
    more. Since in my first readings, I wasn't convinced and questioned
    how this avoids the required import anyways to even use the enum/
    enum-like type.

    I do see a lot of value/"nice'ness" with/for being able to use this
    feature with the static factory methods (e.g. |.of(...)|, etc.).
    That would be really nice and just add that extra bit of nice syntax
    sugar.

    I am still a bit hazy on how/where this shorthand would apply
    exactly in all cases, based on reading the GEP. I am uncertain if it
    would work in/with the |==| operator. For example, would it work for
    |if (this.my_color == .RED) { ... }| cases or not? Would it depend
    if |my_color| was defined as something like |public Color my_color|
    or |public def my_color|? Again, I am not /super/ clear on this.

    The GEP seems to have a bit of contradictory notes around this. The
    |==| example/case is mentioned a few times as being partially
    supported early on and then it is mentioned as maybe being supported
    in the deliverables section but then says its an excluded feature?
    So that's why I am unsure.


Yeah, that wasn't clear and I have improved it. Let me know if it still seems too complex.


    Finally, more of a meta note/question, was this GEP written by AI or
    assisted with AI? There seem to quite a few places that read as if
    it were written by AI. I am not sure what the official policy or
    line is on the whole usage of AI is in Groovy / GEP's, but if AI is
    involved in anyway: it would be good etiquette to explicitly,
    clearly, and boldly mention that the GEP was AI written and/or its
    contents' creation was assisted with AI tools. This way more
    appropriate and applicable scrutiny may be applied, especially for
    contradictions, inaccuracies, and/or hallucinations.

    Any who, I hope this has been helpful. Looks really cool.

    Thanks, Matt

    ------ Original Message ------ From "Paul King" [email protected]
    <mailto:[email protected]> To "Groovy_Developers"
    [email protected] <mailto:[email protected]> Date 7/29/2026
    12:06:10 AM Subject Re: Some draft GEPs: GEP-28 and GEP-29

        Yeah, it will be one of those subjective things.

        Some folks think it is ugly to have to type "Color.RED" when
        Java lets

        you just type "RED". We have that down to just one case now (or you

        can use an import) but for dynamic Groovy we probably can't do
        better.

        The GEP proposal would give another option. And for some folks
        all the

        extra places they can shorten using a consistent pattern might seem

        like a beautiful thing to them, but yes I still fall into the
        camp of

        "it looks a little awkward", but I am still keen to explore the

        possibilities.

        Cheers, Paul.

        On Wed, Jul 29, 2026 at 6:25 AM MG [email protected]
        <mailto:[email protected]> wrote:

            Just a quick feedback for GEP-28: I saw that "Sigil
            variants" (@RED,

            :RED, etc) are already stated as rejected in the GEP, but
            what about

            something similar to Groovy's "it" in closures (maybe as an
            option, to

            also be able to break ambiguities without having to fall
            back to the

            current fully qualified notation) ?

            To me the floating-in-free-air-dot-prefix notation ( .RED )
            looks like

            an error / ugly...

            Cheers,

            mg

            Am 28.07.2026 um 08:30 schrieb Paul King:

                Hi folks,

                I created two more draft GEPs tentatively targeting
                Groovy 7:

                  * GEP-28 is about supporting DOT-notation that is
                    supported by Dart and Swift.

                [I like the brevity it gives but it also takes some
                getting used to

                and it complicates things if Java ever develops a
                different compact

                syntax.]

                  * GEP-29 is about supporting deeper null checking.

                [This is really about whether we want to fully support
                JSpecify which

                seems to be gaining momentum. NullChecker in Groovy 6
                already offers

                partial support.]

                I don't really have the cycles to do major reviews of
                the GEPs yet,

                and I expect both will go through several iterations
                when we do. For

                both we should ask ourselves whether we need the feature
                and weigh up

                each's value against the increased complexity. But I
                wanted to capture

                the ideas anyway, since I think they are worth thinking
                about in due

                course. We should be comfortable with rejecting GEPs if
                the ideas

                don't belong or belong somewhere else.

                To be honest, we already have a backlog of potential
                Groovy 7 features

                that will keep us busy for a while.

                But if other folks want to jump ahead and think about
                them, feel free.


Reply via email to