Freerange: The TypeScript Analyzer That Turns `console.assert` Into Proofs

A zero-annotation static analysis engine for catching NaN, division by zero, and layout bugs before they ship.

8 min read • View on GitHub • More from chenglou

A drafting-table scene shows a TypeScript function under a contract seal at the top, with numbers flowing through a measuring instrument that splits into safe and unsafe paths. Hazards like 0, NaN, and Infinity appear as labeled trouble spots, explaining how ordinary code can become a machine-checkable boundary.
Freerange treats familiar TypeScript as a proof surface, not just a runtime script.
Key Takeaways

The clever part of Freerange is not that it knows math. Plenty of tools know math. The clever part is that it borrows a familiar JavaScript habit, `console.assert`, and upgrades it into a compile-time contract surface.

Introducing: Freerange, a zero-API tool that automatically deduces your code's numerical ranges. By doing so, Freerange is able to prove that e.g.: - your TS layouts obey your specified sizing - that they're free of NaNs and Infinity - that your array indices stay within bounds.

Cheng Lou, Software Engineer at Midjourney, Creator of Freerange · Cheng Lou on X

The weirdly powerful idea: `console.assert` becomes a proof boundary

Freerange treats assertions at function entry as caller requirements. Assertions inside the body become internal obligations. That split is simple, but it changes the whole shape of the problem: the tool no longer needs a new annotation language to tell it what must be true.

That is why the project feels so legible. It does not ask a TypeScript team to become a proof team. It asks them to keep writing ordinary code, then lets the analyzer infer where the boundaries are.

Why layout code is the perfect target

UI code is full of arithmetic that looks harmless and fails in ugly ways. A width divided by a column count can hit zero. A computed size can drift into `Infinity`. A branch meant to exclude invalid values can still let `NaN` leak through.

Failure modeWhy TypeScript misses itWhat Freerange tracks
Division by zero`number` stays opaqueRange plus excluded points like `0`
NaN propagationValid at the type level`mayBeNaN` and arithmetic rules
Infinity edge casesStill just `number`Upper and lower bounds with IEEE 754 behavior
Out-of-bounds indexingIndex is a numberFacts such as `belowLength` and `nonzero`
A close-up shows a numeric path being narrowed by three guards: `if (x > 0)`, `if (i < arr.length)`, and `if (!Number.isNaN(n))`. A side channel emits a refactor card when a requirement cannot be discharged, explaining how facts flow through control logic into repair guidance.
Freerange is most interesting when control flow becomes evidence.

How Freerange lowers TypeScript into a smaller world

Under the hood, Freerange does not analyze the full TypeScript AST as-is. It lowers code into a smaller intermediate representation, then runs the analysis over that simplified world. That matters because the smaller the language, the easier it is to reason about numeric behavior without drowning in syntax.

Freerange turns source code into a small proof pipeline where facts can be carried, strengthened, or discharged.

Inside the number domain

The core abstraction is not a raw `number`. It is an `AbstractNumber` that can carry lower and upper bounds, integer-ness, a `mayBeNaN` flag, and an `excludesPoint` constraint. That last one is quietly powerful. A range can include zero and still know that zero is impossible.

type AbstractNumber = {
  lower: number;
  upper: number;
  integer: boolean;
  mayBeNaN: boolean;
  excludesPoint?: number;
};

// Conceptually: [0, 10] but not 0.
// That is more useful than a coarse "positive" tag.

The implementation also respects floating-point reality. Functions like `nextUp` and `nextDown` use typed-array tricks to find the adjacent representable double, which means strict comparisons are modeled more honestly than a hand-wavy interval checker usually would.

Facts flow through the program, not just values

This is where Freerange stops looking like a range linter and starts looking like symbolic execution. The engine tracks `valueFacts` such as `nonzero` and `belowLength`, then uses branch conditions to strengthen those facts as code paths split and reconverge.

That is also why the analysis feels useful on real UI logic. A guard does not just satisfy the analyzer. It changes what the analyzer knows downstream, including across function calls in the same file.

A tiny proof sketch

function widthFor(cols: number, total: number) {
  console.assert(cols > 0);
  const cell = total / cols;
  if (!Number.isFinite(cell)) throw new Error("bad layout");
  return cell;
}

// Freerange can treat the assertion as a caller requirement
// and the division as safe only when the fact `cols !== 0` holds.

What the audit mode adds for humans and agents

Freerange is not content to print a warning and walk away. Its audit mode maps failures to refactor guides, which makes the output much more actionable for humans and much more machine-readable for AI assistants.

The first day we introduced Freerange into our Midjourney codebase, we've caught 12 layout bugs that slipped through the best agents. Can't really blame LLMs for not catching those across thousands of files and shaky floating point calculations.

Cheng Lou, Software Engineer at Midjourney, Creator of Freerange · Cheng Lou on X

That is the bigger bet. Freerange is not only a bug finder. It is a feedback engine for code that humans and agents can both repair.

What Freerange replaces, and what it intentionally avoids

Freerange sits in a useful gap. It is lighter than refinement-type systems, which usually demand annotations and solver-backed proofs. It is also far less ceremonious than full formal verification workflows, which can be excellent but expensive to introduce into ordinary product code.

ApproachAnnotationsSolver dependenceFit for app codeAI-agent friendliness
Branded typesManualNoneLightweight but shallowMedium
Refinement typesHeavyUsually yesStrong, but proof tax is realLow
Formal verificationVery heavyOften yesExcellent for critical systemsLow
FreerangeNone for the common pathCustom analyzer, no SMT baselineDesigned for ordinary TypeScript layoutsHigh

That tradeoff is the whole point. Freerange gives up the ceremony so it can reach code that teams actually ship.

Why this matters now

As more code arrives from AI tools, the bottleneck is shifting from generation to verification. Teams need safety rails that are machine-checkable, fast, and usable without a proof language in the room.

Freerange is deliberately designed to cater to a useful (and growing) subset of TypeScript... so that your code and math can meet in the middle to unlock the most proof power without much ergonomics drawbacks.

Cheng Lou, Software Engineer at Midjourney, Creator of Freerange · chenglou/freerange README

That is why Freerange feels timely. It is a bridge from vibe coding to proof engineering, built with just enough mathematical honesty to catch the bugs that matter and just enough ergonomics to fit inside a normal TypeScript workflow.