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), but the runtime _jsx / _jsxs symbols are
not yet linked, so a .tsx file fails at the link stage today. The
canonical pattern is the function-call form Perry’s UI examples already use
(Text("hi") instead of <Text>hi</Text>).
// Planned JSX shape (parses but doesn't link yet — issue: runtime _jsx symbols):
function Greeting({ name }: { name: string }) {
return <Text>{`Hello, ${name}!`}</Text>;
}
<Button onClick={() => console.log("clicked")}>Click me</Button>
<>
<Text>Line 1</Text>
<Text>Line 2</Text>
</>
JSX elements are transformed to function calls via the jsx()/jsxs() runtime.
Next Steps
- Type System — Type inference and checking
- Limitations — What’s not supported yet