Hi Jochen,
as a precursor: Besides it being really helpful to us, I see this
(seemingly innocuous) NULL_TO macro spike as a general check how well
existing Groovy macro functionality works in practice.
To me, the goal would be that adding a Groovy macro to a project should
be similar and nearly as simple as implementing a few methods in a
helper class, and far below the level of effort and required
understanding of build phases and inner workings of the Groovy compiler
to implement a (local) transform for the same functionality.
Your post also seems to cut off abpruptly... (?)
Comments inline.
Cheers,
mg
Am 25.09.2026 um 04:34 schrieb Jochen Theodorou via dev:
On 9/24/26 23:07, MG wrote:
1. I checked today, and INFERRED_TYPE unfortunately does not seem to be
filled at this point, in fact all the metaDataMap|s I checked on
different Expression|s that appear in my test code were null...
yes, I mentioned that may be the case. The macros run before this
information is added
Yea, I was confirming your suspicion (could have said "does /in
fact/ not //seem to be filled" to make that clearer).
Btw, would supporting to be able to choose in what compile phase a macro
gets executed make sense in your eyes ?
<zip>
1. To get around this, I introduced a
simple NullToMacroWrapper helper class which stores the
result, with an overridden asBoolean method that checks its
stored value against null, i.e.:
1. wrapInNullToMacroWrapper(exp0) ?: exp1
2. ...but that helper class (which currently is not generic, so
it can resuse a thread local singleton) is exactly what
requires the cast (as I saw when rechecking the ternary
operator NULL_TO version).
I don't think macros are a good fit for this
Even if one does not expect the RHS to be cast to the LHS type (see
below) ... ?-)
4. So as far as I can see either:
1. There is a way to store the exp0 evaluation result inside the
macro, then the rest is trivial.
temporary variables maybe, but frankly that does not look like it will
work well as the right side of an assignment/declaration
Are you referring to the local transform approach here ?
Otherwise: How would I introduce a local variable inside a macro
(unfortunately Groovy does not accept something like: final x = (final
h=exp0(); h !== null ? h : exp1()) - which, on second thought, would not
work anyway, since the local variable h would have type Object, so an
explicit cast would again be needed when using NULL_TO under static
compilation...).
(As a general remark: Besides being more typed, I have always felt that
it would be nice for more things in Groovy to also be usable as an
expression).
2. There is a way to get the right type to do the cast of the Elvis
operator based result.
the "right" type is the left side type I assume, which you have no
access to in the macro
No, instead it should be WideningCategories.lowestUpperBound of the
NULL_TO arguments (which is what the ternary operator based
implementation seems to do), and compilation should fail if the RHS is
not is not assignment compatible with the LHS.
3. Turning NullToMacroWrapper into a generic class fixes the cast
problem - but this approach would create (short lived, i.e. gen
zero) NullToMacroWrapper instances on the heap...
I thought that also created a casting problem?
I had not tried the generic variety when I wrote that, since generic
types are so ethereal in Groovy/Java, static compilation of generics in
Groovy can sometimes lead to unexpected/incorrect build errors, and
creating new objects (even if they are gen zero on the heap) did not
seem like the best way forward.
I did try it just now, and alas it fails with "... cannot be cast to
type T"/"cannot cast type T to ...." (where "T" is the literal generics
argument name in the GenericNullToMacroWrapper class) build errors
wherever the NULL_TO macro is used - I assume the type T is bound to its
actual type in a later compilation phase ? Or maybe not, since the
"unexpected/incorrect build errors" I mentioned above look very similar,
but outside of macros one can sometimes get around that by "casting to
T" ( which should not be a thing afaiaao, since type T does not exist
during runtime, but it satisfies the compiler ;-) ).
macros are basically specialized local transforms. You have a slightly
different interface and you can choose the compilation phase... but
most importantly you have more context to work with and can place them
at other source positions.
So yes, they
they... ?-)