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

Reply via email to