Type Mapping

Your TypeScript types become C++ types. Here’s how the common ones line up. You rarely need to think about this — it just works — but the table below is handy when you want to know exactly what’s happening.


Primitive Types

TypeScriptC++Notes
numberintDefault; widened to long if value exceeds int range
booleanbool
stringconst char*On Zephyr, std::string is normalized to const char* — no heap
voidvoid
doubledouble
longlong
uint8_tuint8_tDirect pass-through
int16_tint16_tDirect pass-through
size_tsize_tDirect pass-through

Type Aliases from cuttlefish-env.d.ts

Scaffolded projects include C-style numeric type aliases available in TypeScript:

const count: uint8_t = 42;
const bigValue: int32_t = 100000;

These map directly to their C++ equivalents — no conversion needed.


Composite Types

Arrays

Array<T>, ReadonlyArray<T>, and T[] become plain C-style arrays on Zephyr (small and predictable, the way embedded code expects):

// TypeScript
const values: number[] = [1, 2, 3];
// C++ (Zephyr)
int values[] = {1, 2, 3};

On the native (desktop) target, they become std::vector<T>.

Negative numeric literals inside typed arrays are compile-time-safe and stay as initializers — they are not migrated into setup().

Objects

Object literals become anonymous C structs with generated type names:

// TypeScript
const config = { baud: 9600, parity: 'none' };
// C++
struct _config_t { int baud; const char* parity; };
_config_t config = {9600, "none"};

Nested objects generate recursively defined structs (deepest first). Top-level object literals used as destructuring sources are emitted as globals.

Enums

// TypeScript
enum PinMode { Input, Output, InputPullup }
// C++
enum class PinMode { Input, Output, InputPullup };

Collections

TypeScriptC++
Set<T> / ReadonlySet<T>std::set<T>
Map<K,V> / ReadonlyMap<K,V>std::map<K,V>
Record<K,V>std::map<K,V>

Functions

const handler: (pin: number) => void = (pin) => { /* ... */ };
// C++
std::function<void(int)> handler = [](int pin) { /* ... */ };

On Zephyr, std::function is normalized to C function pointer syntax.

Unions

Union types map to std::variant:

type Result = string | number;
// C++
std::variant<std::string, int> result;

Union with null or undefined strips the null side:

type MaybeNumber = number | null;
// C++: int (null handled via cuttlefish_nullish)

Template Literals

A string with ${...} placeholders becomes a small text buffer filled with the formatted result:

// TypeScript
const msg = `Temperature: ${temp}C`;
// C++
char __cuttlefish_str_1[17];
snprintf(__cuttlefish_str_1, sizeof(__cuttlefish_str_1), "Temperature: %dC", temp);

The + operator on strings works the same way:

const msg = "Value: " + String(value) + " units";

Nullish Coalescing (??)

The ?? operator becomes a safe check that treats 0 and false as real values (so 0 ?? 42 is 0, not 42):

// TypeScript
const value = input ?? 42;
// C++
int value = cuttlefish_nullish(input, 42);

Optional Chaining (?.)

The ?. operator becomes a safe guard:

// TypeScript
const len = obj?.length;
// C++
auto len = cuttlefish_exists(obj) ? obj.length : /* default */;

Equality Operators

TypeScript === and !== become C++ == and !=:

// TypeScript
if (value === 42) { /* ... */ }
// C++
if (value == 42) { /* ... */ }

Ownership Type Erasure

Owned<T>, Shared<T>, and Mutable<T> are unwrapped during type resolution — the inner T is used as the C++ type. Shared<T> adds const in C++ for compiler-enforced read-only access. No wrappers are generated. (See the Ownership docs.)


Classes

new ClassName() instances become value types or pointers depending on context. Cuttlefish generates proper C++ class definitions with constructors matching the TypeScript source.

Forward Declarations

When top-level code calls helper functions defined later in the file, Cuttlefish automatically inserts forward declarations before main(). Default arguments appear on the declaration, not the definition:

// Forward declaration (before main)
int calculateAverage(int a, int b = 0);

int main() { /* ... */ }

// Definition
int calculateAverage(int a, int b) {
  return (a + b) / 2;
}

Control Flow

for-of

// TypeScript
for (const item of items) { /* ... */ }
// C++
for (const auto item : items) { /* ... */ }

for-in

When object keys are known at compile time:

const char* _ki_obj_keys[] = { "name", "value" };
for (int _ki_obj = 0; _ki_obj < 2; _ki_obj++) { /* ... */ }

try/throw

On Zephyr, throw maps to an infinite halt loop since exceptions are disabled:

for (;;) { k_msleep(1000); }

A diagnostic is emitted if try-catch is used on a platform with -fno-exceptions.

Modulo on Float

The % operator on floating-point values is rewritten to fmod():

const remainder = value % 3.14;
double remainder = fmod(value, 3.14);

⚙️ Advanced details — framework overrides and formatting internals

How std::string is handled on no-STL targets

The framework strategy may override the default type mapping. On heap-free targets the strategy normalizes std::string to const char*, so string variables never allocate — and interpolated text is formatted into stack char buffers with snprintf instead of building heap std::string objects.

Format specifier inference

The transpiler automatically picks the right printf format based on the expression type:

Expression typeFormat specifier
Integer literal / int%d
Float literal / float%g or %.Nf
Boolean%s with "true" / "false" ternary
String%s
long%ld

The cuttlefish_nullish helper

The helper template behind ??:

template<typename T, typename U>
inline T cuttlefish_nullish(const T& a, U b) {
  return !cuttlefish_is_nullish(a) ? a : (T)b;
}

This preserves 0 as a real value (only a nullish sentinel triggers the fallback).