Reputation: 3487
I had code like this working with Haxe 3:
macro public static function get(key:String)
{
return Context.makeExpr(Context.definedValue(key), Context.currentPos());
}
However, after migrating to Haxe 4 this fails to compile with error:
Uncaught exception macro-in-macro
How should I go about migrating this function to Haxe 4? Is there a better way to access build flags in order to avoid this issue?
Upvotes: 0
Views: 306
Reputation: 3487
We ended up removing this whole get
method and switching occurrences to use Compiler.getDefine() instead, which is supported both by Haxe 3 and 4.
I believe the problem we were facing was related with the fact that this static macro get
was being called from our test runner script, so that probably was the place where a macro was calling another macro. Still, I tried to put the solution suggested by Jeff Ward in place but kept getting the same result.
Upvotes: 0
Reputation: 19026
As @Gama11 alluded to, there's not actually a problem with your macro function, there's a problem with where you're calling it from. (Haxe 4 may have gotten more strict with these checks.)
If you have:
Main.hx
class Main
{
public static function main()
{
// Can call get from here:
var cvar = MacroUtil.get('cvar');
MacroUtil.some_macro_function();
trace('Hello world! cvar=${ cvar }');
}
}
MacroUtil.hx
import haxe.macro.Context;
import haxe.macro.Expr;
class MacroUtil
{
macro public static function get(key:String):Expr
{
return Context.makeExpr(Context.definedValue(key), Context.currentPos());
}
macro public static function some_macro_function()
{
// Cannot call get from here:
var cvar:Expr = get('cvar');
trace('will trace at compile time, and cvar is ${ cvar }');
return macro trace('will trace at runtime');
}
}
And execute it with: haxe -x Main -D cvar=abc
That will generate the error you're experiencing. It's because in some_macro_function
, you're already in the macro context, so you can't call the macro function get
from there.
There are a couple ways of dealing with this.
You can use #if macro
/ if !macro
to detect the macro context and adjust accordingly. So as silly as this looks, it does indeed solve your particular problem:
class MacroUtil
{
#if !macro macro #end public static function get(key:String):Expr
{
This function signature says, if I'm already in the macro context, don't consider this function a macro function. It's just a static helper at that point. And remember that it returns an Expr
, not a String like it does in the main context.
If you mix macro and non-macro functions in a single file, you may also find yourself needing to use #if macro
to avoid this condition as well.
You can refactor your macro functions into macro functions and macro helpers. It's a little more verbose, but maybe a little more clear as to what's happening:
MacroUtil.hx
import haxe.macro.Context;
import haxe.macro.Expr;
class MacroUtil
{
macro public static function get(key:String):Expr
{
return Context.makeExpr(MacroHelpers.get_define(key), Context.currentPos());
}
macro public static function some_macro_function()
{
// Cannot call get from here:
var cvar:String = MacroHelpers.get_define('cvar');
trace('will trace at compile time, and cvar is ${ cvar }');
return macro trace('will trace at runtime');
}
}
class MacroHelpers
{
public static function get_define(key:String):String
{
return Context.definedValue(key);
}
}
If you do it this way, then your macro functions all call the MacroHelpers, and non-macro function call the MacroUtils. Notice the helper returns a String, and it's up to the call-site to then convert it to an expression, if that's what they want.
Upvotes: 3