A common confusion when developing Reflaxe compilers is the difference between haxe.macro.Compiler and haxe.macro.Context APIs. Using the wrong API causes "macro-in-macro" compilation errors.
Reflaxe compilers run entirely in macro context. All code in ElixirCompiler executes during compilation, not at runtime.
These are macro functions designed to be called from regular Haxe code to generate compile-time expressions.
// This is a MACRO FUNCTION - note the 'macro' keyword
macro static public function getDefine(key:String) {
return macro $v{haxe.macro.Context.definedValue(key)};
}Usage: From regular Haxe code when you want compile-time values:
class MyClass {
static var appName = haxe.macro.Compiler.getDefine("app_name"); // ✅ Correct context
}These are regular functions designed to be called from within macro context.
public static function definedValue(key:String):String {
return load("defined_value", 1)(key);
}Usage: From macro context (like Reflaxe compilers):
#if (macro || reflaxe_runtime)
class ElixirCompiler {
private function getCurrentAppName(): String {
var appName = haxe.macro.Context.definedValue("app_name"); // ✅ Correct context
return appName;
}
}
#end#if (macro || reflaxe_runtime)
class ElixirCompiler {
private function getCurrentAppName(): String {
// ERROR: Calling macro function from macro context
var appName = haxe.macro.Compiler.getDefine("app_name");
return appName;
}
}
#endError Message: Uncaught exception macro-in-macro
#if (macro || reflaxe_runtime)
class ElixirCompiler {
private function getCurrentAppName(): String {
// Correct: Using Context API in macro context
var appName = haxe.macro.Context.definedValue("app_name");
return appName;
}
}
#end| Context | For Defines | For Types | For Metadata |
|---|---|---|---|
| Regular Code | Compiler.getDefine() |
Compiler.getType() |
N/A |
| Macro Context | Context.definedValue() |
Context.getType() |
Context.getLocalClass() |
- Writing regular Haxe application code
- You want compile-time constants in your classes
- Building macros that generate expressions for regular code
- Writing macros, build macros, or initialization macros
- Developing Reflaxe compilers (like ElixirCompiler)
- Any code marked with
#if (macro || reflaxe_runtime) - Inside
--macrofunctions
We needed to configure the Phoenix application name for PubSub calls. Using the wrong API caused compilation failures.
private function getCurrentAppName(): String {
// ✅ CORRECT: Use Context.definedValue() in macro context
#if app_name
var defineValue = haxe.macro.Context.definedValue("app_name");
if (defineValue != null && defineValue.length > 0) {
return defineValue;
}
#end
// Fallback to other methods...
return "App";
}# In build.hxml - single source of truth for app name
-D app_name=TodoAppAll PubSub calls now correctly use TodoApp.PubSub instead of hardcoded App.PubSub.
Example emitted Elixir shape (from codegen using that define):
Phoenix.PubSub.broadcast(TodoApp.PubSub, "todos:events", message)- Single Source of Truth: Use compiler defines (
-D) for configuration values - Priority System: Define > Annotation > Inference > Fallback
- Clear Documentation: Always comment why you're using Context vs Compiler APIs
- Test Both Contexts: Verify your code works in the intended macro context