Skip to content

Metadata

Andrew Lind edited this page Jul 29, 2026 · 9 revisions

Hatchet reacts to two kinds of metadata, kept deliberately separate. (Internally Hatchet matches metadata by name and ignores the @:/@ prefix, but the prefix matters to other Haxe targets, so write each tag in the form shown.)

Haxe / hxcpp compiler metadata Hatchet honours (@:…)

Real metadata whose meaning Hatchet matches, so the same source stays valid and keeps compiling under hxcpp:

Tag Effect in Hatchet
@:native("a::b::Name") Rename the emitted C++ symbol (and namespace) — the type/function is still Hatchet's to emit, just under that name. Orthogonal to extern
@:include("p.h") / @:include("<h>") Emit an #include (quoted for a project header, angle-bracketed verbatim for a system one)
@:cppFileCode('…') Inject verbatim C++ at that point in a body (also real hxcpp)
@:headerCode('…') Inject verbatim C++ into the generated header (after the #includes, before the declarations), on any module — also real hxcpp. On the prelude source (StdAfx.hx) it is instead merged into the prelude (see The Prelude)
@:overload(function(...){}) Resolve a call to the matching C++ overload by argument type, else a hard error
@:isVar Force a physical backing field for a property (so (get,never) keeps storage)
@:op(...) (on an abstract method) operator overloading → a C++ operator forwarding to the method: @:op([]) read → operator[], @:op(A op B) → binary operator op, prefix unary @:op(-A)
@:to / @:from (on an abstract method) @:to → an implicit conversion operator; @:from (static) → a converting constructor
@:stackOnly A value class that also obeys hxcpp's stack-residence rule — may not be nested as a field/element (flagged, steering to an abstract). Portable; use for genuine stack-only value types

Interop — the extern keyword

A type whose implementation lives in hand-written C++ is declared extern (extern class / extern interface / extern enum, all valid Haxe): Hatchet emits no definition for it, only type-checks and references it, and pulls its @:include into anything that uses it. This is separate from @:native, which only renames — combine them to bind to a specific C++ name (extern @:native("jobject") class JObject).

Hatchet directives (user metadata, @…)

Hatchet's own. The guiding rule: a user-metadata tag exists only for a C++ reality Haxe genuinely cannot express — now that value-types-with-methods are abstract newtypes, that comes down to a few things: manual memory ownership, zero-cost extern interop glue (@proxy), a VC6-safe ordered-map representation (@orderedMap), and shared-library / C-ABI symbol export (@libexport / @cexport — Haxe's own @:decl / @:abi turned out to be inbound-only, see the note above). Anything Haxe can say — operators, casts, value types via abstract, external types via extern, access levels — is expressed in real Haxe, not invented here. Because these are plain user metadata, every other Haxe target ignores them, so the source stays portable.

Tag Effect
@owned A field the owning object frees in its destructor (a scalar via delete, a container element-by-element)
@sink Ownership leaves the current scope (it is not freed here). On a parameter (@sink val:T) it marks a consuming position; on a call argument (foo(@sink v), new Quad(@sink new Vertex(...))) it hands that argument off at the call; on a local declaration (@sink var x = new X(...)) it suppresses the scope-close free of x (the inverse of @delete var)
@delete Free a marked local (@delete var x = …) at scope close
@proxy("native::Name") Binds a Haxe type to a native C++ class it is never emitted for — either a transparent handle you call through, or a base you subclass. See Interop via @proxy
@orderedMap (on a Map<K,V> field) store it as two insertion-ordered parallel vectors (m_keys/m_vals) instead of a std::map — an ordered map that also sidesteps std::map (key-sorted, and fragile on VC6). Operations (get/set/exists/remove/keys, for (k => v in m)) lower to scans over the vectors; using the field as a whole value is a hard error. See Container Semantics
@libexport (on a class) export it from the shared library via the portable <PREFIX>_CLASS visibility macro — __declspec(dllexport) on MSVC, __attribute__((visibility("default"))) on GCC/Clang, nothing elsewhere. Platform-neutral by design (covers Windows DLLs and Unix .so/.dylib)
@cexport (on a module-level function) export it across a C ABI: emitted as an extern "C" global at file scope with the portable export/calling-convention macros (HATCHET_EXPORT/HATCHET_CALL)

The ownership tags (@owned / @sink / @delete) are covered in detail on Memory Ownership.

Portability note

Both paths compile under hxcpp and Hatchet (the compile-time guarantee); only their runtime representation can differ, and Hatchet's emitted C++98 is the authoritative runtime (see the note on Home). The reference-semantic path — plain class plus @owned/@sink/@delete — even behaves the same under both (hxcpp's GC ignores the ownership tags; Hatchet's analysis uses them). The value-semantic path — abstract Name(U) and @:stackOnly — is a flat value type under Hatchet, but under hxcpp its representation depends on the underlying (an anonymous-structure underlying is a heap object there), so copy-vs-share behaviour can diverge. Validate such behaviour on the transpiled C++98.

Clone this wiki locally