Categories
Haxe

You cannot use @:build inside a macro : make sure that your enum is not used in macro

If you ever come across this error, it can be a bit cryptic to understand.

Here’s some sample code that produces the error:

import haxe.macro.Expr;
import haxe.macro.Context;
import neko.Lib;

class BuildInMacroTests 
{
    public static function main() {
        var s:MyModel = new MyModel();
        MyMacro.doSomething( s );
    }
}

class MyModel extends sys.db.Object // has a build macro
{
    var id:Int;
    var name:String;
}

private class MyMacro 
{
    public static macro function doSomething(output : Expr) : Expr {
        return output;
    }
}

I’m still not sure I have a full grasp on it, but here’s a few pointers:

  • It’s not necessarily an enum – it can happen with a class or interface that has a “@:build” macro also.
  • The basic sequence happens in this order:
    • BuildInMacrosTest.main() is the entry point, so the compiler starts there and starts processing.
    • When it hits “var s:MyModel”, it does a lookup of that type, realizes the class needs a build macro to run, and runs the build macro.
    • When it hits “MyMacro.doSomething()”, it types the expression, and realizes that it is a macro call.  To run the macro, it must find the macro class, load it into the macro context, and execute it.
    • It finds the macro class, it’s in this file.
    • It tries to load this module (hx file) into the macro context, so it goes through the whole process of typing it again.
    • As it’s loading it into the macro context, it hits the “MyModel” build macro again, which it can’t do at macro time, so it spews out the error.
  • The basic solutions:
    • Wrap your build macro declarations in conditionals:
      #if !macro @:build(MyMacro.build()) #end class Object { ... }
    • Wrap anything that is not a macro in conditionals:
      #if !macro 
        class BuildInMacroTests {}
        class MyModel {}
      #else
        class MyMacro {}
      #end
    • Keep your macros in seperate files:
      BuildInMacroTests.hx:
          class BuildInMacroTests {
              public static function main() {
                  var s:MyModel = new MyModel();
                  MyMacro.doSomething( s );
              }
          }
      
          class MyModel extends sys.db.Object {
              var id:Int;
              var name:String;
          }
      
      MyMacro.hx:
          import haxe.macro.Expr;
          import haxe.macro.Context;
          class MyMacro {
              public static macro function doSomething(output : Expr) : Expr {
                  return output;
              }
          }

A combination of the 1st and 3rd solutions is probably usually the cleanest.

Good luck!