Supported TypeScript Features
Perry compiles a practical subset of TypeScript to native code. This page lists what’s supported.
Primitive Types
function primitives(): void {
const n: number = 42;
const s: string = "hello";
const b: boolean = true;
const u: undefined = undefined;
const nl: null = null;
console.log(`primitives: n=${n} s=${s} b=${b} u=${u} nl=${nl}`)
}
All primitives are represented as 64-bit NaN-boxed values at runtime.
Variables and Constants
function variables(): void {
let x = 10;
const y = "immutable";
var z = true; // var is supported but let/const preferred
console.log(`variables: x=${x} y=${y} z=${z}`)
}
Perry infers types from initializers — let x = 5 is inferred as number without an explicit annotation.
Functions
function functionsDemo(): void {
function add(a: number, b: number): number {
return a + b;
}
// Optional parameters
function greet(name: string, greeting: string = "Hello"): string {
return `${greeting}, ${name}!`;
}
// Rest parameters
function sum(...nums: number[]): number {
return nums.reduce((a, b) => a + b, 0);
}
// Arrow functions
const double = (x: number) => x * 2;
console.log(`functions: add=${add(2, 3)} greet=${greet("Perry")} sum=${sum(1, 2, 3)} double=${double(5)}`)
}
Classes
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
speak(): string {
return `${this.name} makes a noise`;
}
}
class Dog extends Animal {
speak(): string {
return `${this.name} barks`;
}
}
// Static methods
class Counter {
private static instance: Counter;
private count: number = 0;
static getInstance(): Counter {
if (!Counter.instance) {
Counter.instance = new Counter();
}
return Counter.instance;
}
}
Supported class features:
- Constructors
- Instance and static methods
- Instance and static properties
- Inheritance (
extends) - Method overriding
instanceofchecks (via class ID chain)- Singleton patterns (static method return type inference)
Enums
// Numeric enums
enum Direction {
Up,
Down,
Left,
Right,
}
// String enums
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE",
}
const dir = Direction.Up;
const color = Color.Red;
Enums are compiled to constants and work across modules.
Interfaces and Type Aliases
interface User {
name: string;
age: number;
email?: string;
}
type Point = { x: number; y: number };
type StringOrNumber = string | number;
type Callback = (value: number) => void;
Interfaces and type aliases are erased at compile time (like tsc). They exist only for documentation and editor tooling.
Arrays
function arraysDemo(): void {
const nums: number[] = [1, 2, 3];
// Array methods
nums.push(4);
nums.pop();
const len = nums.length;
const doubled = nums.map((x) => x * 2);
const filtered = nums.filter((x) => x > 2);
const sum = nums.reduce((acc, x) => acc + x, 0);
const found = nums.find((x) => x === 3);
const idx = nums.indexOf(3);
const joined = nums.join(", ");
const sliced = nums.slice(1, 3);
nums.splice(1, 1);
nums.unshift(0);
const sorted = nums.sort((a, b) => a - b);
const reversed = nums.reverse();
const includes = nums.includes(3);
const every = nums.every((x) => x > 0);
const some = nums.some((x) => x > 2);
nums.forEach((x) => console.log(x));
const flat = [[1, 2], [3]].flat();
const concatted = nums.concat([5, 6]);
// Array.from
const arr = Array.from([10, 20, 30]);
// Array.isArray
const value: any = [1, 2, 3]
if (Array.isArray(value)) { /* ... */ }
// for...of iteration
for (const item of nums) {
console.log(item);
}
console.log(`arrays: len=${len} doubled=${doubled.length} filtered=${filtered.length} sum=${sum} found=${found} idx=${idx} joined=${joined} sliced=${sliced.length} sorted=${sorted.length} reversed=${reversed.length} includes=${includes} every=${every} some=${some} flat=${flat.length} concatted=${concatted.length} arr=${arr.length}`)
}
Objects
function objectsDemo(): void {
const obj: { name: string; version: number; [k: string]: any } = { name: "Perry", version: 1 };
obj.name = "Perry 2";
// Dynamic property access
const key = "name";
const val = obj[key];
// Object.keys, Object.values, Object.entries
const keys = Object.keys(obj);
const values = Object.values(obj);
const entries = Object.entries(obj);
// Spread
const copy = { ...obj, extra: true };
// delete
delete obj[key];
console.log(`objects: val=${val} keys=${keys.length} values=${values.length} entries=${entries.length} copy=${copy.extra}`)
}
Destructuring
function destructuringDemo(): void {
// Array destructuring
const [a, b, ...rest] = [1, 2, 3, 4, 5];
const user = { name: "Alice", age: 30, email: "a@example.com", id: 1 }
const obj = { id: 2, role: "admin", level: 5 }
// Object destructuring
const { name, age, email = "none" } = user;
// Rename
const { name: userName } = user;
// Rest pattern
const { id, ...remaining } = obj;
// Function parameter destructuring
function process({ name, age }: { name: string; age: number }) {
console.log(name, age);
}
process(user)
console.log(`destructuring: a=${a} b=${b} rest=${rest.length} name=${name} age=${age} email=${email} userName=${userName} id=${id}`)
}
Template Literals
function templateLiteralsDemo(): void {
const name = "world";
const greeting = `Hello, ${name}!`;
const multiline = `
Line 1
Line 2
`;
const expr = `Result: ${1 + 2}`;
console.log(`template-literals: greeting=${greeting} multiline_len=${multiline.length} expr=${expr}`)
}
Spread and Rest
function spreadRestDemo(): void {
const arr1 = [1, 2]
const arr2 = [3, 4]
const defaults = { theme: "light", size: "md" }
const overrides = { size: "lg" }
// Array spread
const combined = [...arr1, ...arr2];
// Object spread
const merged = { ...defaults, ...overrides };
// Rest parameters
function log(...args: any[]) { /* ... */ }
log("a", "b", "c")
console.log(`spread-rest: combined=${combined.length} merged=${merged.size}`)
}
Closures
function closuresDemo(): void {
function makeCounter() {
let count = 0;
return {
increment: () => ++count,
get: () => count,
};
}
const counter = makeCounter();
counter.increment();
console.log(counter.get()); // 1
}
Perry performs closure conversion — captured variables are stored in heap-allocated closure objects.
Async/Await
async function asyncAwaitDemo(): Promise<void> {
interface Profile { id: number; name: string }
async function fetchUser(id: number): Promise<Profile> {
// The docs example uses fetch(...) here; we inline a synthetic
// result so the snippet compiles and runs hermetically.
return { id, name: `user-${id}` }
}
const data = await fetchUser(1);
console.log(`async-await: id=${data.id} name=${data.name}`)
}
Perry compiles async functions to a state machine backed by Tokio’s async runtime.
Promises
async function promisesDemo(): Promise<void> {
const p = new Promise<number>((resolve, reject) => {
resolve(42);
});
p.then((value) => console.log(value));
// Promise.all
const results = await Promise.all([
Promise.resolve("a"),
Promise.resolve("b"),
]);
console.log(`promises: results=${results.length}`)
}
Generators
function generatorsDemo(): void {
function* range(start: number, end: number) {
for (let i = start; i < end; i++) {
yield i;
}
}
for (const n of range(0, 10)) {
console.log(n);
}
}
Map and Set
function mapSetDemo(): void {
const map = new Map<string, number>();
map.set("a", 1);
map.get("a");
map.has("a");
map.delete("a");
map.size;
const set = new Set<number>();
set.add(1);
set.has(1);
set.delete(1);
set.size;
console.log(`map-set: map_size=${map.size} set_size=${set.size}`)
}
Regular Expressions
function regexDemo(): void {
const re = /hello\s+(\w+)/;
const match = "hello world".match(re);
if (re.test("hello perry")) {
console.log("Matched!");
}
const replaced = "hello world".replace(/world/, "perry");
console.log(`regex: match=${match !== null} replaced=${replaced}`)
}
Error Handling
function errorsDemo(): void {
try {
throw new Error("something went wrong");
} catch (e: any) {
console.log(e.message);
} finally {
console.log("cleanup");
}
}
JSON
function jsonDemo(): void {
const obj = JSON.parse('{"key": "value"}');
const str = JSON.stringify(obj);
const pretty = JSON.stringify(obj, null, 2);
console.log(`json: str_len=${str.length} pretty_len=${pretty.length}`)
}
typeof and instanceof
function typeofInstanceofDemo(): void {
const x: any = "hello"
if (typeof x === "string") {
console.log(x.length);
}
const obj: any = new Dog("Rex")
if (obj instanceof Dog) {
obj.speak();
}
}
typeof checks NaN-boxing tags at runtime. instanceof walks the class ID chain.
Modules
ES module syntax is fully supported: named exports, default exports, and re-exports.
The exporting module:
// Named exports
export function helper(x: number): number { return x + 1 }
export const VALUE = 42
// Default export
export default class MyClass {
name: string
constructor(name: string) {
this.name = name
}
}
The importing module:
// Default + named imports from a sibling module
import MyClass, { helper, VALUE } from "./utils"
// Re-export
export { helper } from "./utils"
BigInt
function bigintDemo(): void {
const big = BigInt(9007199254740991);
const result = big + BigInt(1);
// Bitwise operations
const and = big & BigInt(0xFF);
const or = big | BigInt(0xFF);
const xor = big ^ BigInt(0xFF);
const shl = big << BigInt(2);
const shr = big >> BigInt(2);
const not = ~big;
console.log(`bigint: result_ok=${result !== null} and_ok=${and !== null} or_ok=${or !== null}`)
}
String Methods
function stringMethodsDemo(): void {
const s = "Hello, World!";
s.length;
s.toUpperCase();
s.toLowerCase();
s.trim();
s.split(", ");
s.includes("World");
s.startsWith("Hello");
s.endsWith("!");
s.indexOf("World");
s.slice(0, 5);
s.substring(0, 5);
s.replace("World", "Perry");
s.repeat(3);
s.charAt(0);
s.padStart(20);
s.padEnd(20);
console.log(`string-methods: ${s.toUpperCase()}`)
}
Math
function mathDemo(): void {
Math.floor(3.7);
Math.ceil(3.2);
Math.round(3.5);
Math.abs(-5);
Math.max(1, 2, 3);
Math.min(1, 2, 3);
Math.sqrt(16);
Math.pow(2, 10);
Math.random();
Math.PI;
Math.E;
Math.log(10);
Math.sin(0);
Math.cos(0);
console.log(`math: floor=${Math.floor(3.7)} sqrt=${Math.sqrt(16)}`)
}
Date
function dateDemo(): void {
const now = Date.now();
const d = new Date();
d.getTime();
d.toISOString();
console.log(`date: now_positive=${now > 0}`)
}
Console
function consoleDemo(): void {
console.log("message");
console.error("error");
console.warn("warning");
console.time("label");
console.timeEnd("label");
}
Garbage Collection
Perry includes a mark-sweep garbage collector. It runs automatically when memory pressure is detected (~8MB arena blocks), but you can also trigger it manually:
function gcDemo(): void {
gc(); // Explicit garbage collection
}
The GC uses conservative stack scanning to find roots and supports arena-allocated objects (arrays, objects) and malloc-allocated objects (strings, closures, promises, BigInts, errors).
JSX/TSX
Perry’s parser and HIR understand JSX syntax (parsed via SWC, lowered in
crates/perry-hir/src/jsx.rs) and .tsx files link through Perry’s built-in
jsx() / jsxs() runtime path. You do not need a local
react/jsx-runtime package just to compile TSX.
import { Box, Text } from "perry/tui";
function Greeting({ name }: { name: string }) {
return <Text>{`Hello, ${name}!`}</Text>;
}
const page = <div className="card"><Greeting name="Perry" /></div>;
const app = <Box><Greeting name="TUI" /></Box>;
JSX elements are transformed to function calls via the jsx() / jsxs()
runtime. Perry’s built-in adapter supports HTML-style intrinsic tags,
fragments, function components, and compile-time rewrites for perry/tui
Box / Text so those TUI JSX forms lower to the same native builders as the
function-call form.
Caveat: this is Perry’s TSX runtime, not React DOM or full React reconciler
semantics. For perry/ui, or for perry/tui intrinsics whose JSX rewrite has
not landed yet, the function-call form remains the canonical native API.
JavaScript (.js) Input
Perry is a TypeScript compiler, but TypeScript is a superset of JavaScript — so
Perry also compiles plain JavaScript. .js, .cjs, .mjs, and .jsx files are
parsed as JavaScript (decorators, JSX, and import attributes enabled) and lowered
through the exact same native pipeline as .ts. No type annotations are required.
perry compile src/main.js -o myapp
./myapp
There are no guarantees for every dynamic JavaScript pattern (the
Limitations still apply — no eval, no general dynamic
require()), but most plain JavaScript projects compile and run.
Node.js Compatibility
Perry implements a large, real (non-stub) slice of the Node.js standard library —
fs, http/https/http2, net/tls, dns/dgram, crypto, stream
(+ stream/web), events, child_process, cluster, worker_threads, zlib,
process, async_hooks / AsyncLocalStorage, Atomics / SharedArrayBuffer,
and the WHATWG web globals (fetch, URL, streams, structuredClone,
WebCrypto, …). Against Node’s own test suite (node v26, 53 node:* modules)
Perry passes ~97% of cases, with overall Node/TypeScript compatibility around
95%. The per-module surface and remaining gaps are tracked in
docs/runtime-parity-gaps.md.
Next Steps
- Type System — Type inference and checking
- Limitations — What’s not supported yet