Quartz v5.25

Quartz Language Roadmap Archive (v5.12.28)

Archived: Feb 6, 2026 Final State: v5.12.28-alpha | 1952 tests (1 failure, 22 pending) | Self-Hosted Primary

This document preserves the complete development history of the Quartz programming language from v1.0.0 (self-hosting fixpoint, Jan 2, 2026) through v5.12.28 (Feb 6, 2026).

The Story: Quartz went from a C bootstrap compiler to a fully self-hosted language in 35 days, implementing a world-class type system, UFCS, macros, closures, and modern ergonomics along the way.


Timeline Highlights

VersionDateMilestone
v1.0.0Jan 2, 2026Self-hosting fixpoint achieved - Quartz compiles itself
v2.0.0Jan 11, 2026Quartz compiler becomes primary
v3.0.0Jan 17, 2026Full type inference
v4.0.0Jan 18, 2026DWARF debug info, type checker parity
v5.0.0Jan 22, 2026Parallel iteration, pipeline |>, list comprehensions
v5.5.7Feb 2, 20260 failures (was 44). Macro system complete.
v5.10.0Feb 3, 2026Self-hosted macros, transitive closure capture. 1858 tests
v5.12.0Feb 4, 2026UFCS complete, dogfooding phase 2 complete
v5.12.28Feb 6, 202656 pre-existing test failures fixed (57→1). 1952 tests

Major Accomplishments

Type System Overhaul (v5.4 Series)

Transformed Quartz from “everything is Int” to a world-class type system:

  • Vec<T>, HashMap<K,V>, Ptr<T>, CPtr<T> parameterized types
  • Full type safety - can’t pass Vec<Int> where Vec<String> expected
  • Zero runtime overhead - all types erase to i64 at codegen

UFCS Migration (v5.12 Series)

  • 1029 calls converted from vec_push(v, x) to v.push(x)
  • Index syntax: v[i], v[i] = x, m["key"], m["key"] = x
  • Property access: v.size, s.length, m.keys

Macro System (v5.5-5.9 Series)

  • Quote/unquote AST manipulation
  • Hygienic by default with gensym
  • Built-in macros: $try, $unwrap, $assert, $debug
  • Self-hosted macro expansion

Compiler Structification (v5.11-5.12 Series)

Replaced all Vec-based pseudo-structs with proper types:

  • LexerResult, MirProgram, MirFunc, MirBlock, MirInstr
  • TypeStorage, AstStorage, ParserState, TypecheckState
  • CodegenState, InferStorage, InferEnv

Bootstrap Fixes (v5.12.28)

Fixed 56 pre-existing test failures:

  • Drop trait self parameter handling
  • Trait method call double-prefix bug
  • Safe navigation ?. lexing
  • as_type<T> builtin type handling
  • Generic function return types
  • Multiple type args handling

Completed Features

Module System (10 items)

  • Top-level variables, cross-module function resolution
  • Enum variant imports, private sections
  • Selective imports, aliased imports, qualified calls
  • Hierarchical import prefixing, qualified types
  • Private type enforcement

Type System (21 items)

  • Traits, impls, trait constraints
  • Generic return types, Drop trait (RAII)
  • Allocator trait, type aliases, newtypes
  • Match exhaustiveness, parameterized intrinsics

Syntax (22 items)

  • Regex literals and operators
  • Pipeline operator, list/map comprehensions
  • Labeled loops, postfix guards
  • Collection literals, slice syntax
  • String/Vec/HashMap method sweetness

Concurrency (13 items)

  • Thread spawn/join, Mutex, Channel
  • Atomic operations, parallel intrinsics

FFI (25 items)

  • extern “C” declarations, variadic functions
  • @repr(C) structs, C type aliases
  • @cImport for C headers

Error Handling (7 items)

  • try/catch, Result audit
  • Elm-quality error messages with —explain
  • Numbered error codes (QZ0xxx)

Memory (8 items)

  • Arena/pool allocation
  • Typed pointers, implicit drop glue
  • defer statement, @heap annotation

Regex (13 items)

  • PCRE2 integration (replaced Thompson NFA)
  • Named groups, backreferences
  • find_all, replace, split, count

The Full Roadmap Document

The complete original roadmap with all phase details, implementation notes, and bug fixes is preserved below for historical reference.


Original ROADMAP.md Content (v5.12.28)

Quartz Language Roadmap

Current: v5.12.27-alpha | 1952 tests (57 failures, 22 pending) | Feb 6, 2026

Status: Dogfooding Phase 2 COMPLETE | Self-Hosted Primary

Philosophy Shift: Self-hosted compiler is now the primary development target. C bootstrap is frozen as emergency recovery. See AGENTS.md for when to update bootstrap.

UFCS Migration Status (87% complete):

  • 1029 calls converted to UFCS (.size, .push, .pop, .clear, indexing)
  • ~130 calls remaining — handle patterns (Int indices into Vec) that CANNOT be converted
  • Phase 5.6.9 COMPLETE — handle patterns categorized, convertible calls done

v5.12.27 (Feb 6): Reclaim ?/! for Identifiers (Phase 5.8.12):

  • Trailing ? and ! now allowed in identifiers (Ruby/Crystal style)
  • Postfix ? operator REMOVED — use $try(expr) macro instead
  • Postfix ! operator REMOVED — use $unwrap(expr) macro instead
  • Predicates now real identifiers: v.empty? tokenizes as [v][.][empty?] (3 tokens, not 4)
  • C bootstrap: lexer.c allows ?/! suffix, parser.c removed postfix handling, typecheck.c handles predicates in field access
  • Self-hosted: lexer.qz, parser.qz, typecheck.qz updated with same changes
  • Tests updated: opt?$try(opt), opt!$unwrap(opt) across all specs
  • ref.md updated: Documents new identifier syntax and $try/$unwrap macros
  • Phase 5.8.12 now COMPLETE

v5.12.26 (Feb 6): Predicate Unification (Phase 5.8.4):

  • Result predicates: .ok? / .err? rewrites to is_ok() / is_err()
  • Char predicates: .digit? / .alpha? / .alnum? / .whitespace? rewrites to is_digit() etc.
  • C bootstrap + self-hosted: Both compilers updated with UFCS predicate rewrites
  • 18 new tests in result_predicates_spec.rb (5) and char_predicates_spec.rb (13)
  • Phase 5.8.4 now COMPLETE

v5.12.25 (Feb 6): Set UFCS Methods (Phase 5.7.5):

  • Set UFCS methods: .add(), .delete(), .remove(), .contains(), .has(), .clear(), .members(), .size
  • Set predicates: .is_empty, .empty? now work on Set types
  • C bootstrap: Added Set$* UFCS rewrites in typecheck.c (method calls + property access + empty predicate)
  • Self-hosted: Added Set$* UFCS registrations and rewrites in typecheck.qz
  • 16 new tests in spec/integration/set_ufcs_spec.rb
  • Phase 5.7.5 (Deletable) now COMPLETE for both HashMap and Set

v5.12.24 (Feb 6): TY_* Enums for Phase 2 Builtins:

  • 8 new type kinds added to bootstrap: TY_SET, TY_INTMAP, TY_ARENA, TY_POOL, TY_ARENAPOOL, TY_CHANNEL, TY_MUTEX, TY_ATOMIC
  • Builtins updated: set_new→Set, intmap_new→IntMap, arena_new→Arena, pool_new→Pool, arena_pool_new→ArenaPool, channel_new→Channel, mutex_new→Mutex, atomic_new→Atomic
  • types.h/types.c: Added type kinds, singletons, types_match support, type_name, type_kind_name
  • typecheck.c: Updated all builtin registrations to return proper types
  • Self-hosted updated: typecheck.qz and types.qz now have TYPE_SET()..TYPE_ATOMIC() constants
  • ENABLES: Future UFCS for Set.add(), Arena.alloc(), Channel.send(), Mutex.lock(), etc.
  • Match payload type inference verified WORKING: match Result::Err(e) => e.message correctly types e as the error struct

v5.12.23 (Feb 6): Cross-Module Type Aliases:

  • Cross-module type aliases now worktype ParseResult<T> = Result<T, ImportedError>
  • resolver.c: Added NODE_TYPE_ALIAS handling in module merge (prefix alias name and aliased_type)
  • ast.c: Added ast_program_has_type_alias() helper function
  • typecheck.c: Added fallback lookup in tc_lookup_type_alias() for module$AliasName
  • spec_helper.rb: Added compile_multi_file() and compile_and_run_multi() for multi-file tests
  • 2 new tests in spec/integration/type_alias_spec.rb for cross-module type aliases
  • UNBLOCKS: Domain Result migration (Phase 3 of option_result_stdlib)

v5.12.22 (Feb 6): Bootstrap Arity-Mangling Fixes:

  • All 8 remaining test failures FIXED (down from 25 on Feb 5)
  • Type safety tests: Updated expectations — compiler now correctly reports type mismatches
    • expected Vec<String>, got Vec<Int> instead of “Undefined function”
  • Arity mismatch detection: Added tc_find_any_arity_variant() helper
    • Reports “Function X expects N argument(s), got M” for wrong arity calls
  • Trait constraint lookup fix: tc_lookup_bounded_func() now strips arity suffix
    • Was looking up are_equal$2 but bounded func registered as are_equal
  • Files changed: typecheck.c (3 fixes), 2 spec files (updated expectations)

v5.12.21 (Feb 5): Quality of Life Improvements:

  • Phase 5.8.9: Lexer now requires spaces around ?? operator for readability
    • x ?? 42 (valid), x??42 (error: spaces required around ?? operator)
    • Makes predicate syntax (v.empty?) unambiguous
  • Phase 5.6.6 Phase 2: Added missing builtin return types to MIR
    • sb_new, set_new, intmap_new, arena_new, pool_new, arena_pool_new, channel_new, mutex_new, atomic_new
    • Enables UFCS for these types when TY_* enums are added later
  • Parallel testing: rake test now runs specs in parallel (8 cores, ~5x faster)
    • rake stest for serial execution when debugging
  • 5 new tests in spec/integration/nil_coalescing_spec.rb for ?? spacing

v5.12.20 (Feb 5): Type Inference Bootstrap Fixes:

  • Fixed HOF function reference lookup: Passing functions as values (apply(double, 21)) now works — MIR tries arity-mangled names (name$0, name$1) for TY_FUNC identifiers
  • Fixed struct field inference: def get_x(p) return p.x end where p is inferred from struct type — typechecker now sets node->ty after struct inference, MIR fallback checks obj_node->ty->data.strukt.name
  • Fixed type mismatch detection: add("hello", 5) now correctly rejects String + Int — function lookup uses arity-mangled names, UFCS logic only tries prefixed lookup for untyped params (not type mismatches)
  • Files changed: typecheck.c (arity lookup, UFCS logic), mir.c (HOF function ref, struct field fallback)
  • All 24 type inference tests passing in spec/integration/type_inference_spec.rb

v5.12.19 (Feb 5): Defer Statement Fix:

  • Fixed defer typecheck bug: NODE_DEFER was calling typecheck_expr on deferred statement, but parser wraps calls in NODE_EXPR_STMT
  • Root cause: typecheck_expr doesn’t handle NODE_EXPR_STMT, so arity mangling wasn’t applied to deferred function calls
  • Fix: Changed to typecheck_stmt which properly processes NODE_EXPR_STMT and applies arity mangling
  • Result: defer cleanup("msg") now correctly emits call cleanup$1(...) in MIR
  • 15 defer tests now passing (was 0 before fix)

v5.12.18 (Feb 5): Arity-Based Function Overloading:

  • Arity overloading implementeddef add(), def add(x: Int), def add(x: Int, y: Int) are distinct functions
  • Mangling scheme: Functions registered/called as name$arity (e.g., greet$0, greet$1, greet$2)
  • Cross-module support: Import resolver uses arity-qualified names for duplicate checking
  • Drop trait fix: Drop calls emit Type$drop$1 (arity 1 for self parameter)
  • Files changed: typecheck.c (arity lookup), mir.c (arity-aware binding), mir_codegen.c (main detection), resolver.c (import arity)
  • 8 new tests in spec/integration/arity_overload_spec.rb — all passing
  • Pre-existing gaps identified: FFI/type_inference (bootstrap limitations)

v5.12.17 (Feb 5): Phase 5.6.9 Handle Pattern Completion:

  • 40 struct fields changed from bare Vec to Vec<Int> (typecheck.qz: 38, infer.qz: 2)
  • ~50 calls converted from vec_push()/vec_len() to .push()/.size in typecheck.qz
  • Key insight: mir.qz/codegen.qz use existential typing (bare Vec fields with typed getter returns like Vec<String>) — NOT safe to convert
  • Handle patterns identified: struct_field_names[i], enum_variant_names[i], trait_method_names[i] return Int handles, MUST use vec_len(handle) not .size
  • Type alias clarification: Reported issue was syntax confusion, not a bug. Struct constructors use braces Point { x: 1 }, not parens Point(1, 2)
  • 1922 tests pass (cross-module type alias tests now passing)

v5.12.16 (Feb 5): Bootstrap Scope Shadowing Fix + Version Flags:

  • Fixed C bootstrap scope shadowing bug: x = expr (parsed as NODE_LET) was incorrectly creating new immutable bindings even when a mutable binding existed at an outer scope
  • Root cause: Quartz syntax x = expr means both “declare new const” and “reassign”. The typechecker now treats it as reassignment when a binding already exists (unless explicit var x = expr)
  • Key fix in typecheck.c:4197: if (existing && !node->data.let.is_mutable) → treat as reassignment to existing binding
  • Added --version / -v flags to both C bootstrap and self-hosted compiler
  • Version synced: Both compilers now report quartz 5.12.15-alpha
  • Self-hosted compiles itself again (Stage 2 validation passes)
  • All 1907 tests pass

v5.12.15 (Feb 5): Chained Index Assignment in Bootstrap:

  • Fixed v[0] = v[1] = 42: Added NODE_INDEX_ASSIGN case to lower_expr() in mir.c
  • Root cause: Index assignment was only in lower_stmt() (returns void), not lower_expr() (returns MIRValue)
  • Chaining requires the assignment to return its stored value for the outer assignment
  • Parser, typecheck, and codegen fixes from prior sessions now working end-to-end
  • All 10 vec_index_spec tests pass (1 pending for future work)

v5.12.11 (Feb 5): Bootstrap Developer Experience + Limit Increases:

  • Bootstrap CLI flags: Added -o (output path), --run (compile+execute), --emit-ir (dump LLVM IR)
  • Fixed type_name() buffer corruption: Now uses 8 rotating 512-byte buffers for recursive calls
  • Increased trait/impl limits: TC_MAX_TRAITS 32→64, TC_MAX_IMPLS 64→128, MAX_ENUMS 64→128
  • 6 new nested generic tests for CPtr/Array/Ptr with complex type params
  • Phase 6 audit: 6.1-6.3 COMPLETE, 6.5 COMPLETE, 6.4/6.6 pending (type system gaps)

v5.12.8-10 (Feb 5): Language Cleanup + Type Parsing Fixes:

  • Purged let keyword: Quartz uses x = expr (const-by-default), no explicit let
  • Fixed naive type parsing in typecheck.c: CPtr, Array, Ptr now use depth-based bracket matching
  • Fixed naive type parsing in types.c: Added find_matching_close_bracket() helper, replaced strrchr
  • Increased limits: MAX_STRUCTS/TC_MAX_STRUCTS 64→256, MAX_MODULES 64→256, MAX_MIR_FUNCTIONS 1024→2048
  • Trimmed AGENTS.md: 225→86 lines, added anti-hallucination table for Rust/Swift/Ruby keywords
  • 7 nested generics tests pass, all type parsing tests pass

v5.12.7 (Feb 5): Nested Generics Fix + Language Audit:

  • C bootstrap: Fixed Vec type parsing in typecheck.c:1197-1219 — depth-based bracket matching
  • Problem: Vec<Vec<Int>> was parsed as Vec<Int (naive len - 5 offset broke for nested types)
  • Solution: Use depth tracking like HashMap already does — counts < and > to find matching bracket
  • 7 new tests in spec/integration/nested_generics_spec.rb
  • UNBLOCKS: 5.6.9 UFCS cleanup → 5.3-5.4 UFCS migration (~2,121 calls)
  • Language Audit: Identified 28 naiveties/limitations (see Phase 6 section below)

v5.12.6 (Feb 5): Struct Literal Field Ordering Parity:

  • C bootstrap Phase 1-2: Fixed struct literal field ordering (use find_field_index() not literal order)
  • Self-hosted Phase 3: Fixed MIR lowering in mir.qz to use mir_ctx_find_field_index for correct indices
  • Self-hosted str_eq fix: Fixed 22 str_eq() calls in typecheck.qz to use == 1 (returns Int, not Bool)
  • Phase 4 resolved: codegen.qz slot1 issue resolved by Phase 3 MIR fix
  • All 21 struct tests pass, 4/4 field ordering tests pass
  • All 1882 tests pass (2 pre-existing failures in max/min enumerable tests)

v5.12.6 (Feb 5): String Method Intrinsics:

  • 5 intrinsics implemented in C bootstrap: downcase, upcase, trim, replace, str_join
  • 12 new tests added in spec/integration/strings_spec.rb
  • Bootstrap files modified: typecheck.c, mir.c, mir_codegen.c, codegen.c

v5.12.5 (Feb 4): Dogfooding Phase 2 — Collection Literals:

  • 74 patterns converted from vec_new() + .push() to [a, b, c] literals
  • Files converted: typecheck.qz (18), mir.qz (15), macro_expand.qz (12), hir.qz (12), ast.qz (8), infer.qz (6), resolver.qz (3)
  • 2 dynamic patterns correctly skipped (loop-based building in quartz.qz, ast.qz)
  • All 1878 tests pass (2 pre-existing failures, 40 pending)

v5.12.4 (Feb 4): Dogfooding Phase 1 — Postfix Guards:

  • 566 patterns converted from if X then return Y end to return Y if X
  • ~1,100 lines saved across the compiler codebase
  • Files converted: mir.qz (264), typecheck.qz (96), infer.qz (79), codegen.qz (37), parser.qz (35), types.qz (33), resolver.qz (11), typeenv.qz (8), quartz.qz (3)
  • 83 patterns remain (legitimate multi-statement blocks)
  • All 1878 tests pass

v5.12.3 (Feb 4): UFCS Migration Complete + Self-Hosted Primary:

  • Phase E Complete: All vec_push, vec_len, vec_get calls converted to UFCS (.push(), .size, [])
  • Phase F Cancelled: Legacy intrinsics are internal IR ops, not removable (UFCS desugars into them)
  • Philosophy Shift: Self-hosted is primary; bootstrap frozen as escape hatch
  • as_int_generic<T>(value: T): Int: Generic type erasure builtin (any→Int, identity at runtime)
  • as_type<T>(value: Int): T: Type recovery with explicit type argument
  • 9 new tests in spec/integration/as_int_as_type_spec.rb
  • All 1878 tests pass (2 pre-existing min/max failures unrelated)

v5.12.2 (Feb 4): InferStorage structification + UFCS verification:

  • InferStorage struct (infer.qz): 7 fields (kinds, names, elem_types, param_types, return_types, next_type_var, bindings)
  • 47 storage[N] usages converted to named field access
  • 20+ function signatures updated to use InferStorage type
  • UFCS verified: Both compilers correctly type vec_new() as Vec, enabling .push() and .size on locals
  • All 1869 tests pass

v5.12.1 (Feb 4): Major structification sprint:

  • TypecheckState struct (typecheck.qz): 40 fields, 77 functions, 94 tc[N] replacements
  • MAX_PARAMS: 32 → 64 across C bootstrap
  • InferEnv struct (infer.qz): 2 fields (names, types)
  • CodegenState struct (codegen.qz): 10 fields
  • Error codes: BE0xxx → QZ0xxx across C bootstrap and specs

v5.12.0 (Feb 4): UFCS transformation + C bootstrap improvements:

  • C bootstrap parser: Index-dot chaining (x[i].method(), x[i].property) — full postfix continuation loop
  • C bootstrap typechecker: UFCS rewrite-before-lookup — mangled name table runs before builtin lookup, not after
  • C bootstrap typechecker: Full UFCS method support for Vec (push/pop/clear/free/slice), HashMap (get/set/del/free/size), StringBuilder (append/to_string/free/size)
  • C bootstrap typechecker: Property access for Vec (.pop, .clear, .free), HashMap (.keys, .values, .free), StringBuilder (.to_string, .size, .free)
  • Self-hosted compiler: ~2,100 vec_*/hm_*/sb_* calls → UFCS/index syntax across 14 files
  • Self-hosted compiler: Accessor return types fixed: ps_get_types/lexemes/lines/cols/nodes and mir_ctx_get_loop_stack/bindings/string_vars now return Vec instead of Int
  • ~78 calls on Int-typed receivers (4 remaining old-style pseudo-structs) kept as function-call syntax
  • 7 new spec tests for index chaining, Vec/HashMap/StringBuilder UFCS
  • All 1869 tests pass
  • NEXT: Structify remaining 4 pseudo-struct containers (TypecheckState, CodegenState, InferStorage, InferEnv)

v5.11.0 (Feb 4): Structification of compiler internals — replaced all Vec-based pseudo-structs:

  • LexerResult struct (lexer.qz): 4 named fields replace r[0]..r[3] magic indices
  • MirProgram struct (mir.qz): 6 fields replace prog[0]..prog[5]
  • MirFunc struct (mir.qz): 11 fields replace func[0]..func[10]
  • MirBlock struct (mir.qz): 7 fields replace block[0]..block[6]
  • MirInstr struct (mir.qz): 11 fields replace instr[0]..instr[10]
  • TypeStorage struct (types.qz): 7 named fields replace s[0]..s[6]
  • AstStorage Vec fields typed: 12 bare VecVec<Int>
  • ~60 magic index patterns eliminated, ~80 accessor functions updated
  • All 1862 tests pass, fixpoint maintained

v5.10.4 (Feb 3): Naivety audit — identified 11 builtins with type erasure issues:

  • Updated 77 function signatures from Int to Vec across ast.qz, resolver.qz, mir.qz, codegen.qz
  • Confirmed .push() UFCS works via aliased type prefixed lookup (no C bootstrap changes needed)
  • MirContext.program and MirContext.all_funcs now Vec type
  • mir_new_program/mir_new_function/mir_new_block/mir_new_instr now return Vec
  • All mir_func_, mir_block_, mir_instr_* getters/setters updated to use Vec params
  • Created scripts/apply_vec_types.rb for reproducible migration
  • Remaining: 2121 vec_* calls (299 vec_len, 1227 vec_push, 558 vec_get, 37 vec_set)

v5.10.2 (Feb 3): Struct field Vec typing for UFCS:

  • Updated AstStorage: 12 fields from Int to Vec type
  • Updated MirContext: 8 Vec fields from Int to Vec type
  • Updated TypeEnv: 11 fields from Int to Vec type
  • Updated ParserState: 5 fields from Int to Vec type, create_parser signature
  • Fixed ufcs_vec_len.rb transform script to handle method-style calls
  • UFCS now works: storage.children.size correctly rewrites to vec_len(storage.children)

v5.10.1 (Feb 3): Fix for cross-module struct field codegen bug:

  • Fixed AstStorage accessor functions in ast.qz that still used Vec-based indexing after struct refactor
  • Fixed mir_collect_enums/mir_collect_structs to use typed ast$AstStorage parameter instead of Int
  • Root cause: When AstStorage was converted from Vec to typed struct, several accessor functions were not updated

v5.10.0 (Feb 3): Three major features:

  1. Self-hosted macro expansion: TOK_DOLLAR lexing, NODE_MACRO_CALL parsing, full AST-walking expander for $try/$unwrap/$assert/$debug in macro_expand.qz, wired into pipeline between parse and resolve
  2. Transitive closure capture fix: C bootstrap collect_captures() now handles nested lambdas — inner lambda captures propagated to outer lambda. Added NODE_LAMBDA/FIELD_ACCESS/MATCH/MATCH_ARM/ASSIGN/FOR/WHILE cases. 6 new closure edge-case tests
  3. Closure-as-Fn return type unification: Fixed return type checking inside lambda bodies — return statements now checked against lambda’s inferred return type, not enclosing function’s declared return type. Enables make_adder_chain and similar HOF patterns.

v5.9.9 Cleanup (Feb 3): Three architectural fixes applied:

  1. panic() output moved from stdout to stderr (write(2,…) instead of @puts)
  2. Block expressions added to parser (do…end and {} forms)
  3. $debug upgraded to expression-returning with stderr output (Rust dbg!() pattern)

TYPE SYSTEM OVERHAUL (v5.4 Series)

Goal: Transform Quartz from “everything is Int” to a world-class type system with zero runtime overhead.

Insight: The current “existential types” model conflates compile-time type information with runtime representation. We keep i64 runtime semantics but add rich compile-time type tracking.

The Problem (Feb 1, 2026)

Current state: Function parameters like def foo(v: Int) lose semantic meaning even when v is a vector.

  • vec_len(v) works (intrinsic function taking Int)
  • v.size fails (“Int has no property ‘size’”)

Root cause: Type erasure at the signature level. The type checker can’t distinguish:

  • Actual integers (numeric values)
  • Vector handles (Vec[T])
  • Map handles (Map[K,V])
  • Struct pointers
  • Function pointers

The Solution

Keep: i64 runtime representation (zero overhead) Add: Semantic type tracking at compile time

# Before (everything is Int)
def resolve_is_loaded(loaded: Int, name: String): Int

# After (semantic types, same runtime)
def resolve_is_loaded(loaded: Vec[String], name: String): Bool

Implementation Phases

Phase 0: Foundation — Type Representation (C Bootstrap) ✓ COMPLETE

Goal: Add internal representation for parameterized types without changing user syntax yet.

StepFileChangeStatus
0.1types.hAdd vec/hashmap union data structs for element types✓ Done
0.2types.cAdd type_vec() and type_hashmap() constructors with interning✓ Done
0.3types.cUpdate type_name() to display Vec<T> and HashMap<K,V>✓ Done
0.4types.cUpdate types_equal_deep() for parameterized comparison✓ Done
0.5typecheck.ctc_parse_type_annotation creates Vec<T> and HashMap<K,V> types✓ Done
0.6typecheck.cMethod resolution uses kind == TY_VEC not pointer equality✓ Done
0.7typecheck.cError messages use type_name() for parameterized display✓ Done

Test: def foo(v: Vec<Int>): Int compiles and v.size resolves to vec_len(v). ✓ PASSING

Known Issue: .is_empty property rewrite creates NODE_BINARY that needs recursive typecheck. Deferred.

Phase 1: Vec[T] Full Support (C Bootstrap) ✓ COMPLETE

Goal: Vec[T] works everywhere — parameters, locals, returns, struct fields.

StepFileChangeStatus
1.1typecheck.cVec[T] in return type position✓ Done
1.2typecheck.cVec[T] in struct field types✓ Done
1.3typecheck.cType inference from vec_new() yields Vec[Unknown], from [1,2,3] yields Vec[Int]✓ Done
1.4typecheck.cMethod resolution: v.push(x)vec_push(v, x) when v: Vec[T]✓ Done
1.5typecheck.cIndex syntax: v[i]vec_get(v, i), v[i] = xvec_set(v, i, x)✓ Done
1.6mir_codegen.cCodegen unchanged — types erase to i64 ops✓ Verified

Test suite: 20+ new specs for Vec[T] in all positions.

Phase 2: Map[K,V] Full Support (C Bootstrap) ✓ COMPLETE

Goal: Map[K,V] (or HashMap[K,V]) works everywhere.

StepFileChangeStatus
2.1typecheck.cMap[K,V] in all positions (params, returns, fields)✓ Done
2.2typecheck.cType inference from {:} yields Map[Unknown,Unknown], from {"a": 1} yields Map[String,Int]✓ Done
2.3typecheck.cMethod resolution: m.get(k)hashmap_get(m, k)✓ Done
2.4typecheck.cIndex syntax: m["key"]hashmap_get(m, "key")✓ Done

Phase 3: Ptr[T] and Function Types (C Bootstrap) ✓ COMPLETE

Goal: Typed pointers and function types for FFI safety.

StepFileChangeStatus
3.1types.h/cPtr[T] type representation✓ Done
3.2typecheck.cPtr[T] prevents mixing incompatible pointer types✓ Done
3.3types.h/cFn(Args): Ret function type representation✓ Done (pre-existing)
3.4typecheck.cLambda/closure type checking with function types✓ Done (pre-existing)

Test suite: 6 specs for Ptr[T], 9 specs for Fn types (params, returns, struct fields, lambdas, closures).

Phase 4: Self-Hosted Compiler Migration ✓ COMPLETE

Goal: Port all type system changes to self-hosted compiler.

StepFileChangeStatus
4.1middle/types.qzAdd parameterized type kinds✓ Done
4.2middle/typecheck.qzPort tc_parse_type_annotation changes✓ Done
4.3middle/typecheck.qzPort method resolution✓ Not needed (ptr intrinsics)
4.4backend/mir.qzPort type-aware lowering✓ Not needed (i64 at runtime)
4.5Verify fixpointSelf-hosted compiles itself✓ Done

Phase 4.5: Type Safety for Parameterized Types (C Bootstrap) ✓ COMPLETE (Feb 1, 2026)

Goal: Enforce type safety for Vec, HashMap<K,V> element types.

Bug discovered: types_match() in types.h treated all i64 types as compatible, allowing Vec<Int> to be passed where Vec<String> was expected. Same hole for HashMap<K,V>.

StepFileChangeStatus
4.5.1types.hAdd Vec element type checking in types_match()✓ Done
4.5.2types.hAdd HashMap<K,V> key/value type checking in types_match()✓ Done
4.5.3types.hPtr already had pointee type checking (Phase 3)✓ Done
4.5.4Test suite11 new specs in spec/integration/types/parameterized/✓ Done
4.5.5Self-hostedPort to self-hosted (architecture refactor)✓ Done

Test results: 11 new parameterized type safety tests pass. Fixpoint maintained (1778 tests).

Self-hosted porting: Added tc_types_match_with_annotations() with parallel vector for type annotations (slot 39). Assignment checking now uses target annotation for parameterized type comparison. Infrastructure in place for full element type tracking.

Phase 4.5.5: Self-Hosted Type Safety ✓ COMPLETE (Feb 1, 2026)

  • Added tc_extract_element_type(), tc_annotations_match(), tc_is_parameterized_type()
  • Added tc_types_match_with_annotations() for full parameterized type checking
  • Added slot 39 for binding_type_annotations in scope system
  • Updated tc_check_let and assign checking to use annotation-aware matching
  • Matches C bootstrap behavior: Vec vs Vec now rejected

Phase 5: Compiler Codebase Migration ✓ IN PROGRESS (Feb 1, 2026)

Goal: Update all compiler source to use proper types.

StepFilesChangeStatus
5.1All .qz filesChange v: Int to v: Vec<T> where semanticDone
5.2All .qz filesChange m: Int to m: HashMap<K,V> where semanticDone (no public APIs return HashMap)
5.3All .qz filesConvert vec_len(v)v.size, vec_push(v, x)v.push(x)84% done - 180 handle-pattern calls remain
5.4All .qz filesConvert hashmap_get(m, k)m.get(k) or m[k]Pending
5.5VerifyAll tests pass, fixpoint maintainedOngoing

Phase 5.1-5.2 Summary (Feb 1, 2026):

  • Updated 38 function signatures across 4 files (typecheck.qz, mir.qz, lexer.qz, resolver.qz)
  • Key functions now use Vec<String>, Vec<Int>, Vec<Vec<Int>> return types
  • Fixpoint maintained: 1778 tests, 6 pre-existing failures unchanged
  • HashMap only used internally for builtins registry - no public API changes needed

Phase 5.3 Progress (Feb 1, 2026): Migrated 10 vec_len(v)v.size calls in typed contexts:

  • mir.qz: 8 sites (drop_types, struct_registry, struct_types, enum_names, typed params)
  • typecheck.qz: 2 sites (param_names, captures with Vec params)

Phase 5.3 Temporary Blocker: Most vec_* calls (~1400 total) currently operate on Int handles rather than typed Vec<T>. The codebase stores vectors as Int handles in context slots (e.g., ctx = vec_get(state, 5)). UFCS methods like .size only work on typed Vec<T>, not on Int handles.

This is temporary. Full migration to typed Vec is required for world-class type safety.

Phase 5.6: Internal Storage Refactor (Required) — IN PROGRESS

Convert compiler internals from Int handles to typed Vec<T> storage:

StepScopeChangeStatus
5.6.1MIR contextRefactor mir_ctx slots to typed fieldsComplete
5.6.2Type envRefactor typeenv slots to typed fieldsComplete
5.6.3AST storageConvert AST storage to AstStorage structComplete
5.6.4Parser stateConvert parser state to typed storageComplete
5.6.5aVec field typesUpdate struct fields from Int to VecComplete
5.6.5b-typesFunction signaturesUpdate 77 Int→Vec function signaturesComplete
5.6.6StructificationReplace Vec pseudo-structs with proper structsComplete
5.6.6aLexerResultlexer.qz: struct with 4 named fieldsComplete
5.6.6bMIR structsmir.qz: MirProgram, MirFunc, MirBlock, MirInstr (4 structs)Complete
5.6.6cTypeStoragetypes.qz: struct with 7 named fieldsComplete
5.6.6dAstStorage typingast.qz: 12 Vec fields → VecComplete
5.6.7-xformTransform vec_lenConvert vec_len() to .size (~299 calls)COMPLETE
5.6.7c-xformTransform vec_pushConvert vec_push() to .push() (~1227 calls)COMPLETE
5.6.7dTransform indexingConvert vec_get/vec_set to index syntax (~595 calls)COMPLETE
5.6.8Structify remainingStructify 4 remaining pseudo-struct containersComplete
5.6.8aTypecheckStatetypecheck.qz: 40 fields → TypecheckState structComplete
5.6.8bCodegenStatecodegen.qz: 10 fields → CodegenState structComplete
5.6.8cInferStorageinfer.qz: 7 fields → InferStorage structComplete
5.6.8dInferEnvinfer.qz: 2 fields → InferEnv structComplete
5.6.9UFCS cleanupHandle patterns categorized; convertible calls doneCOMPLETE
5.6.10Error code renameBE0xxx → QZ0xxx across both compilersComplete
5.6.11Increase MAX_PARAMS32 → 64 fields per structComplete

Phase 5.6.5a COMPLETE (Feb 3, 2026):

UFCS migration unblocked! Struct fields now use proper Vec type instead of Int handles.

Problem solved: The C bootstrap’s UFCS rewriting (e.g., x.sizevec_len(x)) requires the receiver to have type kind TY_VEC. Previously, struct fields like AstStorage.children were typed as Int (holding Vec handles), so UFCS methods like .size couldn’t be used on them.

Changes made:

  1. AstStorage (ast.qz:193-206): All 12 fields changed from Int to Vec

    • kinds, lines, cols, int_vals, str1s, str2s, ops, lefts, rights, extras, children, docs
  2. MirContext (mir.qz:87-113): 8 Vec fields changed from Int to Vec

    • bindings, string_vars, struct_types, struct_registry, drop_types, loop_stack, droppable_stack, defer_stack
    • Scalar Int fields kept as Int: program, func, block, all_funcs, enum_names, etc.
  3. TypeEnv (typeenv.qz:14-29): 11 fields changed from Int to Vec

    • binding_names, binding_types, binding_depths, binding_mutables
    • struct_names, struct_types, enum_names, enum_types
    • func_names, func_types, scope_stack
  4. ParserState (parser.qz:49-64): 5 fields changed from Int to Vec

    • types, lexemes, lines, cols, nodes
    • Updated create_parser() signature to accept Vec parameters
  5. Transform script (scripts/ufcs_vec_len.rb): Fixed to handle both styles

    • vec_len(x)x.size (function style)
    • x.vec_len()x.size (method style, was producing x..size)

Verified working:

var storage = ast_new_storage()
print_int(storage.children.size)  # Outputs: 0
vec_push(storage.children, 100)
print_int(storage.children.size)  # Outputs: 1

Remaining work for full UFCS migration (Phase 5.6.5b-d): The scripts/ufcs_vec_len.rb transform is ready but blocked on function return types. Many functions return Int but actually return Vec handles (e.g., resolve_imports(): Int). To complete the migration:

  1. Change function return types from Int to Vec where appropriate
  2. Run the transform script
  3. Add .push() UFCS rewriting to C bootstrap (currently only .size/.length/.is_empty)

See HANDOFF-UFCS-READY.md for detailed continuation guide.

Next: Phase 5.6.3 — AST storage redesign (see handoff plan)

Phase 5.6.4 Complete (Feb 3, 2026):

ParserState refactored from Int-indexed vector slots to typed struct with 14 fields:

  • Created struct ParserState with typed fields for all parser state
  • Updated create_parser() to return struct literal
  • Converted 80 ps: Int parameters to ps: ParserState
  • Converted all ps[N] index accessors to ps.field_name
  • String fields (error_msg, pending_doc) now native String type (no as_int/as_string)
  • All 1858 tests pass, fixpoint validated

New ParserState struct fields:

types, lexemes, lines, cols, count, pos, nodes,
had_error, error_msg (String), error_line, error_col,
ast_storage, pending_doc (String), in_private_section

Next: Phase 5.6.3 — AST storage redesign (see handoff plan)

Phase 5.6.6: Bidirectional Type Inference for Builtins — FIXED (Feb 5, 2026)

Goal: Fix the root cause of UFCS transform blockers — local variables from builtin calls get proper types.

The Problem:

var v = vec_new()   # v gets type Int (wrong!)
v.push(1)           # ERROR: Int has no method 'push'

Root Cause Analysis:

  • typecheck.c registers builtins with proper return types (e.g., vec_new → t_vec)
  • mir.c:resolve_expr_type() only queries FuncBinding (user-defined functions)
  • Builtins have no FuncBinding entry, so resolve_expr_type() returns NULL
  • push_var_binding() never called for builtin-initialized variables
  • Result: var v = vec_new() has no type binding in MIR → defaults to Int

The Fix (mir.c only): Added builtin return type lookup to resolve_expr_type(). When lookup_func_binding() returns NULL, checks if the function name is a known constructor builtin and returns its return type.

Implementation Status:

PhaseScopeBuiltinsStatus
Phase 1Types exist, MIR loses themvec_new, hashmap_new, string_new, array_new, io_newFIXED
Phase 2MIR type strings addedsb_new, set_new, intmap_new, arena_new, pool_new, arena_pool_new, channel_new, mutex_new, atomic_newFIXED (MIR only)
Phase 3Full UFCS supportAdd TY_* enums for Phase 2 typesPending

Phase 1 (Complete): These builtins already have TY_* enum values and t_* type globals in typecheck.c:

  • vec_newt_vec (TY_VEC)
  • hashmap_newt_hashmap (TY_HASHMAP)
  • sb_newt_stringbuilder (TY_STRINGBUILDER)

Fix: Add static lookup table in mir.c:resolve_expr_type():

if (strcmp(func_name, "vec_new") == 0) return "Vec";
if (strcmp(func_name, "hashmap_new") == 0) return "HashMap";
if (strcmp(func_name, "sb_new") == 0) return "StringBuilder";

Phase 2 (Complete - MIR only): Added type string returns to mir.c:resolve_expr_type() for builtin constructors. These enable type propagation through MIR but don’t yet enable full UFCS (needs TY_* enums):

  • sb_new → “StringBuilder”, set_new → “Set”, intmap_new → “IntMap”
  • arena_new → “Arena”, pool_new → “Pool”, arena_pool_new → “ArenaPool”
  • channel_new → “Channel”, mutex_new → “Mutex”, atomic_new → “Atomic”

Phase 3 (Later): These need TY_* enums added to types.h and t_* globals added to typecheck.c for full UFCS:

  • set_new → needs TY_SET
  • intmap_new → needs TY_INTMAP
  • arena_new → needs TY_ARENA
  • pool_new → needs TY_POOL
  • arena_pool_new → needs TY_ARENAPOOL
  • channel_new → needs TY_CHANNEL
  • mutex_new → needs TY_MUTEX
  • atomic_new → needs TY_ATOMIC

Unblocks: 2,121 UFCS transforms (299 vec_len→.size, 1227 vec_push→.push(), 595 vec_get/set→[])

Test: var v = vec_new(); v.push(1) should typecheck and run.

Phase 5.7: Unified Collection API (Feb 1, 2026)

Goal: Consistent trait-like interfaces across ALL collection types. See UNIFIED_API.md.

Design Principles:

  • Property-style access for zero-argument getters (.size, not .size())
  • UFCS desugaring to existing intrinsics (zero runtime overhead)
  • Rename to unified (deprecate prefixed functions like str_len.size)
StepProtocolTypesStatus
5.7.1SizedString, HashMap, Set, Array, StringBuilderComplete
5.7.2Sized.is_empty synthesized for all Sized typesComplete
5.7.3PushableStringBuilder (.pushsb_append)Complete
5.7.4ClearableVec, HashMap, Set, StringBuilderComplete
5.7.5DeletableHashMap (.delete), Set (.delete)Complete
5.7.6ContainsSet (.contains), String (.contains)Complete (String returns Bool)
5.7.7ContainsHashMap (.has)Complete
5.7.8String methods.find, .slice, .starts_with, .ends_withComplete
5.7.9String methods.downcase, .upcase, .trim, .replace, str_joinComplete
5.7.10Conversion.to_i() (String), .to_s() (Int)Already exists
5.7.11HashMap methods.keys(), .values()Complete
5.7.12Self-hostedPort all changes to typecheck.qzComplete
5.7.13DeprecationAdd warnings for prefixed functionsFuture

Already Complete (from earlier phases):

  • Vec: .size, .push(x), .pop(), v[i], v[i] = x
  • HashMap: .get(k) / m[k], .set(k, v) / m[k] = v

Phase 5.8: Predicate & Unwrap Operators (Future)

Goal: Add ?/! suffix operators for predicates and force-unwrap, plus C-style truthiness.

Current State:

  • nil exists as a value
  • ?? nil coalescing works
  • ?. safe navigation works
  • No force-unwrap exists yet
  • No predicate suffix (using verbose is_ prefix)
  • Verbose bool checks: if str_eq(a, b) == 1 instead of if str_eq(a, b)

Proposed Syntax:

SyntaxMeaningExample
foo?Predicate methods.empty? → Bool
foo!Force unwrapmaybe! → crash if nil
foo!()Mutating methods.upcase!() → mutates in place
a ?? bNil coalescemaybe ?? default
if conditionC-style truthinessnon-zero = true, zero/nil = false
a == bString equality== on strings calls str_eq internally

Lexical Rule: Require spaces around ?? operator:

value = maybe ?? default    # Valid
value = maybe??default      # ERROR: spaces required around ??

# This makes ? suffix unambiguous and readable
result = s.empty? ?? false  # Clear: predicate, then coalesce

Implementation Steps:

StepDescriptionStatus
5.8.0C-style truthiness: any type in if/while conditionsComplete
5.8.1String ==, !=, + operators (already in mir.c)Complete
5.8.2Lexer: ! allowed as final char in identifiersComplete
5.8.2.1Codegen: sanitize !X in LLVM IR namesComplete
5.8.2.2Lexer: ? in identifiersREJECTED — conflicts with ? try-unwrap operator
5.8.3UFCS predicate rewriting: v.empty?vec_len(v) == 0✓ COMPLETE
5.8.4Result ok?/err? + char predicates digit?/alpha?/alnum?/whitespace?✓ COMPLETE
5.8.5some?/none? predicates via UFCS rewrite to is_some/is_none✓ COMPLETE
5.8.6Parser: expr! as force-unwrap expressionSUPERSEDED by macro system
5.8.7Typecheck: unwrap Option → T, error if not OptionSUPERSEDED by $unwrap macro
5.8.8Codegen: emit nil check + panic on NoneSUPERSEDED by $unwrap macro
5.8.9Lexer: Require spaces around ??✓ COMPLETE
5.8.10! suffix in identifiers implies var (mutable binding)APPROVED
5.8.11? suffix in identifiers for predicates (empty?, valid?)APPROVED

Phase 5.9: Macro System (v5.5 Series) — IN PROGRESS

Goal: World-class hygienic macro system enabling $try, $unwrap, DSLs, and code generation.

See: docs/macroplan.md for full design document.

Key Decisions:

  • Macro invocation: $macro(args) — dollar prefix, distinct from functions
  • String templates for simple macros, quote/unquote for AST manipulation
  • Default hygienic with var! escape hatch
  • Export/import unified with existing module system

Implementation Phases:

PhaseDescriptionStatus
5.9.1AST nodes: NODE_MACRO_DEF, NODE_MACRO_CALL, NODE_QUOTE, NODE_UNQUOTE✓ Complete
5.9.2Parser: macro name(args) do ... end and $macro(args)✓ Complete
5.9.3Macro registry and basic expansion pass✓ Complete
5.9.4Quote/unquote semantics✓ Complete
5.9.5Hygiene: gensym, scope tracking, var! escape✓ Complete
5.9.6String template macros (simple form)✓ Complete
5.9.7Variadic arguments (args...)✓ Complete
5.9.8--expand-macros debug flag✓ Complete
5.9.9Built-in macros: $try, $unwrap, $assert, $debug✓ Complete (C bootstrap)
5.9.10Remove ?/! operators, allow in identifiersSUPERSEDED — see note
5.9.11! suffix implies var in typecheckSUPERSEDED — see note

Phase 5.9.10-11 Design Decision (Feb 5, 2026):

After analysis, we chose NOT to remove ?/! operators because:

  1. The macro-based approach ($try, $unwrap) provides equivalent functionality
  2. Predicates (empty?, some?, none?) already work via UFCS rewriting at typecheck level
  3. Removing operators would be a breaking change with minimal benefit

How predicates work now: v.empty? tokenizes as [v][.][empty][?] and the typechecker rewrites it to (vec_len(v) == 0) for Vec types. This gives us Ruby-style predicates without allowing ? in identifiers (which would conflict with expr? try-unwrap).

Phase 5.9.1-5.9.2 Implementation Notes (Feb 2, 2026):

  • Added TOK_MACRO, TOK_QUOTE, TOK_UNQUOTE, TOK_UNQUOTE_EACH to lexer
  • Added NODE_MACRO_DEF, NODE_MACRO_CALL, NODE_QUOTE, NODE_UNQUOTE, NODE_UNQUOTE_EACH to AST
  • Parser: parse_macro_def(), parse_macro_call(), parse_quote(), parse_unquote(), parse_unquote_each()
  • AST printing: Full support for macro nodes in --dump-ast
  • Keyword collision fix: Renamed var quote to var dquote in codegen.qz:3881
  • 8 new spec tests in spec/integration/macro_parsing_spec.rb
  • Fixpoint verified: C bootstrap compiles self-hosted successfully

Phase 5.9.3, 5.9.6, 5.9.8 Implementation Notes (Feb 2, 2026):

  • Created macro.h and macro.c in quartz-bootstrap/src/
  • MacroRegistry: stores macro definitions by name
  • String template expansion: #{param} substitution → re-parse
  • Escape processing: \n → newline, \t → tab in templates
  • Added parse_single_expr() and parse_single_stmt() to parser.c
  • Expansion integrated in main.c: post-parse, pre-resolve_imports
  • --expand-macros flag shows expansion debug output
  • ast_to_source() converts AST back to text for template substitution
  • Test file: spec/fixtures/macros/simple_max.qz
  • All 8 macro tests passing, fixpoint verified

Phase 5.9.4 Quote/Unquote Implementation Notes (Feb 2, 2026):

  • Implemented clone_with_unquote() in macro.c: deep AST cloner with unquote substitution
  • Fixed parse_unquote() to require parentheses around argument (was eating entire expression)
  • Wired TOK_UNQUOTE and TOK_UNQUOTE_EACH into parse_factor for use inside quote blocks
  • Added forward declarations for parse_unquote() and parse_unquote_each() in parser.c
  • Quote-based macro example: macro double(x) do quote do unquote(x) + unquote(x) end end
  • UnquoteContext binds parameters to argument ASTs during clone
  • All 10 macro tests passing (8 original + 2 new quote/unquote tests)
  • Fixpoint verified: C bootstrap compiles self-hosted (185k lines IR)

Phase 5.9.5 Hygiene Implementation Notes (Feb 2, 2026):

  • Added HygieneContext struct to track renames during macro expansion
  • hygiene_add_rename(): Creates gensym’d name (__name_N) and stores mapping
  • hygiene_lookup_rename(): Finds renamed binding by original name
  • Modified clone_with_unquote() to rename bindings:
    • NODE_LET: Binding name renamed to gensym’d identifier
    • NODE_FOR: Loop variable renamed to gensym’d identifier
    • NODE_ASSIGN: Assignment target updated if binding was renamed
    • NODE_IDENT: Reference updated to renamed identifier if applicable
  • Key hygiene behavior: unquote() expressions use is_hygienic=false
    • User-provided code is NOT renamed, preserving user’s variable references
    • This prevents macro bindings from accidentally capturing user variables
  • var! escape hatch: Design documented, deferred (needed for intentional capture)
  • All 12 macro tests passing (10 original + 2 new hygiene tests)
  • Fixpoint verified: C bootstrap compiles self-hosted

Phase 5.9.7 Variadic Arguments Implementation Notes (Feb 2, 2026):

  • Added is_variadic and variadic_param fields to MacroDef struct and AST node
  • Parser: parse_macro_def() detects param... syntax (TOK_DOTDOTDOT after identifier)
  • Variadic param must be last in definition (error QZ0206 otherwise)
  • Expansion: Variadic args bound as NODE_ARRAY in UnquoteContext
  • find_param_index() returns VARIADIC_PARAM_INDEX (-2) for variadic param lookup
  • expand_quote_block() creates array node with remaining args for variadic param
  • 5 new tests in spec/integration/macro_parsing_spec.rb
  • Fixpoint verified: C bootstrap compiles self-hosted successfully

Phase 5.9.9 Built-in Macros Implementation Notes (Feb 2, 2026):

  • Four built-in macros: $try, $unwrap, $assert, $debug
  • Implemented in C bootstrap only (self-hosted compiler lacks macro expansion phase)
  • is_builtin_macro() and expand_builtin_macro() in macro.c intercept before user macro lookup
  • $try(expr) → match with Some/None arms using gensym binding (__try_v_N)
  • $unwrap(expr) → match with Some/panic arms, includes source line in panic message
  • $assert(cond)if !(cond) then { panic("assertion failed at line N"); 0 } end
  • $debug(expr)do __v = expr; eputs("[debug] line N: "); print_int(__v); eputs("\n"); __v end (expression-returning, stderr)
  • Parser fix: parse_macro_call() now accepts TOK_TRY after $ (keyword as macro name)
  • expand_macros() no longer short-circuits when no user macros defined (built-ins always active)
  • Type system fix: Added NODE_RETURN case to typecheck_expr() returning TY_NEVER (both compilers)
    • Previously return statements in expression context (e.g. match arm bodies) typed as Void
    • Now correctly typed as Never (bottom type), compatible with any expected type via types_match
    • This enables match arms like None => return Option::None alongside value-producing arms
  • 21 new tests in spec/integration/builtin_macros_spec.rb
  • Fixpoint verified: 1843 tests, 0 failures, 40 pending

Phase 5.9.9 Cleanup (Feb 3, 2026): Three architectural deficiencies fixed:

  1. panic() → stderr: mir_codegen.c panic codegen now uses write(2, ...) instead of @puts(). Both panic sites and unwrap_panic fixed. All diagnostic output goes to fd 2.

  2. Block expressions: Parser (parser.c) now supports blocks as expressions.

    • do...end form: always unambiguous in expression context
    • { } form: parsed as block when first token is a statement keyword (var, if, while, etc.)
    • Both typecheck.c (typecheck_expr NODE_BLOCK) and codegen.c (old codegen validation) updated with block expression support and scope tracking
    • 5 new tests in spec/integration/block_expressions_spec.rb
  3. $debug expression-returning: Rewritten to Rust dbg!() pattern.

    • Uses gensym binding (__dbg_v_N) to evaluate argument exactly once
    • Debug framing ([debug] line N: ) goes to stderr via eputs()
    • Returns the argument value — usable in val x = $debug(expr) or f($debug(a) + $debug(b))
    • 6 new/updated tests for expression semantics
  • Key discovery: codegen.c has its own shadow type system (typecheck_expr/typecheck_stmt) that runs via codegen_validate() before the MIR pipeline. New language features must be supported in BOTH type systems.

  • Fixpoint verified: 1852 tests, 0 failures, 40 pending

Phase 5.10.0 Self-Hosted Macro Expansion (Feb 3, 2026): COMPLETE

  • Full macro infrastructure ported from C bootstrap to self-hosted compiler
  • lexer.qz: Added standalone $ token lexing (char 36 → TOK_DOLLAR before identifier check)
  • ast.qz: Added NodeMacroCall (kind 67) with ast_macro_call(s, name, args, line, col) constructor
  • parser.qz: Added ps_parse_macro_call() — dispatched when TOK_DOLLAR seen in primary expression
  • macro_expand.qz: New file — full AST walker with 4 built-in expansions ($try/$unwrap/$assert/$debug)
    • Uses AST node count as gensym counter (avoids module-level mutable global issues)
    • Module named macro_expand (not macro — that clashes with C bootstrap reserved word)
  • quartz.qz: Wired expand_macros() between parse and resolve in pipeline
  • Self-hosted macro tests pass with C bootstrap (self-hosted typecheck has pre-existing TY_NEVER gaps)

Phase 5.10.0 Transitive Closure Capture Fix (Feb 3, 2026): COMPLETE

  • BUG: C bootstrap collect_captures() had no NODE_LAMBDA case — nested lambdas silently skipped
  • FIX: Added NODE_LAMBDA case that first collects inner captures, then propagates to outer lambda
  • Also added missing cases: NODE_FIELD_ACCESS, NODE_MATCH, NODE_MATCH_ARM, NODE_ASSIGN, NODE_FOR, NODE_WHILE
  • Self-hosted compiler already had correct transitive capture implementation
  • 6 new closure edge-case tests (5 passing, 1 pending for closure-as-Fn return type unification)
  • Fixpoint verified: 1858 tests, 0 failures, 41 pending

Decision: APPROVED — This is the right design.

Design Note: ? and ! in Identifiers (Feb 2, 2026 - REVISED)

After implementing expr! as a force-unwrap operator, we pivoted to a macro-based approach. This allows ? and ! to be used in identifiers for semantic meaning:

# ! suffix = mutable binding (implies var)
count! = 0
count! = count! + 1  # Mutation visible at every use site

# ? suffix = predicate convention (cosmetic)
def empty?(list: List): Bool
  list.size == 0
end

# Macros replace operators
content = $try(read_file(path))   # Early return on None
value = $unwrap(maybe)            # Panic on None

This gives us:

  1. Ruby/Crystal-style predicates (empty?, valid?, nil?)
  2. Self-documenting mutable bindings (no need to grep for var)
  3. Explicit macro invocation ($macro) distinct from functions

Phase 5.2.1: Array Literal Indexing Fix ✓ COMPLETE (Feb 1, 2026)

Bug: arr[i] on array literals caused SIGSEGV in self-hosted compiler.

Root cause: tc_check_index called infer_get_elem_type(storage, TYPE_ARRAY()) where TYPE_ARRAY()=5 is a primitive constant, not a storage index. Out-of-bounds crash when storage had <5 elements.

Fix:

  • typecheck.qz: Return TYPE_INT() directly for bare TYPE_ARRAY() (non-parameterized arrays)
  • infer.qz: Added infer_is_parameterized_type() to distinguish stored types from constants
  • typecheck.qz: Handle TYPE_ARRAY() in v.size desugaring (resolves to vec_len)

Bonus work completed:

  • Added tc_get_type_alias() helper for type-prefixed UFCS resolution
  • Added type-prefixed UFCS: push(v, x)vec_push(v, x) when v: Vec
  • Added string builtins: str_to_lower, str_to_upper, str_trim, str_replace
  • Added intrinsic registrations: str_find, str_cmp

Phase 5.2.2: Function Name Decimal Bug ✓ COMPLETE (Feb 2, 2026)

Bug: Self-hosted compiler emitted memory addresses as function names (e.g., @105553121076864 instead of @foo).

Root cause: In codegen.qz:156, cg_sanitize_llvm_name used sb.to_str(). Quartz method syntax sb.to_str() desugars to to_str(sb). Since sb is typed as Int (StringBuilder pointer), the C bootstrap’s to_str builtin checked arg_type == TYPE_STRING. Finding it was NOT a String, it converted the Int to a decimal string via sprintf.

Fix: Changed return sb.to_str() to return sb_to_string(sb) — using the proper StringBuilder builtin that extracts buffer content.

Lesson learned: In Quartz, x.method() is syntactic sugar for method(x). When a variable is typed as Int but conceptually holds a String pointer, calling .to_str() triggers integer-to-string conversion. Always use explicit builtins like sb_to_string(sb).

Phase 6: Language Naivety Audit (Feb 5, 2026)

Goal: Eliminate naive patterns and hardcoded limits that prevent Quartz from being world-class.

Audit Results: 28 issues identified across 6 categories.

Category 1: Naive Type Parsing (HIGH PRIORITY)
IssueFileProblemStatus
6.1.1typecheck.c:1197-1219Vec len - 5 breaks nested typesFIXED (depth-based)
6.1.2typecheck.c:1152-1289CPtr, Array, Ptr, HashMap use naive len - NALL FIXED (depth-based)
6.1.3types.c:950, 973, 991strrchr finds wrong > for nested typesFIXED (find_matching_close_bracket())
6.1.4typecheck.c, mir.cSmall buffers (64-128 bytes) for type namesFIXED (256-byte MAX_TYPE_NAME)
6.1.5types.c:796Static 256-byte buffer overwritten in recursive callsFIXED (rotating buffers)

Fix Pattern: Use depth-based bracket matching for all generic type parsing:

int depth = 1;
size_t end_pos = PREFIX_LEN;
while (end_pos < len && depth > 0) {
    if (str[end_pos] == '<') depth++;
    else if (str[end_pos] == '>') depth--;
    end_pos++;
}
Category 2: Parser Syntax Gaps (HIGH PRIORITY)
IssueFileProblemStatus
6.2.1parser.cx: Type = expr (const with type annotation) not supportedFIXED

Current State:

  • x = expr — works (immutable, inferred type)
  • var x = expr — works (mutable, inferred type)
  • var x: Type = expr — works (mutable, explicit type)
  • x: Type = exprFIXED (added parser support)

The Fix (parser.c):

  • Added TOK_COLON to the peek check in parse_statement
  • Added branch in parse_assign_or_call to handle x: Type = expr

Note: There is no let keyword. Quartz uses x = expr for const bindings (const-by-default).

Category 3: Hardcoded Limits (MEDIUM PRIORITY) — ✅ ALL FIXED
ConstantValueLocationRisk
MAX_STRUCTS256codegen.c:272, cimport.h:14OK (increased from 64)
MAX_MODULES256resolver.h:6OK (increased from 64)
MAX_MIR_FUNCTIONS2048mir.h:17OK (increased from 1024)
MAX_ENUMS128codegen.c:377, typecheck.h:14OK (increased from 64)
TC_MAX_TRAITS64typecheck.h:15OK (increased from 32)
TC_MAX_IMPLS128typecheck.h:16OK (increased from 64)
MAX_SOURCE_SIZE512KBlexer.h:56May hit for generated code
MAX_CHILDREN2048ast.h:69May hit for generated code
MAX_TYPE_PARAMS8ast.h:72May hit for complex generics

Note: Critical limits for self-hosted compilation now adequate. Dynamic allocation deferred.

Category 4: Type System Incompleteness (MEDIUM PRIORITY)
IssueFileProblemStatus
6.4.1typecheck.c:3998Multi-param enum uses type_args[0] alwaysFIXED (EnumDef.type_param_names)
Fix: Add type_param_names[] to EnumDef, lookup param index
6.4.2typecheck.c:4836Trait impl param types not validatedDEFERRED (to self-hosted)
6.4.3infer.c:151-159occurs_in_impl doesn’t recurse into Array/FuncBY DESIGN (simplified HM)
6.4.4typecheck.c:1099Generic struct instantiation TODON/A (Quartz has no generic structs)
6.4.5typecheck.c:1382No proper TY_NEWTYPE kindDEFERRED (transparent works, self-hosted)
Category 5: Missing Type Cases (LOW PRIORITY)
IssueFileProblemStatus
6.5.1types.c:717-793types_equal_deep missing TY_STRINGBUILDERFIXED (line 735)
6.5.2types.c:892-927type_kind_name missing TY_STRINGBUILDERFIXED (line 960)
Category 6: Incomplete/Unimplemented (LOW PRIORITY)
IssueFileProblemStatus
6.6.1mir_codegen.c:4933Channel select not implementedDEFERRED (advanced concurrency)
6.6.2cimport.c:358@cImport function params not wiredDEFERRED (parse_param_types exists but unused)
Priority Fix Order
  1. Immediate (blocks UFCS migration):

    • 6.1.1: Vec nested type parsing (DONE)
    • 6.1.2-3: Other generic types (CPtr, Array, Ptr, HashMap) — HashMap fixed
  2. Next (common user pain):

    • 6.2.1: const type annotation syntax (x: Type = expr) — FIXED
    • 6.3: Increase MAX_STRUCTS/MAX_MODULES/MAX_ENUMS — FIXED (all now 128-256)
  3. Maintenance pass:

    • 6.1.4-5: Buffer sizes — FIXED (rotating buffers in types.c)
    • 6.4.1: Multi-param enum substitution — FIXED (type_param_names in EnumDef)
    • 6.5.1-2: Missing TY_STRINGBUILDER cases — FIXED
  4. Track for self-hosted (less critical in bootstrap):

    • 6.4.2: DEFERRED, 6.4.3: BY DESIGN, 6.4.4: N/A, 6.4.5: DEFERRED
    • 6.6.1-2: DEFERRED to self-hosted

Type Hierarchy (Target State)

Type
├── Int                    # Actual numeric integer
├── Bool                   # True/false
├── String                 # UTF-8 string
├── Nil                    # Null/None value
├── Vec[T]                 # Vector of T
├── Map[K,V]               # Hash map K→V
├── Set[T]                 # Hash set of T
├── Ptr[T]                 # Raw pointer to T
├── CPtr[T]                # C-compatible pointer
├── Fn[Args, Ret]          # Function type
├── Option[T]              # Some(T) | None
├── Result[T,E]            # Ok(T) | Err(E)
├── Channel[T]             # Typed channel
├── Struct types           # User-defined
└── Enum types             # User-defined

Runtime: All compile to i64 operations. Zero overhead. Compile-time: Full type checking, method resolution, generics.

Success Criteria

  1. Ergonomics: v.size, v.push(x), v[i], m["key"] work on typed parameters
  2. Safety: Can’t accidentally pass Vec[Int] where Vec[String] expected
  3. Zero overhead: Generated LLVM IR identical to current (all i64 ops)
  4. Backward compatible: Old code with Int parameters still compiles
  5. Fixpoint: Self-hosted compiler compiles itself throughout migration

Estimated Timeline

PhaseDurationMilestone
Phase 02-3 daysVec[T] in parameters works
Phase 13-4 daysVec[T] complete in C bootstrap
Phase 22 daysMap[K,V] complete in C bootstrap
Phase 32-3 daysPtr[T], Fn types in C bootstrap
Phase 43-4 daysSelf-hosted port complete
Phase 53-5 daysCompiler codebase migrated

Total: ~2-3 weeks


Development Roadmap (Priority Queue)

Execution order. Complete top-to-bottom. Dogfooding validates all features before stdlib depends on them.

#ItemScopeStatus
1option_result_stdlibPrelude auto-import, ? operator, Option/Result in stdPhase 2 Complete
2asm_variable_binding@c("code", var1, var2) in self-hostedComplete
3Language FeaturesPostfix guards, regex patterns (78 tests)Complete
4DOGFOODINGRefactor self-hosted with all new sugarUnblocked - Ready
5Standard Librarytcp, http, datetime, csv, yaml modulesPending
6ToolingREPL, LSP, package registryLast

option_result_stdlib Design Decisions (Jan 30, 2026)

DecisionChoiceRationale
Error coercionExact match requiredNo From trait complexity. Explicit .map_err() for conversion. Add From later if pain warrants.
Prelude scopeTrue import (shadowable)Compiler injects import prelude at parse time. Not magic—user can shadow.
Domain ResultsMigrate via type aliasestype TomlResult<T> = Result<T, TomlError>. Non-breaking, ? works universally.

Implementation Status (Jan 31, 2026):

  • Phase 1: Prelude auto-import with Option/Result types
    • Created std/prelude.qz with Option, Result<T,E>, and combinators
    • C bootstrap: resolver.c injects from prelude import * when stdlib path configured
    • Self-hosted: resolver.qz mirrors the same logic
    • Fixed wildcard import bug (symbol_count==0 was rewriting calls even for wildcards)
    • 9 new specs in spec/integration/prelude_spec.rb
  • Phase 2: ? operator for early return on None/Err
    • Already implemented in C bootstrap (parser.c, mir.c:2105-2162) and self-hosted (mir.qz:2796-2830)
    • 6 tests in spec/integration/option_result_spec.rb for ? operator
  • Phase 3: Migrate domain Results to type aliases
    • UNBLOCKED (Feb 6, 2026): Cross-module type aliases now work!
    • type TomlResult<T> = Result<T, TomlError> compiles correctly when TomlError is imported
    • DEFERRED: Existing custom enums (TomlResult, JsonResult) work fine and changing them would break existing code patterns
    • Recommendation: Use type alias pattern for NEW modules only

Dogfooding Scope (Jan 30 → TBD)

Fixpoint Restored (Jan 31, 2026): Self-hosted compiler can compile itself again.

  • Root cause: ps_parse_return in frontend/parser.qz was missing checks for TOK_IF/TOK_UNLESS
  • return if condition was being parsed as return (if-expression) instead of bare return with guard
  • Fix: Added TOK_ELSE, TOK_IF, TOK_UNLESS checks before calling ps_parse_expr
  • Now generates 182,286 lines of LLVM IR when compiling itself

Features added since last dogfood (Jan 23, 2026):

CategoryFeatures
Range Expressions0..n (exclusive), 0...n (inclusive)
Collection Literals[1, 2, 3], {:}, {"key": val}, trailing commas
String Sweetnesss.size, s.length, s[i], s.find(), s.to_i, s[a:b] slices
Indexingv[i], v[i] = x, m["key"], m["key"] = x
Method SyntaxUFCS on primitives, extend blocks
Safe Navigationx?.field desugaring fixed
GenericsVec<T> in params, CPtr<T> typed pointers
FFIrepr(C) structs, variadic extern, ptr_cast, 22 items complete
RegexPCRE2, find_all, named groups
Matchdo…end arms, string patterns, guard clauses

Stats jump: 1587→1694 tests, 901→1038 functions

Dogfooding Execution Plan

Phase 1: Low-Hanging Fruit (1-2 days) Goal: Make the codebase more readable with sugar that requires no structural changes.

TransformationBeforeAfterStatus
String methodsstr_len(s)s.sizeComplete
Vec/HashMap sizevec_len(v)v.sizeComplete
Collection literalsvec_new() + vec_push()[1, 2, 3]Pending
Map literalshashmap_new() + hashmap_set(){key: 1, other: 2}Pending
Range loopsvar i = 0; while i < nfor i in 0..nPending
Postfix guardsif x { return }return if xPending
Pipelinef(g(h(x))) (3+ deep)x |> h |> g |> fPending

Phase 1 Progress (Feb 1, 2026):

  • Transformed 60+ str_len() calls to .size in self-hosted codebase
  • Added .size property support to self-hosted typecheck (rewrites to str_len/vec_len/hashmap_size)
  • Added TYPE_VEC()=24 and TYPE_HASHMAP()=25 constants to self-hosted
  • Fixed str_slice semantics bug (uses start,end not start,length)
  • Added Vec .size property handling to C bootstrap
  • Standardized on .size over .len/.length (updated STYLE.md)
  • Updated all specs and docs to use .size
  • NodeKind Alignment - Aligned all 66 NodeKind values in self-hosted to match C bootstrap (quartz-bootstrap/src/ast.h)
  • Fixed tc_is_i64_type - Added TYPE_VEC and TYPE_HASHMAP as i64-compatible types (existential types)
  • Fixed tc_type_name - Added Vec and HashMap to type name display for better error messages
  • Fixpoint Restored - Self-hosted compiler successfully compiles itself (4.5s, 182K lines LLVM IR)

Phase 2: UFCS Method Syntax (2-3 days) Goal: Transform function calls to method syntax using extend blocks.

# Before
var len = vec_len(v)
var item = vec_get(v, 0)
vec_push(v, item)

# After (with extend Vec<T>)
var len = v.size
var item = v[0]
v.push(item)

Key files: frontend/ast.qz, frontend/parser.qz, middle/typecheck.qz, resolver.qz, backend/codegen.qz

Phase 2 Progress (Feb 1, 2026):

  • mir.qz partial transformation (~240 vec_get/vec_set calls converted, vec_len/vec_push need manual review)
  • lexer.qz: 31 calls converted (method-style already)
  • ast_print.qz: 12 calls converted
  • types.qz: 30 calls converted
  • codegen_main.qz: 4 calls converted
  • quartz.qz: 12 calls converted (2 kept as vec_len - Int typed vars)
  • Fixpoint verified after each batch
  • mir.qz remaining: ~240 vec_len/vec_push (need manual review - Int vs Vec confusion)
  • hir.qz, resolver.qz: Need manual review (String vs Vec confusion with .size)
  • Remaining files: ast.qz(910), typecheck.qz(360), codegen.qz(219), infer.qz(202), parser.qz(117)

Lessons learned:

  • Automated transforms work well for vec_get/vec_set (indexing syntax unambiguous)
  • vec_len → .size is risky: some variables are Int handles, not typed Vecs
  • String variables (path, name, source) get .size but should use str_len()
  • Need type context or variable naming heuristics for safe automation

Phase 3: Type Safety with Newtypes (2-3 days) Goal: Replace raw Int handles with newtypes for compile-time safety.

# Before (everything is Int, easy to mix up)
def ast_get_kind(storage: Int, handle: Int): Int

# After
newtype AstStorage = Int
newtype NodeHandle = Int
newtype NodeKind = Int
def ast_get_kind(storage: AstStorage, handle: NodeHandle): NodeKind

Priority newtypes: NodeHandle, AstStorage, NodeKind, TypeId, TypeContext, Token, ParseState, MirContext, MirSlot

Phase 4: Match Expressions & Exhaustiveness (1-2 days) Goal: Replace cascading if-else chains with match expressions.

# Before
if kind == NK_CALL
  handle_call(node)
elsif kind == NK_IF
  handle_if(node)
else
  error("Unknown kind")
end

# After
match kind do
  NK_CALL => handle_call(node)
  NK_IF => handle_if(node)
  _ => error("Unknown kind")
end

Phase 5: Drop Trait for RAII (1-2 days) Goal: Add automatic cleanup for resources.

Priority: ParseState, TypeContext, MirContext, AstStorage

Dogfooding Metrics

MetricCurrentTarget
Lines of code~15,000~12,000 (-20%)
Functions using UFCS0200+
Newtypes for safety015+
Match expressions~50150+
Drop implementations05+

Status Summary

CategoryCompleteIn ProgressPlannedTotal
Core Semantics3025
Module System100111
Type System210021
Syntax210122
Concurrency130013
FFI & C Interop250227
Stdlib701118
Error Handling7007
Memory80210
Enumerable7018
Regex130417
Tooling2046
Documentation0033
Bugfixes120214

Completed

Module System

  • top_level_var — Module-level variable declarations
  • internal_function_calls — Cross-module function resolution
  • enum_refs_imported — Enum variant imports
  • private_section — Visibility modifiers
  • selective_importsimport { a, b } from module
  • aliased_importsimport foo as bar
  • qualified_callsmodule.function() syntax
  • hierarchical_import_prefixing — Nested module paths
  • qualified_typesmodule.Struct {} and module.Enum::Variant syntax [Jan 2026]
  • private_type_enforcement — Private structs/enums rejected from other modules [Jan 2026]

Type System

  • trait_declarationstrait Name { def method(self) }
  • impl_blocksimpl Trait for Type { }
  • trait_constraints<T: Eq>, <T: Eq + Ord>
  • generic_return_types — Generic function returns
  • drop_trait_raii_basic — Basic RAII via Drop trait
  • drop_trait_nested_scopes — Drop in nested blocks (if/while/match)
  • drop_trait_break_continue — Drop on loop exit
  • drop_trait_loop_iteration — Drop per iteration
  • allocator_trait — Custom allocator support
  • generic_traits_parity — C/Quartz compiler parity
  • type_aliasestype Name = ExistingType (transparent synonyms) [Jan 2026]
  • newtype_wrappersnewtype Name = Type (distinct zero-cost types) [Jan 2026]
  • drop_trait_question_operator — Drop on ? early return [Jan 2026]
  • drop_trait_match_arms — Drop at match arm boundaries [Jan 2026]
  • drop_trait_function_return — Drop on normal function return [Jan 2026]
  • drop_trait_nested_structs — Drop for nested struct fields [Jan 2026]
  • match_exhaustiveness — QZ0106 non-exhaustive match warnings [Jan 2026]
  • type_parameterized_intrinsicsvec_get<T>, hashmap_set<V> explicit type args [Jan 2026]
  • generic_param_syntax — Allow Vec<T> in function parameter annotations [Jan 2026]

Syntax

  • regex_literal_lexer/pattern/ lexing
  • regex_literal_parser — Regex AST nodes
  • regex_match_operator=~ operator
  • regex_capture_operator=~> for captures
  • pipeline_operatorx |> f |> g
  • list_comprehensions[expr for x in iter if cond]
  • labeled_loopsbreak :label, continue :label
  • type_dot_methodType.method() associated functions
  • regex_patterns — Pattern matching integration
  • postfix_guardsreturn x if cond, break unless cond [Jan 2026]
  • match_arm_brace_blocks — Multi-statement match arms A => { stmts; expr } [Jan 2026]
  • map_comprehensions{k => v for x in iter} [Jan 2026]
  • string_keyed_map_literals{"key" => value} [Jan 2026]
  • set_literals{1, 2, 3} syntax [Jan 2026]
  • string_method_sweetnesss.size, s[i], s.find(), s.to_i [Jan 2026]
  • int_to_string_sweetnessn.to_s integer to string [Jan 2026]
  • slice_syntaxs[a:b], s[:b], s[a:] string slicing [Jan 2026]
  • collection_literals[] empty Vec, {:} empty HashMap, {"key": val} map literals [Jan 2026]
  • vec_sweetnessv.size for Vec length [Jan 2026]
  • hashmap_sweetnessm.size, m["key"] HashMap access [Jan 2026]

Concurrency

  • thread_spawn_intrinsicthread_spawn(fn)
  • thread_join_intrinsicthread_join(handle)
  • mutex_typeMutex<T> with lock/unlock
  • channel_typeChannel<T> send/receive
  • atomic_intrinsicsatomic_load, atomic_store, atomic_cas
  • atomic_operations — Full atomic ops suite
  • lambda_closure_spawn — Spawn with closures
  • parallel_intrinsicsparallel_map, parallel_for, parallel_reduce
  • parallel_map — Parallel array transformation

FFI & C Interop

  • extern_c_declarationsextern "C" blocks
  • c_escape_hatch — Raw C interop
  • c_struct_layout — C-compatible struct memory layout
  • c_type_aliases — C type mappings
  • repr_c_annotation@repr(C) struct annotation [Jan 2026]

Stdlib

  • enumerable_intrinsicsmap, filter, reduce, etc.
  • regex_string_methodsmatch, replace, split
  • json_module — JSON parse/stringify
  • toml_module — TOML parsing

Regex

  • regex_literal_syntax/pattern/ literals
  • regex_match_operator=~ match operator
  • regex_capture_operator=~> capture operator
  • regex_matches_intrinsicregex_matches iteration [Jan 2026]
  • regex_find_all_intrinsicregex_find_all(str, pattern) returns Vec [Jan 2026]
  • regex_replaceregex_replace(str, pattern, replacement) replaces first match [Jan 2026]
  • regex_replace_allregex_replace_all(str, pattern, replacement) replaces all [Jan 2026]
  • regex_splitregex_split(str, pattern) splits on pattern [Jan 2026]
  • regex_countregex_count(str, pattern) counts occurrences [Jan 2026]
  • regex_find_match_optionfind_match returns Option<Match> [Jan 2026]
  • regex_matches_boolstr.matches(pattern) returns Bool [Jan 2026]
  • regex_match_positions — Match start/end index positions via regex_match_start() / regex_match_end() [Jan 2026]
  • regex_backreferences — Backreferences \1-\9 in native regex engine, =~? now uses native engine [Jan 2026]

Error Handling

  • try_catch_syntaxtry/catch blocks
  • compiler_result_audit — Result<T,E> throughout compiler
  • world_class_errors — Elm-quality error messages with —explain [Jan 2026]
  • error_codes — Numbered error codes (QZ0001, etc.) [Jan 2026]
  • rename_error_codes — Rename QZ0xxx error codes to QZ0xxx (vestigial from BE language prototype)
  • improved_inference_errors — Match arm type consistency checking [Jan 2026]
  • related_span_diagnostics — “first arm has this type” secondary locations [Jan 2026]
  • braced_block_void_type{ x = 1 } correctly infers Void [Jan 2026]

Memory

  • arena_pool_intrinsics — Arena allocation primitives
  • type_safe_pointers — Typed pointer operations
  • arena_type — First-class Arena type with Drop [Jan 2026]
  • implicit_drop_glue — Auto-drop for structs with droppable fields [Jan 2026]
  • defer_statementdefer expr cleanup on function exit [Jan 2026]
  • heap_annotation@heap annotation for heap-allocated arrays/structs [Jan 2026]
  • arena_blocks — Block-based allocation with arena_block_count [Jan 2026]

Enumerable

  • HashMap enumerablehashmap_size, hashmap_keys, hashmap_values
  • HashMap dogfooding — HashMap used in self-hosted compiler [Jan 2026]
  • Set data structureset_new, set_add, set_contains, set_remove
  • file_lines intrinsic — Iterate file by lines
  • file_chars intrinsic — Iterate file by characters
  • file_bytes intrinsic — Iterate file by bytes
  • Channel iteration — Iterate over channel messages
  • regex_matches — Iterate regex matches [Jan 2026]

Tooling

  • dwarf_debug_info--debug flag for DWARF metadata
  • package_manifestquartz.toml package config [Jan 2026] — std/toml/manifest.qz
  • docgen_ref_mdAutomated ref.md from doc comments — Extract @syntax, @example, @gotcha from ## comments in self-hosted compiler; generate ref.md on build; single source of truth; extends to website docs, man pages, IDE hints

Compiler Bugfixes (v5.0)

  • string_escaping_fix — for->while loop fix in unescape
  • array_literal_mir — NodeArray unified layout
  • ast_let_init_slot — Let node initialization

Compiler Bugfixes (v5.1)

  • cross_module_struct_fields — Field access on imported struct types [Jan 2026]
  • two_phase_struct_registration — Struct field type resolution order [Jan 2026]
  • void_match_arm_codegen — Single-line match arms with Void calls don’t execute [Jan 2026]
  • continue_for_in_loopcontinue in for..in loops now advances iterator [Jan 2026]
  • do_end_match_arms — Match arms with do...end blocks [Jan 2026]

Compiler Bugfixes (v5.1.1) — Gemini batch [Jan 27, 2026]

  • match_arm_return_codegenreturn inside match arms stored stale MIRValue instead of body expression
  • to_str_string_double_wrapto_str on String arguments double-wrapped causing crashes

Compiler Bugfixes (v5.2.1) — TOML parser [Jan 28, 2026]

  • toml_parser_match_arm_types — Match arm type mismatches in TOML parser (Void vs Nil/Bool/TomlResult)
    • Fixed by converting match-with-early-return patterns to if-elsif chains
    • Added helper functions: toml_result_is_err, toml_result_is_ok, toml_result_unwrap, toml_error_message
    • TOML test suite: 28 tests, 0 failures

Compiler Bugfixes (v5.2.6) — Native regex backreferences [Jan 30, 2026]

  • regex_backreferences — Backreference support \1-\9 in native Thompson NFA engine
    • Added STATE_BACKREF state type, parsed in parse_escape
    • Handle in addthread() as position-advancing epsilon transition
    • Track thread position (sp field) for correct group 0 end bounds
  • regex_capture_native_engine — Switch =~? operator from POSIX regex to native engine
    • POSIX Extended Regex (ERE) does NOT support backreferences
    • Now calls regex_capture_pattern() with full feature support
    • Fixed phi predecessor bug in capture loop codegen

Compiler Bugfixes (v5.3.8) — hashmap buffer overflow [Jan 31, 2026]

  • hashmap_keys_buffer_overflow — Fixed heap corruption in error suggestions
    • Root cause: hashmap_keys and hashmap_values allocated fixed 16-element buffer
    • Symptom: Crash in edit_distance when “Did you mean?” suggestion ran (156 builtins > 16)
    • Fix: Allocate vector based on hashmap capacity, not fixed 16 elements
    • Files: mir_codegen.c (C bootstrap), codegen.qz (self-hosted)
  • vec_set_bounds_checking — Added bounds checking to vec_set in self-hosted codegen
    • Symptom: Silent heap corruption on out-of-bounds vector writes
    • Fix: Check idx >= 0 AND idx < size before storing, skip if invalid

Compiler Bugfixes (v5.5.7) — I/O Buffering & Enum Constructor [Feb 2, 2026]

  • io_buffering_consistency — All I/O functions now use unbuffered write() syscalls
    • Root cause: print_int() used printf() (stdio buffered) and puts() used C puts() (stdio buffered), while print()/eprint()/eputs() used raw write() (unbuffered). Interleaved output appeared in wrong order.
    • Fix: print_int now uses sprintf to stack buffer + write(). puts now uses strlen + write() + newline.
    • Files: codegen.c (print_int helper), mir_codegen.c (puts intrinsic), codegen.c (legacy puts path)
    • Principle: All output is now predictable — appears in the order you call it, no hidden buffering.
  • enum_multi_payload_constructor — Enum variants with multiple fields now store all payloads
    • Root cause: mir.c enum constructor only allocated 2 words (tag + 1 field) and stored payloads[0]. For Both(a: Int, b: Int), the second field was lost (read as heap garbage).
    • Fix: Allocate 1 + payload_count words, store all payloads in a loop.
    • File: mir.c:3476-3512 — enum variant constructor lowering
    • Impact: Fixed match guards with multiple bindings, all multi-field enum destructuring.
  • test_infra_success_semanticsRunResult.success? now means “exited normally” not “exit code 0”
    • Root cause: Tests using non-zero exit codes to signal results (e.g., return 1 for success) were falsely reported as failures because success? checked exitstatus == 0.
    • Fix: Changed to run_status.exited? — process exited normally, regardless of exit code.
    • File: spec/spec_helper.rb (lli path and clang path)
  • test_vec_get_string — Fixed 6 specs using vec_get() on Vec<String> to use vec_get_string()
    • Files: regex_matches_spec.rb (5 tests), doc_comments_spec.rb (1 test)
  • test_print_puts_expectations — Fixed 2 specs with wrong print/puts expectations
    • Files: strings_spec.rb:642 (escape-only string), strings_spec.rb:652 (many strings sequence)

Compiler Bugfixes (v5.10.0) — Closure-as-Fn Unification [Feb 3, 2026]

  • closure_as_fn_return_type_unification — Return type checking inside lambda bodies
    • Root cause: return inside lambda body was checked against enclosing FUNCTION’s return type, not lambda’s
    • Symptom: Return type mismatch: expected (Int) -> Int, got (Int) -> Int (identical text, different objects)
    • Fix: Save/restore ctx->current_return_type in typecheck.c when typechecking lambda bodies
    • File: quartz-bootstrap/src/typecheck.c lines 3438-3459
    • Impact: Unblocks make_adder_chain and similar HOF patterns returning closures

Compiler Bugfixes (v5.4.4) — Self-Hosted Codegen [Feb 1, 2026]

  • self_hosted_str_replace_codegen — ptr/i64 type mismatch in str_replace runtime
    • Root cause: sub i64 %found, %ptr.str used pointer operands instead of i64
    • Fix: Added ptrtoint i8* to i64 conversions before subtraction
    • Also fixed: Missing @strstr declaration in LLVM IR
    • Note: Used single-line cg_emit() with embedded \n to avoid C bootstrap parser bug

Compiler Bugfixes (v5.3.4) — C Bootstrap Heisenbug [Jan 31, 2026]

  • c_bootstrap_uaf_fix — Use-after-free in TypeContext during MIR lowering
    • Root cause: typecontext_destroy(tc) at main.c:302 freed type arena BEFORE lower_to_mir() at line 305
    • Fix: Moved destroy to main.c:344 after all codegen completes
    • Commit: 9bfedb0 in quartz-bootstrap
    • Detection: ASan with -fsanitize=address,undefined caught heap-use-after-free at mir.c:1415
    • Verification: 5 consecutive runs produce identical MD5 (5a5caae8ab736e208cc6891d8b3b4fe0)

Compiler Bugfixes (v5.3.2) — FFI/cimport stability [Jan 30, 2026]

  • cross_module_generic_return — Module.Type and Module.Enum in return types
    • tc_parse_type_annotation now handles dotted types for regular Quartz modules
    • mir.c resolve_struct_name converts lib.Point -> lib$Point for field lookup
  • cimport_call_initialization — Fix flaky cimport test: ast_call() now initializes is_cimport_call=0
    • Root cause: malloc() garbage in is_cimport_call field caused typecheck to subtract 1 from arg count
    • Symptom: cstr_to_string wrapper intermittently reported “expects 1 argument, got 0”

Compiler Bugfixes (v5.2.5) — Regex capture operator [Jan 30, 2026]

  • posix_regex_declarations — POSIX regex functions (regcomp, regexec, regfree) not declared in LLVM IR
    • Fixed: Added declarations to codegen.c for =~? capture operator
    • Unblocked 7 regex capture tests (Match object, groups, subscript, start/end/replace)

Planned

Stack-ranked by impact. Top items unblock the most downstream features.

P1 — Extend Blocks Completion [Jan 28, 2026]

  • extend_blocks_bool — Extension methods on Bool type
    • Fixed: mir.c block_contains_return() helper, NODE_IF expression semantics
  • extend_blocks_recursive — Recursive extension method calls
    • Fixed: Same mir.c changes support recursive self-calls

P2 — HashMap Generics [Jan 29, 2026]

  • hashmap_generic_keyHashMap<K,V> with generic key type
    • Fixed: mir.h type_arg in intrinsic struct, mir_codegen.c int key comparison
  • hashmap_generic_valueHashMap<K,V> with generic value type
    • Already working via type_arg propagation

P3 — Generic Parameter Syntax [Jan 29, 2026]

  • generic_param_syntax — Allow Vec<T> in function parameter annotations
    • Fixed: tc_parse_type_annotation (C) and tc_parse_type (Quartz) now handle Vec
    • Unblocked 5 specs in regex_matches and doc_comments

P4 — PCRE2 Migration [Jan 30, 2026]

Decision: Migrate from POSIX ERE to PCRE2 regex engine.

The current POSIX ERE implementation limits regex capabilities. PCRE2 unlocks:

  • Named capture groups (?<name>...)
  • Non-capturing groups (?:...)
  • Lookahead/lookbehind assertions
  • Unicode property escapes
  • Atomic groups and possessive quantifiers
ItemStatusImpact
pcre2_integrationDoneReplaced native Thompson NFA with PCRE2
regex_named_groupsDone(?<name>...) syntax works
non_capturing_groupsDone(?:...) syntax works
regex_lookaheadAvailable(?=...) and (?!...) now supported
regex_unicodeAvailable\p{L}, \p{N} property escapes work

Implementation:

  • PCRE2 statically linked (~500KB libpcre2-8.a)
  • Replaced regex.c Thompson NFA with PCRE2 wrapper
  • Binary size: 985KB (under 1MB goal)
  • All 78 regex tests passing

P5 — Sweetness: Type-Prefixed UFCS [Jan 30, 2026]

Goal: Transform Quartz from C-with-types into a modern, ergonomic language.

See docs/SWEETNESS.md for full plan.

PhaseFeatureSyntaxStatus
1Vec index getv[i]Done
1Vec index setv[i] = xDone
2Vec method callsv.push(x), v.size, v.get(i), v.set(i, x)Done
2HashMap method callsm.set(k, v), m.get(k), m.size(), m.has(k)Done
3HashMap index getm["key"]Done
3HashMap index setm["key"] = xDone
4Regex captures$0, $1$9Done
5String methodss.size, s[i]Pending
6Collection literals[], {}Pending

Implementation: Type-prefixed UFCS via alias_name on type handles.

  • Vec and HashMap are Int handles with alias_name set
  • v.push(x) resolves to vec_push(v, x) based on receiver type
  • m["key"] rewrites to hashmap_get(m, "key") at typecheck time

Before:

vec_get_string(captures, 1)  # hideous

After:

captures[1]  # beautiful

P6 — FFI Completion (3 remaining items)

Core FFI is working — extern “C” declarations (including variadic), C type aliases, @repr(C) structs, pointer operations, and string_to_cstr are all functional. Remaining items are edge cases and advanced features.

Core Features (remaining)

  • variadic_externextern "C" variadic function declarations (printf, snprintf) [Jan 2026]
  • repr_c_struct_pass — Pass @repr(C) struct by value to C function
  • repr_c_struct_return — Receive @repr(C) struct by value from C function
  • asm_variable_binding — Access Quartz variables from inline assembly

Already Working (via gen_ffi approach)

  • c_header_function_import — Parse C functions from header via @cImport(@cInclude(...)) [Jan 2026]
  • c_header_struct_import — Parse C struct from header [Jan 2026]
  • c_header_typedef_import — Parse C typedef from header [Jan 2026]

String Marshaling (complete)

  • cstr_to_string — Convert C string back to Quartz String [Jan 2026]
  • auto_string_marshal — Automatic String <-> C string conversion [Jan 2026]
    • String args to CPtr params: auto-wrapped with string_to_cstr()
    • CPtr returns to String vars: auto-wrapped with cstr_to_string()
    • CImport lookup now takes priority over builtins (fixes c.getenv conflict)

Pending tests: 12 total (2 asm binding, 2 struct pass/return, 4 gen_ffi approach, 2 string interop, 2 memory interop)

P7 — Stdlib Expansion (11 items)

  • option_result_stdlib — Option/Result as importable stdlib types
  • module_imports — Cross-file module import system (Jan 31, 2026)
  • datetime_module — Date/time operations
  • tcp_socket — TCP networking
  • http_client — HTTP GET/POST requests
  • url_parsing — URL parsing
  • base64_module — Base64 encoding
  • uuid_module — UUID generation
  • csv_module — CSV parsing
  • yaml_module — YAML parsing
  • enumerable_trait — Generic Enumerable

P8 — Memory Completion (2 items)

  • allocation_tracking — Track stack vs heap allocation decisions
  • heap_string_annotation@heap annotation for heap-allocated strings

P9 — Tooling (4 items)

  • package_registry — Package repository
  • basic_repl — Interactive REPL
  • lsp_server — Language Server Protocol
  • formal_spec — Formal language specification

P10 — Syntax Polish (2 items)

  • path_sigil~"path/to/file" literals
  • string_match_patterns — String literal patterns in match (Jan 29, 2026)

Self-Hosted Compiler

  • safe_field_access_field_indexmir_lower_safe_field_access field index resolution [Jan 2026]
    • Resolved: HIR correctly desugars x?.field to match expressions at hir.qz:605-642
    • MIR code at mir.qz:3363 is dead code (never executed after HIR desugar)
    • Tests pass: nil_coalescing_spec.rb:146-240 — all 4 safe navigation tests passing

Planned Fixes

  • self_hosted_codegen_heap_corruption — Gen1 crashes on complex programs (FIXED v5.3.8)
    • Root cause: hashmap_keys buffer overflow (16 elements, 156 builtins)
    • Fix: Dynamic allocation based on hashmap capacity
  • self_hosted_fixpoint_divergence — Self-hosted typechecker rejects valid code (FIXED v5.3.11)
    • Root cause: NodeKind values misaligned with C bootstrap + Vec/HashMap missing from tc_is_i64_type
    • Fix: Aligned 66 NodeKind values, added TYPE_VEC/TYPE_HASHMAP to i64-compatible types
    • Verification: Fixpoint compiles in 4.5s, 1728 tests pass
  • ufcs_dot_syntax_ambiguitymodule.function(arg) uses UFCS unexpectedly (FIXED: added is_ufcs flag to distinguish UFCS calls from regular calls)
  • wildcard_pattern_support_ pattern not fully supported
  • cross_module_generic_returnmodule.Option<T> in return position
  • reserved_word_defaultdefault is reserved, must use fallback

Technical Debt Audit

Systematic review of C bootstrap and self-hosted compiler for shortcuts, incomplete implementations, and correctness issues.

Resolved Issues:

  • hir.c:56-99 — Array iteration uses channel semantics -> Fixed: MIR now dispatches based on type
  • hir.qz:354-404desugar_for stub -> Fixed: HIR disabled, MIR handles all iteration lowering

Audit Checklist:

  • Compare every desugar_* function between C bootstrap and self-hosted
  • Verify all MIR codegen paths handle labels correctly
  • Check for hardcoded values or TODO stubs in production code
  • Ensure list comp, map comp, and for loops use consistent patterns
  • Review channel vs array distinction in iteration contexts

Maybe

Features under consideration. Not committed — need design discussion.

Guards

  • function_guardsdef foo(x) when x > 0 (Elixir-style preconditions)
  • match_arm_guardsn if n > 0 => "positive" (conditional match arms) [Jan 2026]
  • binding_guardsguard x = maybe_nil() else return (unwrap-or-exit pattern)

Regex Extensions (PCRE2-dependent, see P4)

  • regex_lookahead(?=...) and (?!...) lookahead assertions
  • regex_lookbehind(?<=...) and (?<!...) lookbehind assertions
  • regex_atomic_groups(?>...) atomic groups
  • regex_unicode\p{L}, \p{N} Unicode property escapes
  • regex_in_match — Regex patterns directly in match expressions

Pending Tests Summary (40 total)

CategoryCountDetails
Arena/Heap2@heap annotation (heap-allocated arrays/structs)
Networking7TCP socket, HTTP client — stdlib modules not yet built
Regex Captures0✓ COMPLETE — $0-$9 capture variables, runtime linked to PCRE2
FFI11repr(C) alignment, string interop, C header imports, vec type inference/chaining
Type Aliases0✓ COMPLETE — Option, Result<T,E> aliases working
Visibility1Type alias public visibility
Regex =~?2Named capture, match expression integration
Method Syntax0✓ COMPLETE — s.size, s.to_i, s.find, s.slice, n.to_s, chaining all working

Next Steps (Stack Ranked)

Priority 1: Phase 4 Regex Captures — COMPLETE ✓

$0-$9 capture variables now working. Bootstrap links libquartz_regex.a and PCRE2.

Priority 2: Chained Assignment (1 test)

a = b = c syntax. Requires parser change to treat = as right-associative expression operator.

Priority 3: Dogfooding

Once above complete, use new features in self-hosted compiler source:

  • Add type annotations like v: Vec<Int> for clarity
  • Use method chaining where appropriate
  • Clean up any workarounds for missing features

Deferred (Infrastructure Required)

  • Arena/heap allocation tracking (2 tests) — runtime instrumentation
  • Networking/TCP/HTTP (7 tests) — stdlib module system
  • FFI enhancements (11 tests) — gen_ffi architecture
  • Regex =~? (2 tests) — named capture integration

Version History

VersionDateMilestone
v5.5.7Feb 2, 20260 failures (was 44). I/O buffering consistency (all write()), enum multi-payload constructor fix, test infra fixes. 1822 tests.
v5.5.3Feb 2, 2026Macro hygiene Phase 5.9.5 complete. Gensym, scope isolation, unquote preserves user bindings. 12 macro tests.
v5.5.2Feb 2, 2026Macro system Phase 5.9.3-5.9.4 complete. String templates (#{param}) and quote/unquote both working. 10 macro tests.
v5.5.1Feb 2, 2026Macro system Phase 5.9.1-5.9.2 complete. AST nodes, parser, 8 specs. Keyword collision fix (quotedquote).
v5.5.0Feb 2, 2026Macro system design complete. Pivot from expr!/expr? operators to $try/$unwrap macros. !/? reclaimed for identifiers.
v5.4.5Feb 2, 2026Force-unwrap expr! complete: parser, typecheck, MIR codegen. Test harness fix exposes 13 pre-existing crashes.
v5.4.4Feb 1, 2026Self-hosted codegen fix: ptr/i64 type mismatch in str_replace, strstr declaration
v5.4.3Feb 1, 2026Phase 5.8: ! suffix in identifiers, C-style truthiness, UFCS predicate design approved
v5.4.2Feb 1, 2026Phase 5.7: Unified Collection API - String/HashMap UFCS in BOTH compilers
v5.10.0Feb 3, 2026Self-hosted macros (lexer/parser/expander/pipeline), transitive closure capture fix, 6 new closure tests. 1858 tests
v5.9.9Feb 3, 2026panic()→stderr, block expressions, $debug expression-returning, built-in macros. 1852 tests
v5.4.1Feb 1, 2026Phase 5.7: Unified Collection API - String/HashMap UFCS methods in C bootstrap
v5.3.12Feb 1, 2026UFCS transformation Phase 2 started: mir.qz complete (478 vec_* calls → UFCS)
v5.3.11Feb 1, 2026NodeKind alignment (66 values), tc_is_i64_type fix for Vec/HashMap, fixpoint restored
v5.3.8Jan 31, 2026hashmap_keys/values buffer overflow fix, vec_set bounds checking
v5.3.5Jan 31, 2026Channel iteration fix (is_channel_type accepts Int alias). 1708 tests, 0 failures
v5.3.6Jan 31, 2026Match guard clauses (n if n > 5 =>), binding patterns. 1718 tests, 0 failures
v5.3.4Jan 31, 2026C bootstrap UAF fix (typecontext_destroy moved after codegen)
v5.2.9Jan 30, 2026P5 Sweetness Phase 4: Regex capture variables $0-$9. 1687 tests
v5.2.6Jan 30, 2026Native regex backreferences \1-\9, =~? uses native engine (POSIX ERE lacks backrefs). 1629 tests
v5.2.5Jan 30, 2026POSIX regex declarations fix - 7 capture operator tests unblocked. 1623 tests
v5.2.1Jan 28, 2026tc_suggest_identifier fixed, regex intrinsics fully ported. 1038 functions, 1611 tests
v5.2.0Jan 28, 2026Hashmap intrinsics ported to self-hosted, regex_find_all codegen fix. 1611 tests
v5.1.1Jan 27, 20260 failures! Match arm return codegen, to_str String fix (Gemini batch). 1577 tests
v5.1.0Jan 27, 2026Never type (!), dynamic MIR allocation, safe field access lowering
v5.0.5Jan 25, 2026HashMap dogfooding, dynamic resize, Vec migration
v5.0.0Jan 22, 2026Parallel iteration, pipeline `
v4.0.0Jan 18, 2026DWARF debug info, type checker parity
v3.0.0Jan 17, 2026Full type inference
v2.5.0Jan 17, 2026Match codegen for simple enums, docstrings
v2.4.0Jan 15, 2026Compiler parity (778 functions both)
v2.0.0Jan 11, 2026Quartz compiler becomes primary
v1.0.0Jan 2, 2026Self-hosting fixpoint achieved

Stack-Ranked Priority List (Feb 4, 2026)

0 failures, 40 pending. 1878 tests passing. Self-hosted is primary.

Legend: 🟢 Self-hosted only | 🟡 May need bootstrap | 🔴 Must update bootstrap

RankItemScopeEffortRationale
1UFCS MigrationDONE (v5.12.3). All vec_push/len/get → .push()/.size/[].
2Dogfooding: Postfix GuardsDONE (v5.12.4). 566 patterns converted, ~1100 lines saved.
1Dogfooding: Collection Literals🟢Mediumvec_new() + vec_push()[1, 2, 3]. Makes compiler source readable.
2Dogfooding: Map Literals🟢Mediumhashmap_new() + hashmap_set(){key: 1}.
3Dogfooding: Range Loops🟢SmallMost already converted. Remaining are complex (non-unit step, mid-loop modify).
4Self-Hosted TY_NEVER Gaps🟢Mediumtypecheck.qz doesn’t handle Never in match arms. Blocks macro-heavy code.
5Closure-as-Fn Return Unification🟡MediumReturning closure as Fn(Int): Int. 1 pending test. Bootstrap only if compiler uses it.
6Method Syntax Pending (6)🟢Smalls.size, s.to_i, s.find, n.to_s. Triage — some may already pass.
7Regex Capture Variables (8)🟢Medium$0-$9, named groups. Codegen exists, may just need tests fixed.
8$try/$unwrap for Result<T,E>🟢SmallExtend macros for Ok/Err alongside Some/None.
9Cross-Module Type Aliases🟡Mediumtype TomlResult = Result<T, E> across modules. 2 pending.
10FFI Completion (4)🔴Largerepr(C) alignment, C interop. Needs bootstrap intrinsics.
11Networking Stdlib (5)🟢LargeTCP, HTTP. Pure library code, no compiler changes.
12Tooling: REPL, LSP🟢Very LargeExternal tools. Last priority.

Pending Tests Summary (Feb 3, 2026)

CategoryCountNotes
Phase 4 capture groups8$n regex captures
Phase 2 string ops6s.find, s.slice, n.to_s
Networking5tcp_connect, http_get, URL parsing
FFI/C interop4import C struct/enum/macro/typedef
String/allocation4@heap strings, embedded nulls, cstr conversion
Type system3type aliases, chained assignment, index type check
Other10repr(C) struct, chained methods, match expressions
Closure edge case1Closure-as-Fn return type unification (NEW v5.10.0)
Total41

Test Coverage Notes (Feb 3, 2026)

Previously untested areas discovered and fixed during 5.9.9-5.10.0:

  • Return in match arms: Was a real type system bug — return typed as Void instead of Never. Now covered by 2 tests in builtin_macros_spec.rb. Fixed Feb 2.
  • panic() stderr: Fixed Feb 3 — panic() now writes to stderr via write(2, ...). Dedicated test confirms zero stdout output on panic.
  • Block expressions: Fixed Feb 3 — do...end and { } blocks work as expressions. 5 tests in block_expressions_spec.rb.
  • $debug as expression: Fixed Feb 3 — $debug(expr) returns value, outputs to stderr. 6 tests cover expression position, single evaluation, variable binding.
  • Self-hosted macro expansion: Now implemented (v5.10.0). macro_expand.qz handles $try/$unwrap/$assert/$debug. Tests pass via C bootstrap; self-hosted has pre-existing typecheck gaps.
  • Nested closures: Fixed Feb 3 — transitive capture analysis in C bootstrap. 6 new tests (5 passing, 1 pending).
  • codegen.c shadow type system: Discovered Feb 3 — codegen.c has its own typecheck_expr/typecheck_stmt that runs via codegen_validate() before MIR pipeline. Must be updated for any new language feature that affects expression types.