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.