Programming Language Development: Anti-Patterns, Contested Questions, Currency, and Canon¶
Part 5 of 5 of the Programming Language Development reference (plugin
programming-language-development), covering §15–§20. Sibling skills:language-design-parsing-and-types(§0–§4),language-irs-optimization-and-backends(§5–§7),language-runtimes-interpreters-and-jits(§8–§9),language-diagnostics-tooling-and-evolution(§10–§14). Section numbers are shared across the set; a reference written as §N →skillpoints into that sibling skill.Currency: Verified August 2026. See §17 below for the currency snapshot and what goes stale first.
How to read this. Reference, not tutorial. Sections are independent. Three markers: - [DURABLE] — theory, algorithms, or a lesson every language implementation has independently learned. Does not expire. - [VERSIONED] — depends on a specific toolchain, standard, or project's current state. Verify against its docs. - [CONTESTED] — competent language designers disagree, publicly and permanently. Both cases given.
⚠️ GOTCHA boxes mark the design mistakes that are cheap to make on day one and impossible to undo on day one thousand.
The framing that organizes everything below: a language is a set of promises you can never take back. Syntax you can deprecate. Semantics you cannot. Every hard decision in this document is really the question "what am I willing to be permanently responsible for?" — and the languages people love and the languages people resent are distinguished mostly by how carefully their designers answered it early.
§15. Anti-Patterns¶
| Anti-pattern | Why | Instead |
|---|---|---|
| AST-only front end, no CST | Formatters, refactoring, and IDEs become impossible | Lossless CST, derive the AST (§2.4 → language-design-parsing-and-types) |
| Parser that gives up on the first error | The IDE sees broken code 100% of the time | Error nodes + recovery (§2.3 → language-design-parsing-and-types) |
| Generated parser for a production language | Poor error messages, poor recovery | Hand-written recursive descent + Pratt |
| One IR for everything | Every pass handles every abstraction level | 3+ levels, progressive lowering (§5.1 → language-irs-optimization-and-backends) |
| No IR verifier | Miscompiles found by users, not CI | Verifier after every pass in debug builds |
| No textual IR round-trip | Undebuggable, untestable passes | Print/parse your IR |
| Dropping spans during lowering | Debug info and diagnostics silently degrade | Carry spans to the end (§10.4 → language-diagnostics-tooling-and-evolution) |
| Skipping the occurs check | Compiler hangs on fun x -> x x |
Do the occurs check (§4.3 → language-design-parsing-and-types) |
| Naive HM generalization | Accidentally quadratic in environment size | Levels/ranks (§4.3 → language-design-parsing-and-types) |
| Global type inference, no signature annotations | Errors surface far from the cause | Require signatures; infer bodies |
| Deferring generic errors to instantiation | Pre-concepts C++ template errors | Check the generic body against its bounds |
| Undefined behaviour for convenience | The most hostile compiler behaviour there is | Define it — even "unspecified" beats "undefined" (§6.2 → language-irs-optimization-and-backends) |
| Cascading errors | One typo, 400 messages | Suppress derived errors (§2.3 → language-design-parsing-and-types) |
| Warnings everyone ignores | Trains users to ignore all output | Small high-precision default set (§10.3 → language-diagnostics-tooling-and-evolution) |
| Compile-time execution with I/O or no step limit | Non-reproducible builds, non-terminating compiles | Sandbox and bound it (§4.8 → language-design-parsing-and-types) |
| Cyclic module dependencies allowed | Forces whole-program analysis, kills incrementality | Forbid them (§3.2 → language-design-parsing-and-types) |
| Phase-ordered batch compiler, LSP added later | The largest refactor a compiler team can do | Query-based from day one (§11.2 → language-diagnostics-tooling-and-evolution) |
| Two implementations, one for the compiler and one for the IDE | Guaranteed divergent behaviour | One engine, two entry points |
| Optimizing before you have benchmarks | You will optimize the wrong pass | Measure first (§6.2 → language-irs-optimization-and-backends) |
| Adding a keyword without an edition mechanism | Breaks every program using it as an identifier | Reserve early or ship editions (§14.2 → language-diagnostics-tooling-and-evolution) |
| Inventing your own ABI while wanting C FFI | Interop bugs on every platform corner | Implement the platform ABI exactly (§7.4 → language-irs-optimization-and-backends) |
| Letting panics/exceptions unwind into C | UB | Catch at the boundary (§8.4 → language-runtimes-interpreters-and-jits) |
| Choosing GC late | Constrains calling convention, optimizer, FFI | Decide before the back end (§8.1 → language-runtimes-interpreters-and-jits) |
| No formatter, or a formatter with options | Permanent style arguments | Ship one, with no options (§13 → language-diagnostics-tooling-and-evolution) |
| No fuzzing on an optimizer | Miscompiles reach users | Csmith/YARPGen-style fuzzing in CI (§12.1 → language-diagnostics-tooling-and-evolution) |
| Untested diagnostics | They rot immediately | Snapshot-test error output (§10.2 → language-diagnostics-tooling-and-evolution) |
§16. Contested Questions¶
16.1 Static vs. dynamic typing. The empirical literature is genuinely weaker than advocates on both sides claim — controlled studies are small, short, and use toy tasks. What is well-supported: static types help at scale, on large teams, over long maintenance periods, and enable tooling (completion, refactoring) that dynamic languages approximate at best. Gradual typing (TypeScript, mypy, Sorbet) is the market's revealed preference, which is itself informative.
16.2 Monomorphization vs. erasure. §4.5 → language-design-parsing-and-types. Runtime speed vs. compile time and code size.
16.3 GC vs. ownership. Rust proved ownership is viable in a mainstream language; it also proved it has a real learning-curve cost. GC is easier to use and rules out whole domains (hard real-time, kernel, tiny embedded). Neither wins; the domain decides.
16.4 async/await vs. green threads. §8.3 → language-runtimes-interpreters-and-jits. Function colouring vs. runtime requirement.
16.5 LLVM or not. §7.1 → language-irs-optimization-and-backends. Best-in-class codegen and target coverage vs. compile speed,
dependency weight, and API churn. Zig is running the "not" experiment in public; rustc is
running the "both" experiment. Note that Cranelift's ~20% codegen speedup yields only ~5%
total build speedup in rustc — the backend is often not the bottleneck people assume.
16.6 Safe C++ vs. Profiles. WG21 rejected borrow checking for C++ and chose the Profiles direction; the enforcement attribute then slipped to C++29. For Profiles: incremental, no rewrite, works on existing code. Against: many practitioners consider it unable to deliver the guarantees borrow checking does. C++26 did ship real safety improvements you get by recompiling — hardened standard library plus contracts — so this is not nothing; whether it's sufficient is exactly the disputed point.
16.7 Sea of nodes. Powerful reordering vs. debuggability. V8 moving parts of TurboFan away from it is evidence that the debuggability cost is real at scale.
16.8 Batteries-included standard library. §13 → language-diagnostics-tooling-and-evolution.
16.9 Formal verification's cost/benefit. CompCert's Csmith result is the strongest pro-verification evidence in the field; the counter is that CompCert optimizes less and took enormous effort. Translation validation (Alive2) is the compromise most projects should actually adopt.
16.10 How much syntax novelty is justified? §1.3 → language-design-parsing-and-types. Familiarity is worth a great deal;
occasionally a genuinely better notation (Rust's ?, pattern matching, pipelines) earns
its cost. The failure mode is novelty for its own sake.
§17. Currency Snapshot — verified August 2026¶
| Thing | Status as of Aug 2026 | Decay risk |
|---|---|---|
| LLVM | 22.1.x current (22.1.0 released 24 Feb 2026; 22.1.8 in June). ~6-month feature cadence. LLVM 22 adds Armv9.7-A and GICv5 assembly support, C2y work in Clang (named loops), full MLIR-to-LLVM-IR translation for OpenMP TASKLOOP, RISC-V tail folding by default, ThinLTO distributed-build improvements | Medium |
| MLIR | Ships in the LLVM monorepo, moves with its releases; no separate qualification of the release branch. Substrate for Mojo, IREE, Triton, Flang | Medium |
| GCC | 16.1 released 30 April 2026. C++20 by default; ships C++26 reflection and contracts and safety hardening. ⚠️ C++20 modules still experimental, requiring -fmodules |
Medium |
| C++26 | ⚠️ Done. WG21 completed technical work 28 March 2026 (London Croydon; 210 experts, 24 nations); officially shipped by WG21 that date. Headline: static reflection, contracts, std::execution, hardened stdlib. Reflection operator changed ^ → ^^ during standardization. [[profiles::enforce]] deferred to C++29; Safe C++ borrow-checking proposal rejected. Herb Sutter stepped down as convener |
Low |
| WASI | ⚠️ WASI 0.3.0 released 11 June 2026. Native async moved into the Component Model canonical ABI (async func, stream<T>, future<T>); wasi:io removed entirely. Wasmtime 45 ran the RC, Wasmtime 46 ships it; jco supports it. 0.2 remains supported/virtualizable. Component Model 1.0 (formally specified) is the next milestone; WASI 1.0 planned, not shipped. ⚠️ Still no native multithreading. Reporting on version status is inconsistent — check wasi.dev |
High |
| Zig | 0.16 (beta) April 2026; 0.17 in progress. Self-hosted x86_64 backend default in Debug; reported hello-world compile 22.8 s → 275 ms, self-build 75 s → 20 s. Release notes state the x86 backend is now more robust than the LLVM backend for implementing Zig. Open tracking issue to remove LLVM/LLD/Clang libraries entirely. 0.16 added Alpha/KVX/MicroBlaze/OpenRISC/PA-RISC/SuperH; removed Solaris, AIX, z/OS | High |
| rustc backends | LLVM production; Cranelift available via rustup component add rustc-codegen-cranelift-preview and [profile.dev] codegen-backend = "cranelift". Measured ~20% codegen-time reduction → ~5% total clean-build speedup on Zed/Tauri/hickory-dns. GCC backend also maintained; all three behind rustc_codegen_ssa |
Medium |
| rustc type system | Next-gen trait solver and Polonius alpha: both targeted for stabilization, with CI testing being expanded (compiler-team MCP, June 2026). Polonius worst case measured at ~60% slower than NLL on a pathological 5 KLOC function (42K loans, 255K statements, 125K outlives constraints) | High |
| Cranelift | 0.127.x (Dec 2025). Supports x86-64, aarch64, s390x, riscv64. Production use in Wasmtime | Medium |
| gccrs | ⚠️ Still experimental. Stated 2026 goal: be able to mis-compile the Linux kernel. Handles simple standalone programs as of mid-2026; spent H1 2026 fixing attribute handling, name resolution, and resource management against kernel crates. Milestones: embedded → Rust-for-Linux → general purpose. Targets Rust 1.49 semantics, not current Rust | Medium |
| Mojo | 1.0.0 beta1, 7 May 2026. Chris Lattner / Modular; MLIR-based; Linux and macOS. Language under the Modular Community License (stdlib Apache-2.0-with-LLVM-exceptions) — not fully open source | High |
| Carbon | Experimental. Experimental MVP 0.1 expected late 2026 at the earliest; production 1.0 after 2028 | Medium |
Goes stale fastest: WASI/Component Model versions; Zig's backend and LLVM-removal
progress; rustc's Polonius/next-solver status; Mojo. Essentially never stale: §1 → language-design-parsing-and-types
(design principles), §2 → language-design-parsing-and-types (parsing), §4.2 → language-design-parsing-and-types–4.3 (inference and unification), §5.2 → language-irs-optimization-and-backends (SSA),
§6.1 → language-irs-optimization-and-backends (the passes), §7.3 → language-irs-optimization-and-backends (register allocation), §9 → language-runtimes-interpreters-and-jits (interpreter ladder), §10 → language-diagnostics-tooling-and-evolution (diagnostics),
§15 (anti-patterns).
§18. The Canon¶
18.1 Books¶
| Author | Work | Why |
|---|---|---|
| Robert Nystrom | Crafting Interpreters | Free online. The best starting point in existence: a tree-walker and a bytecode VM, both complete, both explained. Start here, always |
| Aho, Lam, Sethi, Ullman | Compilers: Principles, Techniques, and Tools ("the Dragon Book") | The classic. Strong on parsing theory, dated on modern back ends |
| Appel | Modern Compiler Implementation in ML/Java/C; Compiling with Continuations | The best structured treatment of a full compiler; CwC is the CPS reference |
| Muchnick | Advanced Compiler Design and Implementation | The optimization reference. Dense, comprehensive, still the standard |
| Cooper & Torczon | Engineering a Compiler | The best modern textbook; better back-end coverage than the Dragon Book |
| Pierce | Types and Programming Languages (TAPL) | The type systems book. If you're designing a type system, this is not optional |
| Pierce (ed.) | Advanced Topics in Types and Programming Languages | The sequel: dependent types, subtyping, effects |
| Harper | Practical Foundations for Programming Languages | Rigorous, opinionated, structural |
| Krishnamurthi | Programming Languages: Application and Interpretation | Free. Excellent on design trade-offs |
| Friedman & Wand | Essentials of Programming Languages | Interpreters as the lens for understanding semantics |
| Jones, Hosking, Moss | The Garbage Collection Handbook | The GC reference, full stop |
| Smith & Nair | Virtual Machines | VM and JIT architecture |
| Wirth | Compiler Construction | Short, clear, complete. A single-sitting read |
| Grune et al. | Parsing Techniques | Exhaustive on parsing |
18.2 Papers worth reading directly¶
- Cytron et al. (1991), "Efficiently Computing Static Single Assignment Form" — the classic SSA construction.
- Braun et al. (2013), "Simple and Efficient Construction of SSA Form" — use this one.
- Appel (1998), "SSA is Functional Programming" — the unifying insight.
- Maranget (2007), "Warnings for Pattern Matching" — exhaustiveness checking.
- Dunfield & Krishnaswami, "Bidirectional Typing" (survey) — the modern inference guide.
- Damas & Milner (1982) — Algorithm W.
- Chaitin (1981) — register allocation as graph colouring; Poletto & Sarkar (1999) — linear scan; Hack et al. — SSA-based allocation and chordality.
- Yang, Chen, Eide, Regehr (2011), "Finding and Understanding Bugs in C Compilers" — the Csmith paper, and the empirical case for verification.
- Leroy, the CompCert papers.
- Thompson (1984), "Reflections on Trusting Trust."
- Gabriel (1989), "Worse is Better."
- Plotkin & Pretnar, algebraic effects and handlers; Leijen on Koka.
- Grossman et al., Cyclone (regions) — the direct ancestor of Rust's ownership model.
18.3 Primary sources and ongoing¶
- LLVM:
llvm.org/docs(the Language Reference and Kaleidoscope tutorial), the discourse forums,mlir.llvm.org.rustc-dev-guide.rust-lang.orgis arguably the best publicly-written description of a production compiler's architecture. - Cranelift (
cranelift.dev), Wasmtime, Bytecode Alliance blog,wasi.dev, the Component Model book. - Language design in public: Rust RFCs and Inside Rust blog, Rust project goals,
Python PEPs, WG21 papers (
open-std.org/jtc1/sc22/wg21) and Herb Sutter's trip reports, Swift Evolution, Go proposals and theresearch.swtch.comdesign essays. - Zig devlog (
ziglang.org/devlog) — an unusually candid running account of compiler engineering decisions. - Conferences: PLDI, POPL, OOPSLA, ICFP, CGO, SPLASH; the LLVM Developers' Meeting; Strange Loop's archive.
- People to read: Chris Lattner (LLVM, Swift, MLIR, Mojo), Graydon Hoare (Rust; his retrospective essays on language design are excellent), Andrew Kelley (Zig), Rich Hickey (design talks), Simon Peyton Jones (GHC, and the clearest explainer in the field), Niko Matsakis (Rust types), Russ Cox (Go), Anders Hejlsberg (Turbo Pascal, C#, TypeScript), Jonathan Corbet's LWN coverage of toolchain work.
§19. Quick Reference¶
19.1 If you're building one, in order¶
- Read Crafting Interpreters. Build a tree-walking interpreter. Ship it.
- Design the CST and spans before anything else (§2.4 →
language-design-parsing-and-types, §10.4 →language-diagnostics-tooling-and-evolution). - Hand-written recursive descent + Pratt, with error recovery (§2.2 →
language-design-parsing-and-types–2.3). - Name resolution as a separate, queryable phase (§3.1 →
language-design-parsing-and-types, §11.2 →language-diagnostics-tooling-and-evolution). - Type checker — bidirectional unless you have a reason (§4.2 →
language-design-parsing-and-types). - Exhaustiveness checking — highest value per line of code you will write (§4.7 →
language-design-parsing-and-types). - Bytecode VM, so you have a working language.
- A typed, verifiable, printable mid-level SSA IR (§5 →
language-irs-optimization-and-backends). - mem2reg/SROA + inlining + constant folding + DCE. Stop. Measure (§6.1 →
language-irs-optimization-and-backends). - Backend: LLVM for output quality, Cranelift/custom for speed — and abstract over
the choice from the start (§7.1 →
language-irs-optimization-and-backends). - Diagnostics, snapshot-tested (§10 →
language-diagnostics-tooling-and-evolution). - Language server, from the same engine (§11 →
language-diagnostics-tooling-and-evolution). - Formatter, package manager, docs, debugger (§13 →
language-diagnostics-tooling-and-evolution).
19.2 Numbers worth knowing¶
- Optimal register allocation is NP-complete; SSA interference graphs are chordal, so SSA-form allocation is polynomial.
- Interpreter ladder: bytecode ~3–10× over tree-walking; computed goto ~1.5–2× over
switch; optimizing JIT 10–100× over interpreter (§9.1 →
language-runtimes-interpreters-and-jits). - Cranelift in rustc: ~20% codegen-time reduction → ~5% total clean build.
- Zig self-hosted x86 backend: hello world 22.8 s → 275 ms; self-build 75 s → 20 s.
- Csmith found hundreds of bugs in GCC and LLVM and zero in CompCert's verified middle end.
- LLVM ships roughly every 6 months; GCC roughly annually (16.1: April 2026).
19.3 Compiler-bug triage¶
| Symptom | Look at |
|---|---|
Wrong answer at -O2, right at -O0 |
Miscompile. Bisect passes (opt-bisect-limit), check UB in the source first |
| Compiler hangs | Occurs check, trait/instance resolution loop, unbounded comptime, pathological backtracking |
| Stack overflow in the compiler | Deep recursion on nested expressions — most compilers need an explicit depth limit or a manual stack |
| Wrong across an FFI boundary | ABI: struct classification, alignment, varargs, unwinding (§7.4 → language-irs-optimization-and-backends) |
| Works in debug, fails in release | UB, uninitialized memory, or an unsound optimization |
| Error message points at the wrong place | Span lost during lowering or desugaring (§10.4 → language-diagnostics-tooling-and-evolution) |
| IDE and compiler disagree | Two implementations, or a stale query cache (§11 → language-diagnostics-tooling-and-evolution) |
§20. Sources and Method¶
Method. Narrative (not systematic) review. The durable material — §1 → language-design-parsing-and-types (design
principles), §2 → language-design-parsing-and-types (parsing), §4.2 → language-design-parsing-and-types–4.7 (type systems), §5.2 → language-irs-optimization-and-backends (SSA), §6 → language-irs-optimization-and-backends (optimization), §7.2 → language-irs-optimization-and-backends–7.4
(codegen), §8 → language-runtimes-interpreters-and-jits (runtimes), §9 → language-runtimes-interpreters-and-jits (interpreters and JITs), §10 → language-diagnostics-tooling-and-evolution (diagnostics), §12 → language-diagnostics-tooling-and-evolution (testing),
§15 (anti-patterns) — is synthesized from the primary literature and canonical texts in
§18. Every time-sensitive claim (toolchain versions, standard status, project state)
was verified against a primary or near-primary source in August 2026 and is flagged in
§17 with a decay-risk rating. Where language designers genuinely disagree, §16 presents
both cases rather than adjudicating.
Search log (August 2026): LLVM current version and MLIR status · WebAssembly, WASI Preview 3, and the Component Model · Zig's self-hosted backend and LLVM removal; Mojo and Carbon status · rustc's Cranelift backend, Polonius, and the next-gen trait solver · C++26 finalization, contracts, reflection, and profiles · GCC 16 and gccrs.
Primary and near-primary sources consulted (selected):
- LLVM Discussion Forums release announcements (22.1.0 through 22.1.8); Arm's
"What is new in LLVM 21/22" engineering blogs; Phoronix LLVM/Clang 22.1 coverage;
mlir.llvm.org release notes
- wasi.dev — the WASI 0.3 release page and roadmap; Bytecode Alliance — "WASI 0.3
Launched" and "The Road to Component Model 1.0"; the Component Model book
- ziglang.org — 0.16.0 release notes and the 2026 devlog; the ziglang/zig issue
tracking LLVM/LLD/Clang removal; Ziggit and Lobsters discussion of the self-hosted x86
backend default
- Rust: rustc-dev-guide.rust-lang.org (codegen backends, next-gen trait solving);
the Rust Blog "Project goals update — April 2026"; rust-lang/compiler-team MCP #996 on
CI-testing the next solver and Polonius alpha; the rustc_codegen_cranelift repo and
the "Production-ready cranelift backend" project goal; cranelift.dev
- Herb Sutter — "C++26 is done! Trip report: March 2026 ISO C++ standards meeting";
InfoQ and isocpp.org coverage of C++26 and GCC 16.1
- gccrs — the project's monthly reports (Dec 2025, Feb/Mar/May 2026), rust-gcc.github.io,
LWN.net ("Progress toward compiling Linux with gccrs," "Gccrs after libcore")
- Canonical papers and books as listed in §18
Confidence statement. High confidence in §1–§13 → language-design-parsing-and-types, language-diagnostics-tooling-and-evolution and §15, §18–§19 — these rest on
the primary literature, canonical texts, and published compiler documentation. High
confidence in §17's verified items as of the stated date. Moderate confidence in the
performance figures quoted in §7.1 → language-irs-optimization-and-backends and §19.2: the Zig compile-time numbers come from
project announcements and community benchmarking rather than independent measurement, and
the Cranelift figures come from the Rust project's own measurements on three named
projects — both are directionally reliable and should not be treated as general
multipliers. The WASI status in §7.5 → language-irs-optimization-and-backends is the least stable content in this document: it
changed during 2026, and contemporaneous secondary sources describe 0.3 inconsistently as
released, in preview, and forthcoming — the dates given here follow wasi.dev and the
Bytecode Alliance directly, and should be re-checked rather than quoted from memory.
Plugin: programming-language-development · View SKILL.md on GitHub