Skip to content

Statements and Expressions

Andrew Lind edited this page Jul 11, 2026 · 5 revisions

Construction & member access

  • super(...) initializer lists.
  • .-vs--> selection via type inference (including inherited fields).
  • Anonymous-struct-to-temporary expansion (typed, untyped, nested).
  • Array / map / object literals.

Containers

Arraystd::vector and Mapstd::map with their container ops:

  • Arraypush/insert/pop/shift/unshift/length/indexOf/lastIndexOf/contains/ remove/reverse/concat/slice/copy/join/filter/sort (the last two take a lambda: filter builds a kept-elements vector, sort is an in-place insertion sort driven by the comparator).
  • Mapget/exists/set/remove/keys (each an inline loop/expression, no <algorithm> dependency).

Containers are value types in the generated C++ — Haxe's are shared by reference, and mutating an Array/Map parameter is linted at the Haxe line (see Container Semantics for the full divergence and the idiomatic patterns).

Haxe's auto-extending array writesa[i] = v past the end grows the vector first (an inline resize), matching Haxe rather than letting C++ operator[] run off the end.

A typedef alias of a containertypedef Tileset = Array<Tile>; typedef Tilesets = Array<Tileset>; — maps as a name to its emitted typedef std::vector<…>, and every container operation resolves through the alias to that real container head: new Tilesets() value-constructs (it is never treated as an owned heap pointer to free), .pushpush_back, .length.size(), arr[i] indexing, and iteration / comprehensions all work on the aliased name exactly as on the underlying container.

A Map<K,V> field tagged @orderedMap is stored as two insertion-ordered parallel vectors instead of a std::map — a VC6-safe ordered map; see Container Semantics. An anonymous struct as a container element (Array<{x:Int}>) is a hard error (it would become std::vector<void*>) — give the struct a typedef and use that named type.

Loops

for over a range, an array, an anonymous array literal (for (i in [1,2,3])), a map (for (v in m) over values, for (k => v in m) over key/value pairs, via a std::map iterator), or a custom Iterator / Iterable — any value exposing hasNext():Bool + next():T, or an iterator() returning one, lowers to a while (it.hasNext()) { T x = it.next(); … } loop (./-> chosen by whether the iterator is a value or a reference type). When iterator() hands back a heap (reference-type) iterator, the loop owns and deletes it — including on an early return out of the body. The same forms drive array & map comprehensions ([for (x in e) …]).

Iteration that cannot be lowered is a hard error rather than a guess (see Diagnostics): a value that is none of the above, a custom iterator whose protocol methods are only inherited from a base class (they must be on the iterated type itself), and key => value over a value-only custom iterator each fail loudly with a specific message. (A custom iterator reached through a typedef alias does work — an alias is transparent, so it iterates exactly as its target.)

Functions & closures

  • Module-level functions (function f(...) {...} → a namespace free function, public ones declared in the header, private ones static in the .cpp). In --header-only mode they are emitted inline into the amalgamated header instead (there is no .cpp); see Building & Usage. They are callable bare (f(...)) or — as Haxe allows, the module name doubling as the primary class name — through that name (Module.f(...)); either way the call is the free function f(...) (namespace-qualified across modules), never a class-qualified Module::f. A same-named static member of the primary class still resolves to it (Module::f).
  • Closures inlined into a loop for Array.map/filter/sort, or lowered to free functions for a top-level final binding (an arrow param's type may be left off and taken from the binding's function-type annotation — Cross:(Vec, Vec) -> Float = (a, b) -> … types a/b as Vec).

Strings

String methods (charAt/charCodeAt/indexOf/lastIndexOf/substr/substring/toUpperCase/ toLowerCase/split), String.fromCharCode, StringBuf (an add/addChar/toString accumulator over std::string), and the StringTools statics (replace/trim/startsWith/endsWith/hex) — all mapped to std::string expressions; string interpolation and + concatenation (built as a std::string — text appended directly and numeric operands formatted into type-bounded buffers, so no value-guessed buffer can overflow; interpolation also supports the $ident shorthand).

Operators & casts

  • The ?? null-coalesce and NULL-guarded ?..
  • cast (C-style cast for cast(expr, T), passthrough for cast expr).
  • The (expr : Type) type ascription (a compile-time hint that drives inference, e.g. ([] : Array<Int>)).

switch

On an integer/enum subject → a C++ switch; on a String subject → an if/else if chain, since C++ case labels must be integral. With constant patterns — literals, negated numeric literals, and enum constants (bare or EnumType.Member-qualified), with the wildcard case _: lowering to default:, comma alternatives (case A, B:), and the or-pattern (case 1 | 2: — in pattern position | means or, exactly as in Haxe, and lowers to two case labels).

Destructuring of parameterized enumscase Add(a, b): switches on the tag and binds one typed local per non-_ capture from the variant's payload fields, with a side-effecting subject hoisted so it evaluates once (a destructuring pattern must be its case's only alternative, payload positions take only plain captures or _, and a bare capture pattern case x: stays flagged rather than emitted as a broken label).

Haxe's break semantics are preserved: Haxe switch has no break of its own, so a break in a case body exits the enclosing loop — inside a generated C++ switch it routes through a hoisted flag checked after the switch (f = true; break;if (f) break;), chaining through nested switches, while a break in a loop nested within a case body stays bound to that inner loop (continue needs no help — C++ gets it right natively) — including a switch used in value position (var x = switch (e) { … }), which desugars to a hidden temporary assigned inside a statement switch.

Diagnostics, exceptions & intrinsics

  • trace(...) (with --no-traces to strip it).
  • throw / try / catch exception handling (a thrown String is coerced to std::string; a typed catch (e:T) maps the exception type; an untyped/Dynamic catch becomes the non-binding catch (...) — so it may catch but cannot use the value: referencing the caught name there is a hard error, since C++ catch (...) binds nothing; see Memory Ownership for unwind behaviour).
  • The Math / Std / Sys intrinsics (Std.int/Std.string/Std.parseInt/Std.parseFloat/ Std.random → inline (int)/sprintf/strtol/atof/rand()).

Clone this wiki locally