Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Perry Constraints (Detailed)

Perry is an AOT (ahead-of-time) compiler that transforms TypeScript directly into native machine code. Because there is no runtime interpreter or JIT, certain JavaScript/TypeScript patterns that rely on runtime dynamism are not supported. This guide documents every constraint with explanations and corrected code.

1. Dynamic Key Access

Pattern: obj[variable]

Perry resolves all property accesses at compile time. A dynamic key like obj[key] requires runtime dispatch to determine which property to read — Perry has no mechanism for this.

// BROKEN — Perry cannot resolve the property at compile time
function getConfig(config: Config, key: string): string {
  return config[key];
}

// FIXED — enumerate every possible key explicitly
function getConfig(config: Config, key: string): string {
  if (key === 'host') return config.host;
  else if (key === 'port') return config.port;
  else if (key === 'protocol') return config.protocol;
  return '';
}

This also applies to dynamic writes (obj[key] = value) and computed property access on arrays of objects.

2. Optional Chaining (?.)

Pattern: obj?.prop, arr?.[0], fn?.()

Perry does not implement the optional chaining operator. The compiler will fail or produce incorrect code.

// BROKEN
const name = user?.profile?.name;

// FIXED — explicit null/undefined checks
let name = '';
if (user !== null && user !== undefined) {
  if (user.profile !== null && user.profile !== undefined) {
    name = user.profile.name;
  }
}

Every level of the chain needs its own guard. This is verbose but produces correct native code.

3. Nullish Coalescing (??)

Pattern: value ?? fallback

Not implemented in Perry’s codegen. The operator is not recognized.

// BROKEN
const port = config.port ?? 8080;

// FIXED
let port = 8080;
if (config.port !== undefined) {
  port = config.port;
}

If you also need to guard against null:

let port = 8080;
if (config.port !== null && config.port !== undefined) {
  port = config.port;
}

4. Regular Expressions

Pattern: /regex/, RegExp, .test(), .match(), .replace() with regex

Perry has no regex engine. Any code that constructs or uses a RegExp will fail.

// BROKEN
if (/^[a-z]+$/.test(input)) {
  // ...
}

// FIXED — manual character checking
const ALPHA = 'abcdefghijklmnopqrstuvwxyz';
let allAlpha = true;
for (let i = 0; i < input.length; i++) {
  if (ALPHA.indexOf(input.charAt(i)) < 0) {
    allAlpha = false;
    break;
  }
}

For string splitting or searching, use indexOf, substring, and manual parsing loops.

// BROKEN
const parts = line.split(/\s+/);

// FIXED — split on single space, trim first
const trimmed = line.trim();
const parts: string[] = [];
let start = 0;
for (let i = 0; i <= trimmed.length; i++) {
  if (i === trimmed.length || trimmed.charAt(i) === ' ') {
    if (i > start) {
      parts.push(trimmed.substring(start, i));
    }
    start = i + 1;
  }
}

5. ES6 Shorthand Properties

Pattern: { name, age }

Perry requires explicit key-value pairs in object literals.

// BROKEN
const name = 'Alice';
const age = 30;
const user = { name, age };

// FIXED
const user = { name: name, age: age };

This applies everywhere: return statements, function arguments, variable declarations.

6. Array.map on Class Fields

Pattern: this.items.map(fn)

Perry cannot dispatch .map() on arrays stored as class instance fields. The method resolution fails at compile time.

// BROKEN
class UserList {
  users: User[] = [];

  getNames(): string[] {
    return this.users.map(u => u.name);
  }
}

// FIXED — use a for loop
class UserList {
  users: User[] = [];

  getNames(): string[] {
    const names: string[] = [];
    for (let i = 0; i < this.users.length; i++) {
      names.push(this.users[i].name);
    }
    return names;
  }
}

This also applies to .filter(), .reduce(), .find(), and other higher-order array methods on class fields. Use indexed for loops instead.

7. for...of on Arrays

Pattern: for (const item of items)

Perry does not implement the iterator protocol. for...of loops will not work on arrays.

// BROKEN
for (const item of items) {
  process(item);
}

// FIXED
for (let i = 0; i < items.length; i++) {
  const item = items[i];
  process(item);
}

Also avoid for...in on objects — use explicit property access instead.

8. Character Range Comparisons

Pattern: c >= 'a' && c <= 'z'

Perry does not support ordering comparisons on characters (strings of length 1). The comparison operators work on numbers, not character codes.

// BROKEN
function isLowerAlpha(c: string): boolean {
  return c >= 'a' && c <= 'z';
}

// FIXED
const LOWER_ALPHA = 'abcdefghijklmnopqrstuvwxyz';
function isLowerAlpha(c: string): boolean {
  return LOWER_ALPHA.indexOf(c) >= 0;
}

For digits:

const DIGITS = '0123456789';
function isDigit(c: string): boolean {
  return DIGITS.indexOf(c) >= 0;
}

9. Closure Capture Semantics

Pattern: Closures that read or mutate outer variables

Perry captures closure variables by value at the time the closure is created. Any subsequent mutation to the outer variable is invisible to the closure, and any mutation inside the closure does not affect the outer variable.

// BROKEN — closure captures initial value of this.count
class Counter {
  count = 0;

  setup() {
    setInterval(() => {
      this.count++;       // increments a stale copy, not the real field
      console.log(this.count);
    }, 1000);
  }
}

// FIXED — module-level state with named functions
let _count = 0;

function incrementCount() {
  _count++;
  console.log(_count);
}

setInterval(incrementCount, 1000);

Rules:

  • Store mutable state in module-level let variables.
  • Access that state through module-level named functions (not closures).
  • Never capture this in a closure passed to setInterval, event handlers, or callbacks.

10. requestAnimationFrame

Pattern: requestAnimationFrame(callback)

RAF never fires in Perry. The Perry runtime does not have a browser-style event loop with frame callbacks.

// BROKEN — callback never executes
function animate() {
  updateFrame();
  requestAnimationFrame(animate);
}

// FIXED
setInterval(updateFrame, 16); // ~60fps

11. setTimeout Self-Recursion

Pattern: setTimeout called inside its own callback

When setTimeout is called inside the callback of a previous setTimeout, the new timeout only fires once and then stops. This breaks recursive polling patterns.

// BROKEN — fires once, then stops
function poll() {
  doWork();
  setTimeout(poll, 1000);
}
setTimeout(poll, 1000);

// FIXED
setInterval(doWork, 1000);

If you need to stop the interval later, capture the return value:

const id = setInterval(doWork, 1000);
// later:
clearInterval(id);

12. String-Returning Functions in Async Context

Pattern: Returning a string from an async function or a function called in async context

String values returned from async contexts are NaN-boxed StringHeader pointers. When the receiving code treats the return value as a string, it gets a numeric pointer instead.

// BROKEN
async function getName(): Promise<string> {
  return fetchName(); // returns NaN-boxed pointer
}

// FIXED — inline the string operation at the call site
// instead of returning strings through async boundaries

Where possible, perform string operations directly where the result is needed rather than passing strings across async boundaries.

13. new Date() in Async Context

Pattern: new Date() inside an async function

The Date constructor malfunctions in async contexts due to how Perry handles object allocation across execution frames.

// BROKEN
async function getTimestamp(): Promise<number> {
  const now = new Date();
  return now.getTime();
}

// FIXED — Date.now() returns a plain number, no object allocation
async function getTimestamp(): Promise<number> {
  return Date.now();
}

Date.now() is safe everywhere because it returns a primitive number, avoiding the object allocation issue.

Quick Reference

AvoidUse Instead
obj[variable]if/else if per key
?. optional chainingExplicit null checks
?? nullish coalescingif (x !== undefined)
/regex/.test()indexOf or char checks
{ key } shorthand{ key: key }
this.arr.map(fn)for loop
for...of on arraysfor (let i = 0; ...)
c >= 'a' && c <= 'z'ALPHA.indexOf(c) >= 0
Closures capturing thisModule-level functions + state
requestAnimationFramesetInterval
setTimeout recursionsetInterval
String returns in asyncInline string ops
new Date() in asyncDate.now()