This page is the low-level reference for untyped __elixir__() mechanics.
For the default app-level workflow (typed extern first, __elixir__() last resort), start with docs/02-user-guide/INTEROP_WITH_EXISTING_ELIXIR.md.
Reflaxe.Elixir provides direct Elixir code injection using:
untyped __elixir__() - Direct injection for standard library and special cases
Important
Prefer __elixir__() in framework/stdlib code, not application code.
__elixir__() remains available in user apps as an escape hatch, but relying on it heavily
makes it harder for compiler transforms to stay structural/idiomatic.
In this repository, shipped examples opt in to a stricter policy by defining
-D reflaxe_elixir_strict_examples. This guard is examples-only: it fails compilation
when examples/<app>/src_haxe/** uses untyped __elixir__() (or introduces new app-local
extern bridges), so the examples stay “Haxe-first” and keep compiler transforms structural.
Your own applications are not required to enable this flag. __elixir__() and typed
extern definitions remain supported in user apps — just use them intentionally and prefer
promoting reusable Phoenix/Ecto interop into the framework/stdlib layer when it’s generic.
If you want a Gleam-like safety profile, enable strict mode with -D reflaxe_elixir_strict.
That mode rejects untyped, Dynamic, and ad-hoc externs in project-local sources (see
src/reflaxe/elixir/macros/StrictModeEnforcer.hx).
For the high-level decision of portable stdlib-first vs typed Elixir-first authoring, see
docs/02-user-guide/AUTHORING_STYLES_PORTABLE_VS_ELIXIR_FIRST.md.
If you need access to a Phoenix/Ecto/Elixir API from app code, add a typed wrapper in std/
(or a compiler-supported annotation module like @:repo) so the surface is reusable and documented.
// Inject simple Elixir expressions
var now = untyped __elixir__("DateTime.utc_now()");
var atom = untyped __elixir__(":ok");
var tuple = untyped __elixir__("{:error, \"reason\"}");Generated Elixir shape:
now = DateTime.utc_now()
atom = :ok
tuple = {:error, "reason"}This is a common Haxe-side mistake when using __elixir__(). The first argument must stay a constant
string literal so Reflaxe can process it as target code injection.
// ❌ WRONG: $variable syntax causes Haxe string interpolation at compile-time
untyped __elixir__('Phoenix.Controller.json($conn, $data)'); // FAILS!
// This becomes string concatenation: "" + conn + ", " + data + ")"
// Result: Not a constant string, Reflaxe cannot process it
// ✅ CORRECT: {N} placeholder syntax for variable substitution
untyped __elixir__('Phoenix.Controller.json({0}, {1})', conn, data); // WORKS!
// Variables are passed as parameters and substituted at placeholder positionsWhy this works:
$variableinterpolation runs in Haxe first, so the expression is no longer a constant literal.- Reflaxe target injection requires that first argument to remain constant.
{0},{1}, ... keep the template constant and substitute values safely.
Use {0}, {1}, etc. as placeholders for Haxe values:
// Pass Haxe values to Elixir code
var name = "Alice";
var age = 30;
var result = untyped __elixir__("User.create(%{name: {0}, age: {1}})", name, age);
// Generated Elixir:
// result = User.create(%{name: "Alice", age: 30})Generated Elixir shape:
name = "Alice"
age = 30
result = User.create(%{name: "Alice", age: 30})// Multi-line Elixir code
var result = untyped __elixir__("
case File.read({0}) do
{:ok, content} -> content
{:error, _} -> \"\"
end
", filename);All Reflaxe targets use the same untyped __target__() pattern:
| Target | Method | Example |
|---|---|---|
| Elixir | untyped __elixir__() |
untyped __elixir__('DateTime.now()') |
| JavaScript | js.Syntax.code() |
js.Syntax.code("Date.now()") |
| C++ | untyped __cpp__() |
untyped __cpp__("std::time(0)") |
| Go | untyped __go__() |
untyped __go__("fmt.Println({0})", msg) |
Note: JavaScript has evolved to use js.Syntax.code() as a type-safe alternative to untyped __js__().
// ❌ THIS FAILS:
var result = __elixir__("DateTime.now()");
// Error: Unknown identifier: __elixir__Why it fails:
- Haxe's type checker runs BEFORE Reflaxe
- Haxe looks for a function called
__elixir__ - No such function exists in any scope
- Compilation stops with "Unknown identifier"
// ✅ THIS WORKS:
var result = untyped __elixir__("DateTime.now()");Why it works:
untypedtells Haxe: "Don't type-check this expression"- Haxe wraps it in a
TUntypedAST node without validation - Reflaxe receives the AST with
__elixir__intact - Reflaxe recognizes the pattern and injects the Elixir code
When using __elixir__() in abstract type methods, you MUST use extern inline:
abstract LiveSocket<T> {
// ❌ WRONG: Fails with "Unknown identifier: __elixir__"
public function clearFlash(): LiveSocket<T> {
return untyped __elixir__('Phoenix.LiveView.clear_flash({0})', this);
}
// ✅ CORRECT: Works with extern inline
extern inline public function clearFlash(): LiveSocket<T> {
return untyped __elixir__('Phoenix.LiveView.clear_flash({0})', this);
}
}Why: Abstract methods are typed when imported, before Reflaxe initialization. extern inline delays typing until usage.
// When extern definitions don't work
public static function now(): Date {
var timestamp = untyped __elixir__("DateTime.to_unix(DateTime.utc_now(), :millisecond)");
return Date.fromTime(timestamp);
}// Atoms don't exist in Haxe
var status = untyped __elixir__(":active");
var result = untyped __elixir__("{:ok, {0}}", value);// Elixir pattern matching
var extracted = untyped __elixir__("
case {0} do
{:ok, value} -> value
_ -> nil
end
", maybeValue);// Pipe operator
var result = untyped __elixir__("{0} |> Enum.map(&(&1 * 2)) |> Enum.sum()", list);
// With blocks
var filtered = untyped __elixir__("
Enum.filter({0}, fn x ->
x > 10 and rem(x, 2) == 0
end)
", numbers);Treat this as an exception path. For app code, try typed extern + wrapper first and keep __elixir__() for explicit gaps.
-
Standard Library Implementation
// Efficient native implementations return untyped __elixir__("DateTime.utc_now()");
-
Temporary Gap Before a Typed Surface Exists
// Short-term bridge; replace with extern/wrapper when available untyped __elixir__("Phoenix.Controller.json({0}, {1})", conn, data);
-
Atoms and Tuples
// Elixir-specific data types return untyped __elixir__("{:ok, {0}}", result);
-
Performance Critical Code
// Avoid abstraction overhead untyped __elixir__("NIF.fast_operation({0})", data);
-
Business Logic
// ❌ WRONG: Business logic should be in Haxe untyped __elixir__(" defmodule BusinessLogic do def calculate(x), do: x * 2 end ");
-
When Haxe Has Equivalent Features
// ❌ WRONG: Use Haxe's array methods var doubled = untyped __elixir__("Enum.map({0}, &(&1 * 2))", arr); // ✅ CORRECT: Use Haxe var doubled = arr.map(x -> x * 2);
-
Type Definitions
// ❌ WRONG: Define types in Haxe untyped __elixir__("@type user :: %{name: String.t(), age: integer()}"); // ✅ CORRECT: Use Haxe types typedef User = {name: String, age: Int};
// The result is untyped
import elixir.types.Term;
var result: Term = untyped __elixir__("DateTime.now()");
// result is an opaque Elixir term - no structural typing until you wrap/decode it
// Cast if you need types
var date: Date = cast untyped __elixir__("DateTime.now()");// This will fail at RUNTIME, not compile-time
var bad = untyped __elixir__("This.Module.DoesNot.Exist()");// This only works when targeting Elixir
var result = untyped __elixir__("DateTime.now()");
// For cross-platform, use conditional compilation
#if elixir
var result = untyped __elixir__("DateTime.now()");
#else
var result = Date.now();
#endimport elixir.types.Term;
/**
* Uses __elixir__ because:
* - NaiveDateTime extern methods don't resolve properly
* - Need to generate idiomatic Elixir code
*/
public function toElixirDate(): Term {
return untyped __elixir__("Date.from_erl!({0})", erlDate);
}- Standard Library: Use freely for idiomatic output
- Application Code: Prefer Haxe or typed externs
- Business Logic: Always use pure Haxe
// ✅ GOOD: Small, focused injection
var atom = untyped __elixir__(":ok");
// ❌ BAD: Large blocks of Elixir code
var result = untyped __elixir__("
defmodule Helper do
def process(data) do
# 50 lines of Elixir...
end
end
");// ❌ BAD: String concatenation
var code = "DateTime.add(" + date + ", " + days + ", :day)";
var result = untyped __elixir__(code);
// ✅ GOOD: Use parameters
var result = untyped __elixir__("DateTime.add({0}, {1}, :day)", date, days);Solution: Add untyped before __elixir__()
Solution: Test your Elixir code in IEx first
Solution: Ensure you're passing the right number of arguments
Solution: Use cast to restore type information
1. Haxe Source:
untyped __elixir__("DateTime.now()")
2. Haxe AST (after parsing):
TUntyped(TCall(TIdent("__elixir__"), [TConst("DateTime.now()")]))
3. Reflaxe Processing:
- Detects __elixir__ pattern
- Extracts "DateTime.now()"
- Replaces placeholders with parameters
4. Generated Elixir:
DateTime.now()
__elixir__()is a compile-time marker, not a real functionuntypedis REQUIRED because__elixir__doesn't exist in Haxe's scope- Use
{N}placeholders for variable substitution, not$variable - Abstract types need
extern inlinefor proper timing - Use it for standard library and framework integration
- Avoid it for business logic that should be in Haxe
- Document why you're using it
- Keep it small and focused
Remember: The goal is idiomatic Elixir output that leverages the platform's strengths!