Hi Jochen,
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...
2. To be clear, using dynamic compilation everything works find, only
static compilation does not - but of course larger parts of our
project are now statically compiled, due to the known performance
issues.
3. Anyways, I actually have 2 NULL_TO macro implementations:
1. One based on the ternary operator, which I had discarded, and
which I just checked again and saw that it behaves the way you
describe also under static compilation, i.e. it does not require
a cast and works under @CompileStatic as expected.
1. The reason I discarded it, was that I did not know how to
avoid the double evaluation inside a macro when doing a
ternary null check, i.e.:
1. exp0 !== null ? exp0 : exp1 // exp0 gets evaluated 2x,
unless there is a way to store its result inside a
macro... (?)
2. And one based on the Elvis operator - this one of course looks
no good at first glance, since it uses Groovy truth, which is
exactly what we do not want.
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).
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.
2. There is a way to get the right type to do the cast of the Elvis
operator based result.
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...
4. Or one can ditch the macro approach and go down the local
transform route (have never done this, so no idea how much
effort this is and what small, unexpected snags await there ;-) ).
Cheers,
mg
Am 24.09.2026 um 10:52 schrieb Jochen Theodorou via dev:
Hi,
I think you are having a detail problem are your description is a bit
too generic for that.
```groovy
import org.codehaus.groovy.macro.runtime.Macro
import org.codehaus.groovy.macro.runtime.MacroContext
import org.codehaus.groovy.ast.expr.Expression
import org.codehaus.groovy.ast.expr.MethodCallExpression
import org.codehaus.groovy.ast.expr.ArgumentListExpression
import org.codehaus.groovy.ast.stmt.BlockStatement
import static org.codehaus.groovy.macro.runtime.MacroGroovyMethods.macro
class GuardMacros {
@Macro
static Expression NOT_NULL(MacroContext ctx, MethodCallExpression
call) {
List<Expression> expressions = argList.getExpressions()
Expression finalExp = expressions.last()
List<Expression> checks = expressions.subList(0,
Expression resultAST = finalExp
for (int i = checks.size() - 1; i >= 0; i--) {
Expression currentCheck = checks[i]
resultAST = macro {
$v{currentCheck} != null ? $v{resultAST} : null
}
}
return resultAST
}
}
```
If you are using that kind of structure, then the possible problem is
that the ternary expression historically had some problems with using
the right type. But normally
On 9/23/26 22:45, MG wrote:
[...]
5. Question: How do I get the actual type of method parameters as given
in the source code (hope they have not been erased at this point,
since then I would be back to square one...) ?
1. e.g. (nonsensical, just to show the problem):
void foo(BigDecimal x0, BigDecimal x1) {
final BigDecimal x = NULL_TO(x0, x1) // still fails with
"cannot assign Object to BigDecimal", since the Expression#type
of the x0 and x1 Expression|s is a class node of type
java.lang.Object...
}
should then work. you can test it without the macro by actually
writing it as ternary expression in the source code. Just once. To
verify.
6. Bonus question: What one evidently would want is not the type of one
of the Expression|s passed to NULL_TO, but instead the common
supertype of all the Expression|s - is there any accessible existing
functionality to get that ?-)
Eric already mentioned WideningCategories.lowestUpperBound. But I am
not sure if INFERRED_TYPE is available at the time the macro is executed.
If you really want to enforce the cast, I would consider a local
transform instead of a macro. Then you can work on the declaration
itself and use the left type to enforce a cast - maybe.
bye Jochen