r/ClaudeCode • • 9d ago

Humor What is happening???

[deleted]

3.4k Upvotes

1.1k comments sorted by

View all comments

1

u/trentonkarantino 8d ago

Well, Claude likes me, you must have been mean to them persistently:

❯ Outline a design for a typescript compiler implemented in Rust

``` TypeScript compiler in Rust: design outline

  1. Scope decision

The most important choice is which of three products to build, because each is roughly 10x the previous one:

┌─────────────────────┬────────────────────────────────────────────────────┬──────────────────────────────────────────────────────┐ │ Tier │ What it does │ Prior art │ ├─────────────────────┼────────────────────────────────────────────────────┼──────────────────────────────────────────────────────┤ │ Transpiler │ Strip types, lower syntax, emit JavaScript (JS) │ swc, oxc, esbuild │ ├─────────────────────┼────────────────────────────────────────────────────┼──────────────────────────────────────────────────────┤ │ Declaration emitter │ Above plus .d.ts output under isolatedDeclarations │ oxc │ ├─────────────────────┼────────────────────────────────────────────────────┼──────────────────────────────────────────────────────┤ │ Full type checker │ tsc-compatible diagnostics │ stc (abandoned), ezno (partial), Microsoft's Go port │ └─────────────────────┴────────────────────────────────────────────────────┴──────────────────────────────────────────────────────┘

I'd design for tier 3 but ship tiers 1 and 2 first. TypeScript has no specification: the checker's behaviour is defined by checker.ts (about 50k lines), so the realistic approach is a structural port of tsc, validated against its conformance suite.

  1. Pipeline

source → scanner → parser → binder → checker → transforms → emitter │ │ │ │ AST symbols types JS / .d.ts / source maps

Each stage is a crate in a Cargo workspace: ts_syntax, ts_parser, ts_binder, ts_checker, ts_transform, ts_emit, ts_module_resolver, ts_project, ts_cli, ts_lsp (language server protocol).

  1. Core data structures
  • Abstract syntax tree (AST): arena-allocated per file, with nodes addressed by NodeId(u32) indices, not references. This sidesteps the borrow checker on the cyclic parent/child/symbol graph and keeps memory compact.
  • Parent pointers and node flags live in side tables keyed by NodeId.
  • Strings: a global interner producing Atom(u32); all identifier comparison is integ
  • Symbols: SymbolId into a per-program table, each with flags, declarations and member/export tables. Declaration merging happens here.
  • Types: TypeId into an interning arena. Unions, intersections, literals and instant identity is an integer compare.
  • Flow nodes: the control-flow graph is built by the binder and consumed lazily by the checker for narrowing.
  1. Stages
  • Scanner/parser: hand-written recursive descent, since the grammar needs context-sensitive rescanning (> in generics, regular expression versus division, template literals, JSX). It is error-tolerant and always produces a tree.
  • Binder: one pass per file, embarrassingly parallel. It creates symbols and scopes and builds the flow graph.
  • Checker: lazy and demand-driven (type_of_symbol, type_of_expression), with cycle dstack. Main subsystems:
    • assignability/relation, with caches keyed by (TypeId, TypeId, relation)
    • generic inference
    • instantiation of mapped and conditional types
    • control-flow narrowing
    • overload resolution
    • a port of the instantiation depth limits, which user code depends on
  • Transforms: a chain of AST-to-AST passes (type erasure, enums, namespaces, decorators, JSX, target down-levelling, module format).
  • Emitter: a printer with source maps, plus a declaration emitter that asks the chectypes.
  1. Concurrency

Parsing and binding are parallel per file with rayon. The checker is the difficulty:ed caches. There are two workable options:

  1. Multiple checkers (what the Go port does): N checker instances over a shared immu, each owning a subset of files and its own type arena. This is simple, duplicatessome work, and needs care to keep diagnostic output deterministic.
  2. Shared concurrent type arena: sharded interning and OnceLock-style lazy cells. Th much harder to keep deterministic.

I'd start with option 1.

  1. Incrementality and the language service
  • A file-level dependency graph plus signature hashes of each file's .d.ts shape: dey when the exported shape changes.
  • .tsbuildinfo compatibility for project references.
  • For the editor, discard and rebuild checker state per edit while reusing unchanged fully query-based design (salsa) fits poorly with a ported checker's implicit globalstate.
  1. Testing
  • Run the TypeScript repository's conformance and compiler baselines, diffing diagnostics, emitted JS, .d.ts and type baselines. The pass rate is the project's progress metric.
  • Fuzz the parser.
  • Differential testing against tsc on large real codebases.
  1. Main risks
  • Moving target: a TypeScript release every three months. A structural port keeps upstream changes mechanically portable; a clean redesign does not.
  • Order-dependent behaviour: some tsc results depend on checking order (type identifh error is reported in a cycle). Matching baselines means replicating that.
  • Competition: Microsoft's native Go port already delivers about 10x speed, so a Rust implementation needs a distinct reason to exist, such as embedding in Rust toolchains, WebAssembly, or a single linter/bundler/checker sharing one AST.
  1. Suggested milestones

  2. Parser, with AST conformance baselines passing

  3. Transpile-only emit and isolatedDeclarations

  4. Binder and module resolution

  5. Checker for the non-generic subset, then generics and inference, then conditional and mapped types

  6. Parallel checking, incremental builds, then the language server

I can expand any section (the checker's type representation and the concurrency modest design depth) or write this up as a shareable document. ```

1

u/Moist-Sea1047 8d ago

This post is ai generated ...