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* or a small fixed-size text bufferOn Arduino, text is stored in a fixed-size buffer rather than on the 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 AVR (small and predictable, the way embedded code expects):

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

On more capable boards, 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 Arduino, 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 __tc_str_1[32];
snprintf(__tc_str_1, sizeof(__tc_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<int, int>(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 setup(). Default arguments appear on the declaration, not the definition:

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

void setup() { /* ... */ }
void loop() { /* ... */ }

// Definition (after loop)
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 Arduino, throw maps to a halt macro since exceptions are disabled:

cuttlefish_halt("PANIC");

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 rewritten on Arduino

The framework strategy may override the default type mapping. @typecad/framework-arduino rewrites std::string to __tc_str_ptr, a custom fixed-size stack-allocated string struct that avoids heap allocation.

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 / int32_t%ld

AVR float handling

On AVR, %f in snprintf is disabled to save flash. Instead, dtostrf() is used:

// AVR float in template literal
char __tc_float_1[16];
dtostrf(temperature, 0, 2, __tc_float_1);
snprintf(buf, sizeof(buf), "Temp: %sC", __tc_float_1);

The cuttlefish_nullish helper

The helper template behind ??:

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

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