Reflaxe.Elixir supports two complementary “stdlib” layers:
- Haxe standard library compatibility (e.g.
Array,StringTools,haxe.io.*,sys.*) - Typed Elixir externs (e.g.
elixir.File,elixir.DateTime,elixir.IO, plusphoenix.*,ecto.*)
You can use either layer (or both) in the same codebase. The choice is about portability vs BEAM-first ergonomics.
See also:
- Haxe stdlib API inventory
docs/04-api-reference/STDLIB_SUPPORT_MATRIX.mddocs/02-user-guide/AUTHORING_STYLES_PORTABLE_VS_ELIXIR_FIRST.mddocs/02-user-guide/IMPERATIVE_TO_FUNCTIONAL_LOWERING.mddocs/02-user-guide/WRITING_IDIOMATIC_HAXE_FOR_ELIXIR.mddocs/02-user-guide/REFLAXE_RUNTIME_EXPLAINED.mddocs/06-guides/KNOWN_LIMITATIONS.mddocs/08-roadmap/stdlib-and-package-ecosystem.md
Major 1 now requires complete support for every public Haxe stdlib API applicable to generated
Elixir programs. That does not change the selective-override design below: a tested official Haxe
fallback is support, and copying an unchanged file is not progress. See Beads epic
haxe.elixir.codex-0yn.10 for the API inventory and remaining semantics work.
The checked inventory contains every public type, field, method signature, enum constructor, and overload found across the pinned Haxe target profiles. Its normal CI check catches surface or evidence drift:
npm run guard:stdlib-api-inventoryOne of those profiles is Java. Haxe ships that compiler backend separately as hxjava, so this
repository pins it in haxe_libraries/hxjava.hxml. A normal npm install fetches it through Lix.
If you installed dependencies with lifecycle scripts disabled, run npx lix download once before
the inventory guard. You do not need Java installed to generate Elixir; this dependency is only for
typing the pinned Haxe API surface consistently on every machine.
The stricter command answers the release question and is expected to fail until all stdlib work is finished:
npm run guard:stdlib-api-release-readySee the friendly inventory summary for the current counts and definitions. The older module report remains useful for locating target-owned files, but it is not a support score.
Application builds select this target through -lib reflaxe.elixir; the library supplies
the elixir define used by typed target APIs and stdlib overrides. Applications do not
need to define reflaxe_runtime.
reflaxe_runtime remains a compiler-development define for typing implementation code
guarded by #if (macro || reflaxe_runtime) outside macro mode. It is not an Elixir/Mix
project and does not exist in the generated application. See
REFLAXE_RUNTIME_EXPLAINED.md.
Runtime helpers that must exist as emitted Elixir live as Haxe sources under:
std/reflaxe/elixir/runtime/**(native modules likeReflaxe.Elixir.HaxeThrow)
The two core helpers, Reflaxe.Elixir.HaxeThrow and
Reflaxe.Elixir.HaxeFloat, are currently force-typed and kept at macro time by
src/reflaxe/elixir/CompilerInit.hx. They are therefore emitted into normal
generated .ex output even under -dce full. Their call sites remain
selective, but module inclusion is not yet fully demand-driven. See
reflaxe_runtime and Generated Elixir Helpers
for the reason and the tracked footprint work.
Reflaxe.Elixir does not aim to ship a broad Haxe runtime on the BEAM. The stdlib strategy is:
- Let the compiler lower Haxe constructs to ordinary Elixir when it can do so correctly.
- Map stdlib APIs to idiomatic BEAM primitives when the target already has the right abstraction.
- Use small emitted support modules only when Haxe semantics cannot be represented cleanly as compile-time lowering or a thin native wrapper.
Good examples:
haxe.crypto.Hmaccalls:crypto.mac(:hmac, ...);haxe.crypto.Sha224/Sha256call:crypto.hash/2andBase.encode16/2at runtime, with a pure-Haxe fallback only for macro/eval contexts.haxe.crypto.BaseCodeis a small emitted support module because BEAM has no native primitive for arbitrary caller-provided power-of-two dictionaries.haxe.ds.Mapand related surfaces currently use native%{}storage and lower toMap.*/Enum.*for many direct-receiver flows. That lowering is partial: ordinary Haxe map aliases do not yet share mutatingset,remove, andclearoperations. The managed-collection audit owns the final ordinary map representation; explicitly target-native maps remain%{}values.DynamicAccessis a legitimate boundary case: JSON, params, and other map-like values can arrive as native Elixir terms, so the target needs a contained bridge for typed access. That does not makeDynamica general app programming model.Reflaxe.Elixir.HaxeThrowandReflaxe.Elixir.HaxeFloatare deliberately small compatibility helpers for semantics the BEAM cannot directly represent: throwing arbitrary Haxe values, and IEEENaN/ infinity values.
Runtime support is a last resort for generated call shapes and new compiler design. If a stdlib feature can be expressed by better AST lowering, an Elixir-native extern, or a targeted stdlib override, do that before adding another runtime helper. This design rule is separate from the current conservative policy that retains the two core helper modules in every normal build.
The accepted managed-reference ABI is not a loophole for wrapping every BEAM value. It is compiler object-model support for ordinary Haxe values whose identity or shared mutation cannot be represented by local rebinding. Explicit native values remain raw, and future DCE/scalar replacement may remove managed support only when semantics and public ABI remain unchanged.
For many haxe.* modules, the upstream Haxe stdlib already compiles correctly for the Elixir target.
We only override/replace modules when one (or more) is true:
- upstream implementation uses inline patterns that produce invalid Elixir after lowering
- upstream implementation compiles, but produces systematically non-idiomatic Elixir (hard to read/maintain)
- we can map to a strong BEAM primitive (binary, iodata, pattern matching) and get both correctness + readability
- we need a BEAM mapping of
sys.*(filesystem/process/network/thread) rather than “pretend-JS” behavior
Absence from local override roots is therefore meaningful. If Reflaxe.Elixir does not provide a module
under std/elixir/_std/ or a target-owned support module under std/, Haxe falls through to the
installed official Haxe stdlib later on the classpath. That upstream fallback is the preferred answer
for modules that need no Elixir-specific implementation. Do not copy an upstream file into this repo
unless the target owns a real override or documented bootstrap exception.
This repo ships a small set of Elixir-target overrides using the Reflaxe source-layout convention:
std/elixir/_std/*.hx: core Haxe stdlib overrides (Array,String,Std, etc.)std/elixir/_std/haxe/**/*.hx: selected Haxe std modules implemented/adjusted for Elixirstd/elixir/_std/sys/**/*.hx: BEAM-backedsys.*surfaces
Layout rule:
- Use
std/elixir/_std/**/*.hxwhen replacing an upstream Haxe stdlib module with an Elixir-specific implementation that keeps the same public API. Reflaxe package builds turn these authored files into packaged.cross.hxfiles. - Do not add a plain
.hxcopy of an upstream stdlib file just to reduce the parity gap. If the upstream implementation works unchanged, use it from the official Haxe stdlib and add tests/tracking instead. - Put new target-owned support in an explicit target or project namespace.
Do not add official Haxe modules as plain files under
src/orstd/. - If macros need different host behavior, add
src/path/Module.macro.hx. Keep the target implementation understd/elixir/_std/path/Module.hx.
Packaging note:
- Reflaxe's generated-project skeleton authors target std overrides as plain
.hxunder configured_stdpaths, thenhaxelib run reflaxe buildcopies those files into the published classpath as.cross.hx. - Reflaxe.Elixir follows that source layout for stdlib overrides. For normal repo, GitHub-tag, and
Lix builds,
CompilerBootstrap.Start()addsstd/elixir/_std/directly to the active Haxe classpath so the authored files are used without a packaging step. - Adding these paths means Haxe searches the installed package's override/API directories earlier for
this compile. It does not copy files, generate files, or rewrite
.hxfilenames during compilation. - If we publish to haxelib.org, validate the generated package path separately with
npm run test:haxelib-package. That smoke test asserts that release artifacts use the Reflaxe-flattened shape: no rawstd/orsrc/elixir/_std/source tree is published, while upstream-colliding overrides are present as packagedsrc/**/*.cross.hxfiles.
haxe.Exception is a normal _std override at std/elixir/_std/haxe/Exception.hx. Source-checkout
HXML files make that root visible before typing, and Reflaxe build publishes the same implementation
as src/haxe/Exception.cross.hx. The executable implementation and its elixir_output guard are the
same in both modes.
Target-owned helpers use explicit namespaces. Use elixir.ArrayTools, elixir.MapTools, and
elixir.DateConverter. Old code that used the top-level helper names must update its import or
using statement. These helpers are not official Haxe stdlib replacements.
Macro code runs on Haxe's host platform. Elixir target overrides must not replace host behavior.
Haxe provides the .macro.hx suffix for this boundary.
Four collection modules need custom host implementations:
src/haxe/ds/ArraySort.macro.hxsrc/haxe/ds/BalancedTree.macro.hxsrc/haxe/ds/EnumValueMap.macro.hxsrc/haxe/ds/ListSort.macro.hx
Their Elixir implementations live under std/elixir/_std/haxe/ds/. Reflaxe packages those target
files as .cross.hx. GenericStack, HashMap, and List need no custom macro companion. Macro code
uses the official Haxe implementations for those modules.
Use a macro companion only after a focused test proves that official host behavior is insufficient. Do not add an empty companion as a precaution. Keep host code executable and keep target code typed.
See also:
docs/01-getting-started/cross-hx.mddocs/03-compiler-development/CROSS_FILES_STAGING_MECHANISM.md
This keeps:
- macro context and other targets using the official Haxe stdlib
- Elixir builds using the Elixir-specific overrides (and only those)
Best for:
- pure business logic you want to reuse across targets (JS/Node, Elixir, etc.)
- algorithms/data transforms that don’t need Phoenix/Ecto/OTP primitives
- libraries you intend to ship as “multi-target Haxe code”
Typical examples:
Array,StringTools,haxe.ds.Option,haxe.format.JsonPrinterhaxe.io.Bytesfor binary data manipulation (portable API, BEAM-optimized implementation)
Best for:
- Phoenix/LiveView/Ecto integration
- BEAM primitives (processes, iodata/binaries, filesystem ops) where the Elixir API is the “native shape”
- eliminating impedance mismatch (structs, atoms, tagged tuples) in app code
Typical examples:
elixir.DateTime/elixir.File/elixir.Path/elixir.IOphoenix.*/ecto.*integrations fromstd/phoenixandstd/ecto
The ideal architecture for most Phoenix apps:
- Haxe stdlib in the “domain layer” (pure logic, transformations, validation logic, parsing)
- typed Elixir/Phoenix externs in the “integration layer” (LiveView, Ecto, OTP callbacks)
- explicit boundaries:
- decode
Terminputs to typed structures at the edges - keep assigns typed (
Socket<Assigns>)
- decode
Rule of thumb:
- if you’re writing code that “looks like Phoenix”, prefer Phoenix/Elixir externs
- if you’re writing code that “looks like a reusable library”, prefer Haxe stdlib
When you need to fix stdlib behavior for the Elixir target:
- Classify the module first:
- upstream fallback works; add tests/docs/tracking, not a duplicate local file
- compiler lowering should own the behavior
- a BEAM-native stdlib override is needed
- a small runtime helper is unavoidable
- the feature should fail fast as unsupported on the Elixir target
- Prefer adding/adjusting Haxe sources in:
std/elixir/_std/**/*.hxsrc/**/*.macro.hxonly for a proven host-side companion- plain
std/**/*.hxonly for new target-owned support modules
- Add a snapshot test under:
test/snapshot/stdlib/**
- Add Haxe-authored ExUnit coverage when runtime semantics matter.
- Do not patch generated
.exas a behavior change (generated outputs are not the source of truth).
“Support the whole stdlib” doesn’t mean “every sys.* module is identical to native OS targets”.
For sys.*:
- implement what maps cleanly to BEAM/Elixir
- document differences when semantics diverge
- fail fast with actionable errors for things that cannot be supported safely
Track the current stdlib parity work in bd:
haxe.elixir-hm47(stdlib parity roadmap)