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
| Version | Date | Milestone |
|---|---|---|
| v1.0.0 | Jan 2, 2026 | Self-hosting fixpoint achieved - Quartz compiles itself |
| v2.0.0 | Jan 11, 2026 | Quartz compiler becomes primary |
| v3.0.0 | Jan 17, 2026 | Full type inference |
| v4.0.0 | Jan 18, 2026 | DWARF debug info, type checker parity |
| v5.0.0 | Jan 22, 2026 | Parallel iteration, pipeline |>, list comprehensions |
| v5.5.7 | Feb 2, 2026 | 0 failures (was 44). Macro system complete. |
| v5.10.0 | Feb 3, 2026 | Self-hosted macros, transitive closure capture. 1858 tests |
| v5.12.0 | Feb 4, 2026 | UFCS complete, dogfooding phase 2 complete |
| v5.12.28 | Feb 6, 2026 | 56 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>whereVec<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)tov.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,MirInstrTypeStorage,AstStorage,ParserState,TypecheckStateCodegenState,InferStorage,InferEnv
Bootstrap Fixes (v5.12.28)
Fixed 56 pre-existing test failures:
- Drop trait
selfparameter 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/$unwrapmacros- Phase 5.8.12 now COMPLETE
v5.12.26 (Feb 6): Predicate Unification (Phase 5.8.4):
- Result predicates:
.ok?/.err?rewrites tois_ok()/is_err()- Char predicates:
.digit?/.alpha?/.alnum?/.whitespace?rewrites tois_digit()etc.- C bootstrap + self-hosted: Both compilers updated with UFCS predicate rewrites
- 18 new tests in
result_predicates_spec.rb(5) andchar_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.messagecorrectly types e as the error structv5.12.23 (Feb 6): Cross-Module Type Aliases:
- Cross-module type aliases now work —
type 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()andcompile_and_run_multi()for multi-file tests- 2 new tests in
spec/integration/type_alias_spec.rbfor 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$2but bounded func registered asare_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 testnow runs specs in parallel (8 cores, ~5x faster)
rake stestfor serial execution when debugging- 5 new tests in
spec/integration/nil_coalescing_spec.rbfor ?? spacingv5.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) forTY_FUNCidentifiers- Fixed struct field inference:
def get_x(p) return p.x endwherepis inferred from struct type — typechecker now setsnode->tyafter struct inference, MIR fallback checksobj_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.rbv5.12.19 (Feb 5): Defer Statement Fix:
- Fixed defer typecheck bug: NODE_DEFER was calling
typecheck_expron deferred statement, but parser wraps calls in NODE_EXPR_STMT- Root cause:
typecheck_exprdoesn’t handle NODE_EXPR_STMT, so arity mangling wasn’t applied to deferred function calls- Fix: Changed to
typecheck_stmtwhich properly processes NODE_EXPR_STMT and applies arity mangling- Result:
defer cleanup("msg")now correctly emitscall cleanup$1(...)in MIR- 15 defer tests now passing (was 0 before fix)
v5.12.18 (Feb 5): Arity-Based Function Overloading:
- Arity overloading implemented —
def 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
VectoVec<Int>(typecheck.qz: 38, infer.qz: 2)- ~50 calls converted from
vec_push()/vec_len()to.push()/.sizein typecheck.qz- Key insight: mir.qz/codegen.qz use existential typing (bare
Vecfields with typed getter returns likeVec<String>) — NOT safe to convert- Handle patterns identified:
struct_field_names[i],enum_variant_names[i],trait_method_names[i]return Int handles, MUST usevec_len(handle)not.size- Type alias clarification: Reported issue was syntax confusion, not a bug. Struct constructors use braces
Point { x: 1 }, not parensPoint(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 = exprmeans both “declare new const” and “reassign”. The typechecker now treats it as reassignment when a binding already exists (unless explicitvar x = expr)- Key fix in typecheck.c:4197:
if (existing && !node->data.let.is_mutable)→ treat as reassignment to existing binding- Added
--version/-vflags 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 tolower_expr()in mir.c- Root cause: Index assignment was only in
lower_stmt()(returns void), notlower_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
letkeyword: Quartz usesx = expr(const-by-default), no explicitlet- 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, replacedstrrchr- 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 asVec<Int(naivelen - 5offset 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.qzto usemir_ctx_find_field_indexfor correct indices- Self-hosted str_eq fix: Fixed 22
str_eq()calls intypecheck.qzto 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 endtoreturn 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_getcalls 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
InferStoragetype- UFCS verified: Both compilers correctly type
vec_new()asVec, enabling.push()and.sizeon 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/nodesandmir_ctx_get_loop_stack/bindings/string_varsnow returnVecinstead ofInt- ~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:
LexerResultstruct (lexer.qz): 4 named fields replacer[0]..r[3]magic indicesMirProgramstruct (mir.qz): 6 fields replaceprog[0]..prog[5]MirFuncstruct (mir.qz): 11 fields replacefunc[0]..func[10]MirBlockstruct (mir.qz): 7 fields replaceblock[0]..block[6]MirInstrstruct (mir.qz): 11 fields replaceinstr[0]..instr[10]TypeStoragestruct (types.qz): 7 named fields replaces[0]..s[6]AstStorageVec fields typed: 12 bareVec→Vec<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
InttoVectype- Updated MirContext: 8 Vec fields from
InttoVectype- Updated TypeEnv: 11 fields from
InttoVectype- Updated ParserState: 5 fields from
InttoVectype, create_parser signature- Fixed ufcs_vec_len.rb transform script to handle method-style calls
- UFCS now works:
storage.children.sizecorrectly rewrites tovec_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:
- 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
- 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
- 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_chainand similar HOF patterns.v5.9.9 Cleanup (Feb 3): Three architectural fixes applied:
- panic() output moved from stdout to stderr (write(2,…) instead of @puts)
- Block expressions added to parser (do…end and {} forms)
- $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.sizefails (“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.
| Step | File | Change | Status |
|---|---|---|---|
| 0.1 | types.h | Add vec/hashmap union data structs for element types | ✓ Done |
| 0.2 | types.c | Add type_vec() and type_hashmap() constructors with interning | ✓ Done |
| 0.3 | types.c | Update type_name() to display Vec<T> and HashMap<K,V> | ✓ Done |
| 0.4 | types.c | Update types_equal_deep() for parameterized comparison | ✓ Done |
| 0.5 | typecheck.c | tc_parse_type_annotation creates Vec<T> and HashMap<K,V> types | ✓ Done |
| 0.6 | typecheck.c | Method resolution uses kind == TY_VEC not pointer equality | ✓ Done |
| 0.7 | typecheck.c | Error 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.
| Step | File | Change | Status |
|---|---|---|---|
| 1.1 | typecheck.c | Vec[T] in return type position | ✓ Done |
| 1.2 | typecheck.c | Vec[T] in struct field types | ✓ Done |
| 1.3 | typecheck.c | Type inference from vec_new() yields Vec[Unknown], from [1,2,3] yields Vec[Int] | ✓ Done |
| 1.4 | typecheck.c | Method resolution: v.push(x) → vec_push(v, x) when v: Vec[T] | ✓ Done |
| 1.5 | typecheck.c | Index syntax: v[i] → vec_get(v, i), v[i] = x → vec_set(v, i, x) | ✓ Done |
| 1.6 | mir_codegen.c | Codegen 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.
| Step | File | Change | Status |
|---|---|---|---|
| 2.1 | typecheck.c | Map[K,V] in all positions (params, returns, fields) | ✓ Done |
| 2.2 | typecheck.c | Type inference from {:} yields Map[Unknown,Unknown], from {"a": 1} yields Map[String,Int] | ✓ Done |
| 2.3 | typecheck.c | Method resolution: m.get(k) → hashmap_get(m, k) | ✓ Done |
| 2.4 | typecheck.c | Index 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.
| Step | File | Change | Status |
|---|---|---|---|
| 3.1 | types.h/c | Ptr[T] type representation | ✓ Done |
| 3.2 | typecheck.c | Ptr[T] prevents mixing incompatible pointer types | ✓ Done |
| 3.3 | types.h/c | Fn(Args): Ret function type representation | ✓ Done (pre-existing) |
| 3.4 | typecheck.c | Lambda/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.
| Step | File | Change | Status |
|---|---|---|---|
| 4.1 | middle/types.qz | Add parameterized type kinds | ✓ Done |
| 4.2 | middle/typecheck.qz | Port tc_parse_type_annotation changes | ✓ Done |
| 4.3 | middle/typecheck.qz | Port method resolution | ✓ Not needed (ptr intrinsics) |
| 4.4 | backend/mir.qz | Port type-aware lowering | ✓ Not needed (i64 at runtime) |
| 4.5 | Verify fixpoint | Self-hosted compiles itself | ✓ Done |
Phase 4.5: Type Safety for Parameterized Types (C Bootstrap) ✓ COMPLETE (Feb 1, 2026)
Goal: Enforce type safety for Vec
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>.
| Step | File | Change | Status |
|---|---|---|---|
| 4.5.1 | types.h | Add Vectypes_match() | ✓ Done |
| 4.5.2 | types.h | Add HashMap<K,V> key/value type checking in types_match() | ✓ Done |
| 4.5.3 | types.h | Ptr | ✓ Done |
| 4.5.4 | Test suite | 11 new specs in spec/integration/types/parameterized/ | ✓ Done |
| 4.5.5 | Self-hosted | Port 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_annotationsin scope system - Updated
tc_check_letand 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.
| Step | Files | Change | Status |
|---|---|---|---|
| 5.1 | All .qz files | Change v: Int to v: Vec<T> where semantic | Done |
| 5.2 | All .qz files | Change m: Int to m: HashMap<K,V> where semantic | Done (no public APIs return HashMap) |
| 5.3 | All .qz files | Convert vec_len(v) → v.size, vec_push(v, x) → v.push(x) | 84% done - 180 handle-pattern calls remain |
| 5.4 | All .qz files | Convert hashmap_get(m, k) → m.get(k) or m[k] | Pending |
| 5.5 | Verify | All tests pass, fixpoint maintained | Ongoing |
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
Phase 5.6: Internal Storage Refactor (Required) — IN PROGRESS
Convert compiler internals from Int handles to typed Vec<T> storage:
| Step | Scope | Change | Status |
|---|---|---|---|
| 5.6.1 | MIR context | Refactor mir_ctx slots to typed fields | Complete |
| 5.6.2 | Type env | Refactor typeenv slots to typed fields | Complete |
| 5.6.3 | AST storage | Convert AST storage to AstStorage struct | Complete |
| 5.6.4 | Parser state | Convert parser state to typed storage | Complete |
| 5.6.5a | Vec field types | Update struct fields from Int to Vec | Complete |
| 5.6.5b-types | Function signatures | Update 77 Int→Vec function signatures | Complete |
| 5.6.6 | Structification | Replace Vec pseudo-structs with proper structs | Complete |
| 5.6.6a | LexerResult | lexer.qz: struct with 4 named fields | Complete |
| 5.6.6b | MIR structs | mir.qz: MirProgram, MirFunc, MirBlock, MirInstr (4 structs) | Complete |
| 5.6.6c | TypeStorage | types.qz: struct with 7 named fields | Complete |
| 5.6.6d | AstStorage typing | ast.qz: 12 Vec fields → Vec | Complete |
| 5.6.7-xform | Transform vec_len | Convert vec_len() to .size (~299 calls) | COMPLETE |
| 5.6.7c-xform | Transform vec_push | Convert vec_push() to .push() (~1227 calls) | COMPLETE |
| 5.6.7d | Transform indexing | Convert vec_get/vec_set to index syntax (~595 calls) | COMPLETE |
| 5.6.8 | Structify remaining | Structify 4 remaining pseudo-struct containers | Complete |
| 5.6.8a | TypecheckState | typecheck.qz: 40 fields → TypecheckState struct | Complete |
| 5.6.8b | CodegenState | codegen.qz: 10 fields → CodegenState struct | Complete |
| 5.6.8c | InferStorage | infer.qz: 7 fields → InferStorage struct | Complete |
| 5.6.8d | InferEnv | infer.qz: 2 fields → InferEnv struct | Complete |
| 5.6.9 | UFCS cleanup | Handle patterns categorized; convertible calls done | COMPLETE |
| 5.6.10 | Error code rename | BE0xxx → QZ0xxx across both compilers | Complete |
| 5.6.11 | Increase MAX_PARAMS | 32 → 64 fields per struct | Complete |
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.size → vec_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:
-
AstStorage (ast.qz:193-206): All 12 fields changed from
InttoVec- kinds, lines, cols, int_vals, str1s, str2s, ops, lefts, rights, extras, children, docs
-
MirContext (mir.qz:87-113): 8 Vec fields changed from
InttoVec- 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.
-
TypeEnv (typeenv.qz:14-29): 11 fields changed from
InttoVec- binding_names, binding_types, binding_depths, binding_mutables
- struct_names, struct_types, enum_names, enum_types
- func_names, func_types, scope_stack
-
ParserState (parser.qz:49-64): 5 fields changed from
InttoVec- types, lexemes, lines, cols, nodes
- Updated
create_parser()signature to accept Vec parameters
-
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 producingx..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:
- Change function return types from
InttoVecwhere appropriate - Run the transform script
- 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 ParserStatewith typed fields for all parser state - Updated
create_parser()to return struct literal - Converted 80
ps: Intparameters tops: ParserState - Converted all
ps[N]index accessors tops.field_name - String fields (
error_msg,pending_doc) now nativeStringtype (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.cregisters builtins with proper return types (e.g.,vec_new → t_vec)mir.c:resolve_expr_type()only queriesFuncBinding(user-defined functions)- Builtins have no
FuncBindingentry, soresolve_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:
| Phase | Scope | Builtins | Status |
|---|---|---|---|
| Phase 1 | Types exist, MIR loses them | vec_new, hashmap_new, string_new, array_new, io_new | FIXED |
| Phase 2 | MIR type strings added | sb_new, set_new, intmap_new, arena_new, pool_new, arena_pool_new, channel_new, mutex_new, atomic_new | FIXED (MIR only) |
| Phase 3 | Full UFCS support | Add TY_* enums for Phase 2 types | Pending |
Phase 1 (Complete):
These builtins already have TY_* enum values and t_* type globals in typecheck.c:
vec_new→t_vec(TY_VEC)hashmap_new→t_hashmap(TY_HASHMAP)sb_new→t_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_SETintmap_new→ needs TY_INTMAParena_new→ needs TY_ARENApool_new→ needs TY_POOLarena_pool_new→ needs TY_ARENAPOOLchannel_new→ needs TY_CHANNELmutex_new→ needs TY_MUTEXatomic_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)
| Step | Protocol | Types | Status |
|---|---|---|---|
| 5.7.1 | Sized | String, HashMap, Set, Array, StringBuilder | Complete |
| 5.7.2 | Sized | .is_empty synthesized for all Sized types | Complete |
| 5.7.3 | Pushable | StringBuilder (.push → sb_append) | Complete |
| 5.7.4 | Clearable | Vec, HashMap, Set, StringBuilder | Complete |
| 5.7.5 | Deletable | HashMap (.delete), Set (.delete) | Complete |
| 5.7.6 | Contains | Set (.contains), String (.contains) | Complete (String returns Bool) |
| 5.7.7 | Contains | HashMap (.has) | Complete |
| 5.7.8 | String methods | .find, .slice, .starts_with, .ends_with | Complete |
| 5.7.9 | String methods | .downcase, .upcase, .trim, .replace, str_join | Complete |
| 5.7.10 | Conversion | .to_i() (String), .to_s() (Int) | Already exists |
| 5.7.11 | HashMap methods | .keys(), .values() | Complete |
| 5.7.12 | Self-hosted | Port all changes to typecheck.qz | Complete |
| 5.7.13 | Deprecation | Add warnings for prefixed functions | Future |
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:
nilexists 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) == 1instead ofif str_eq(a, b)
Proposed Syntax:
| Syntax | Meaning | Example |
|---|---|---|
foo? | Predicate method | s.empty? → Bool |
foo! | Force unwrap | maybe! → crash if nil |
foo!() | Mutating method | s.upcase!() → mutates in place |
a ?? b | Nil coalesce | maybe ?? default |
if condition | C-style truthiness | non-zero = true, zero/nil = false |
a == b | String 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:
| Step | Description | Status |
|---|---|---|
| 5.8.0 | C-style truthiness: any type in if/while conditions | Complete |
| 5.8.1 | String ==, !=, + operators (already in mir.c) | Complete |
| 5.8.2 | Lexer: ! allowed as final char in identifiers | Complete |
| 5.8.2.1 | Codegen: sanitize ! → X in LLVM IR names | Complete |
| 5.8.2.2 | ? in identifiers | REJECTED — conflicts with ? try-unwrap operator |
| 5.8.3 | UFCS predicate rewriting: v.empty? → vec_len(v) == 0 | ✓ COMPLETE |
| 5.8.4 | Result ok?/err? + char predicates digit?/alpha?/alnum?/whitespace? | ✓ COMPLETE |
| 5.8.5 | some?/none? predicates via UFCS rewrite to is_some/is_none | ✓ COMPLETE |
| 5.8.6 | expr! as force-unwrap expression | SUPERSEDED by macro system |
| 5.8.7 | SUPERSEDED by $unwrap macro | |
| 5.8.8 | SUPERSEDED by $unwrap macro | |
| 5.8.9 | Lexer: 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:
| Phase | Description | Status |
|---|---|---|
| 5.9.1 | AST nodes: NODE_MACRO_DEF, NODE_MACRO_CALL, NODE_QUOTE, NODE_UNQUOTE | ✓ Complete |
| 5.9.2 | Parser: macro name(args) do ... end and $macro(args) | ✓ Complete |
| 5.9.3 | Macro registry and basic expansion pass | ✓ Complete |
| 5.9.4 | Quote/unquote semantics | ✓ Complete |
| 5.9.5 | Hygiene: gensym, scope tracking, var! escape | ✓ Complete |
| 5.9.6 | String template macros (simple form) | ✓ Complete |
| 5.9.7 | Variadic arguments (args...) | ✓ Complete |
| 5.9.8 | --expand-macros debug flag | ✓ Complete |
| 5.9.9 | Built-in macros: $try, $unwrap, $assert, $debug | ✓ Complete (C bootstrap) |
| 5.9.10 | ?/! operators, allow in identifiers | SUPERSEDED — see note |
| 5.9.11 | ! suffix implies var in typecheck | SUPERSEDED — see note |
Phase 5.9.10-11 Design Decision (Feb 5, 2026):
After analysis, we chose NOT to remove ?/! operators because:
- The macro-based approach (
$try,$unwrap) provides equivalent functionality - Predicates (
empty?,some?,none?) already work via UFCS rewriting at typecheck level - 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_EACHto lexer - Added
NODE_MACRO_DEF,NODE_MACRO_CALL,NODE_QUOTE,NODE_UNQUOTE,NODE_UNQUOTE_EACHto 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 quotetovar dquoteincodegen.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.handmacro.cinquartz-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()andparse_single_stmt()to parser.c - Expansion integrated in main.c: post-parse, pre-resolve_imports
--expand-macrosflag shows expansion debug outputast_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_UNQUOTEandTOK_UNQUOTE_EACHintoparse_factorfor use inside quote blocks - Added forward declarations for
parse_unquote()andparse_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
HygieneContextstruct to track renames during macro expansion hygiene_add_rename(): Creates gensym’d name (__name_N) and stores mappinghygiene_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 useis_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_variadicandvariadic_paramfields to MacroDef struct and AST node - Parser:
parse_macro_def()detectsparam...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 lookupexpand_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()andexpand_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::Nonealongside 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:
-
panic() → stderr:
mir_codegen.cpanic codegen now useswrite(2, ...)instead of@puts(). Both panic sites andunwrap_panicfixed. All diagnostic output goes to fd 2. -
Block expressions: Parser (
parser.c) now supports blocks as expressions.do...endform: 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) andcodegen.c(old codegen validation) updated with block expression support and scope tracking - 5 new tests in
spec/integration/block_expressions_spec.rb
-
$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 viaeputs() - Returns the argument value — usable in
val x = $debug(expr)orf($debug(a) + $debug(b)) - 6 new/updated tests for expression semantics
- Uses gensym binding (
-
Key discovery:
codegen.chas its own shadow type system (typecheck_expr/typecheck_stmt) that runs viacodegen_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: AddedNodeMacroCall(kind 67) withast_macro_call(s, name, args, line, col)constructorparser.qz: Addedps_parse_macro_call()— dispatched when TOK_DOLLAR seen in primary expressionmacro_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(notmacro— that clashes with C bootstrap reserved word)
quartz.qz: Wiredexpand_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 noNODE_LAMBDAcase — 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:
- Ruby/Crystal-style predicates (
empty?,valid?,nil?) - Self-documenting mutable bindings (no need to grep for
var) - 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: ReturnTYPE_INT()directly for bareTYPE_ARRAY()(non-parameterized arrays)infer.qz: Addedinfer_is_parameterized_type()to distinguish stored types from constantstypecheck.qz: HandleTYPE_ARRAY()inv.sizedesugaring (resolves tovec_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)whenv: 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)
| Issue | File | Problem | Status |
|---|---|---|---|
| 6.1.1 | typecheck.c:1197-1219 | Vec len - 5 breaks nested types | FIXED (depth-based) |
| 6.1.2 | typecheck.c:1152-1289 | CPtr, Array, Ptr, HashMap use naive len - N | ALL FIXED (depth-based) |
| 6.1.3 | types.c:950, 973, 991 | strrchr finds wrong > for nested types | FIXED (find_matching_close_bracket()) |
| 6.1.4 | typecheck.c, mir.c | Small buffers (64-128 bytes) for type names | FIXED (256-byte MAX_TYPE_NAME) |
| 6.1.5 | types.c:796 | Static 256-byte buffer overwritten in recursive calls | FIXED (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)
| Issue | File | Problem | Status |
|---|---|---|---|
| 6.2.1 | parser.c | x: Type = expr (const with type annotation) not supported | FIXED |
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 = expr— FIXED (added parser support)
The Fix (parser.c):
- Added
TOK_COLONto 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
| Constant | Value | Location | Risk |
|---|---|---|---|
| MAX_STRUCTS | 256 | codegen.c:272, cimport.h:14 | OK (increased from 64) |
| MAX_MODULES | 256 | resolver.h:6 | OK (increased from 64) |
| MAX_MIR_FUNCTIONS | 2048 | mir.h:17 | OK (increased from 1024) |
| MAX_ENUMS | 128 | codegen.c:377, typecheck.h:14 | OK (increased from 64) |
| TC_MAX_TRAITS | 64 | typecheck.h:15 | OK (increased from 32) |
| TC_MAX_IMPLS | 128 | typecheck.h:16 | OK (increased from 64) |
| MAX_SOURCE_SIZE | 512KB | lexer.h:56 | May hit for generated code |
| MAX_CHILDREN | 2048 | ast.h:69 | May hit for generated code |
| MAX_TYPE_PARAMS | 8 | ast.h:72 | May hit for complex generics |
Note: Critical limits for self-hosted compilation now adequate. Dynamic allocation deferred.
Category 4: Type System Incompleteness (MEDIUM PRIORITY)
| Issue | File | Problem | Status |
|---|---|---|---|
| 6.4.1 | typecheck.c:3998 | Multi-param enum uses type_args[0] always | FIXED (EnumDef.type_param_names) |
Fix: Add type_param_names[] to EnumDef, lookup param index | |||
| 6.4.2 | typecheck.c:4836 | Trait impl param types not validated | DEFERRED (to self-hosted) |
| 6.4.3 | infer.c:151-159 | occurs_in_impl doesn’t recurse into Array/Func | BY DESIGN (simplified HM) |
| 6.4.4 | typecheck.c:1099 | Generic struct instantiation TODO | N/A (Quartz has no generic structs) |
| 6.4.5 | typecheck.c:1382 | No proper TY_NEWTYPE kind | DEFERRED (transparent works, self-hosted) |
Category 5: Missing Type Cases (LOW PRIORITY)
| Issue | File | Problem | Status |
|---|---|---|---|
| 6.5.1 | types.c:717-793 | types_equal_deep missing TY_STRINGBUILDER | FIXED (line 735) |
| 6.5.2 | types.c:892-927 | type_kind_name missing TY_STRINGBUILDER | FIXED (line 960) |
Category 6: Incomplete/Unimplemented (LOW PRIORITY)
| Issue | File | Problem | Status |
|---|---|---|---|
| 6.6.1 | mir_codegen.c:4933 | Channel select not implemented | DEFERRED (advanced concurrency) |
| 6.6.2 | cimport.c:358 | @cImport function params not wired | DEFERRED (parse_param_types exists but unused) |
Priority Fix Order
-
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
-
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)
- 6.2.1: const type annotation syntax (
-
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
-
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
- Ergonomics:
v.size,v.push(x),v[i],m["key"]work on typed parameters - Safety: Can’t accidentally pass
Vec[Int]whereVec[String]expected - Zero overhead: Generated LLVM IR identical to current (all i64 ops)
- Backward compatible: Old code with
Intparameters still compiles - Fixpoint: Self-hosted compiler compiles itself throughout migration
Estimated Timeline
| Phase | Duration | Milestone |
|---|---|---|
| Phase 0 | 2-3 days | Vec[T] in parameters works |
| Phase 1 | 3-4 days | Vec[T] complete in C bootstrap |
| Phase 2 | 2 days | Map[K,V] complete in C bootstrap |
| Phase 3 | 2-3 days | Ptr[T], Fn types in C bootstrap |
| Phase 4 | 3-4 days | Self-hosted port complete |
| Phase 5 | 3-5 days | Compiler 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.
| # | Item | Scope | Status |
|---|---|---|---|
| 1 | option_result_stdlib | Prelude auto-import, ? operator, Option/Result in std | Phase 2 Complete |
| 2 | asm_variable_binding | @c("code", var1, var2) in self-hosted | Complete |
| 3 | Language Features | Postfix guards, regex patterns (78 tests) | Complete |
| 4 | DOGFOODING | Refactor self-hosted with all new sugar | Unblocked - Ready |
| 5 | Standard Library | tcp, http, datetime, csv, yaml modules | Pending |
| 6 | Tooling | REPL, LSP, package registry | Last |
option_result_stdlib Design Decisions (Jan 30, 2026)
| Decision | Choice | Rationale |
|---|---|---|
| Error coercion | Exact match required | No From trait complexity. Explicit .map_err() for conversion. Add From later if pain warrants. |
| Prelude scope | True import (shadowable) | Compiler injects import prelude at parse time. Not magic—user can shadow. |
| Domain Results | Migrate via type aliases | type 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.qzwith 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
- Created
- 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.rbfor?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_returninfrontend/parser.qzwas missing checks forTOK_IF/TOK_UNLESS return if conditionwas being parsed asreturn (if-expression)instead of bare return with guard- Fix: Added
TOK_ELSE,TOK_IF,TOK_UNLESSchecks before callingps_parse_expr - Now generates 182,286 lines of LLVM IR when compiling itself
Features added since last dogfood (Jan 23, 2026):
| Category | Features |
|---|---|
| Range Expressions | 0..n (exclusive), 0...n (inclusive) |
| Collection Literals | [1, 2, 3], {:}, {"key": val}, trailing commas |
| String Sweetness | s.size, s.length, s[i], s.find(), s.to_i, s[a:b] slices |
| Indexing | v[i], v[i] = x, m["key"], m["key"] = x |
| Method Syntax | UFCS on primitives, extend blocks |
| Safe Navigation | x?.field desugaring fixed |
| Generics | Vec<T> in params, CPtr<T> typed pointers |
| FFI | repr(C) structs, variadic extern, ptr_cast, 22 items complete |
| Regex | PCRE2, find_all, named groups |
| Match | do…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.
| Transformation | Before | After | Status |
|---|---|---|---|
| String methods | str_len(s) | s.size | Complete |
| Vec/HashMap size | vec_len(v) | v.size | Complete |
| Collection literals | vec_new() + vec_push() | [1, 2, 3] | Pending |
| Map literals | hashmap_new() + hashmap_set() | {key: 1, other: 2} | Pending |
| Range loops | var i = 0; while i < n | for i in 0..n | Pending |
| Postfix guards | if x { return } | return if x | Pending |
| Pipeline | f(g(h(x))) (3+ deep) | x |> h |> g |> f | Pending |
Phase 1 Progress (Feb 1, 2026):
- Transformed 60+
str_len()calls to.sizein self-hosted codebase - Added
.sizeproperty 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
.sizeproperty handling to C bootstrap - Standardized on
.sizeover.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
| Metric | Current | Target |
|---|---|---|
| Lines of code | ~15,000 | ~12,000 (-20%) |
| Functions using UFCS | 0 | 200+ |
| Newtypes for safety | 0 | 15+ |
| Match expressions | ~50 | 150+ |
| Drop implementations | 0 | 5+ |
Status Summary
| Category | Complete | In Progress | Planned | Total |
|---|---|---|---|---|
| Core Semantics | 3 | 0 | 2 | 5 |
| Module System | 10 | 0 | 1 | 11 |
| Type System | 21 | 0 | 0 | 21 |
| Syntax | 21 | 0 | 1 | 22 |
| Concurrency | 13 | 0 | 0 | 13 |
| FFI & C Interop | 25 | 0 | 2 | 27 |
| Stdlib | 7 | 0 | 11 | 18 |
| Error Handling | 7 | 0 | 0 | 7 |
| Memory | 8 | 0 | 2 | 10 |
| Enumerable | 7 | 0 | 1 | 8 |
| Regex | 13 | 0 | 4 | 17 |
| Tooling | 2 | 0 | 4 | 6 |
| Documentation | 0 | 0 | 3 | 3 |
| Bugfixes | 12 | 0 | 2 | 14 |
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_imports—import { a, b } from module -
aliased_imports—import foo as bar -
qualified_calls—module.function()syntax -
hierarchical_import_prefixing— Nested module paths -
qualified_types—module.Struct {}andmodule.Enum::Variantsyntax [Jan 2026] -
private_type_enforcement— Private structs/enums rejected from other modules [Jan 2026]
Type System
-
trait_declarations—trait Name { def method(self) } -
impl_blocks—impl 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_aliases—type Name = ExistingType(transparent synonyms) [Jan 2026] -
newtype_wrappers—newtype 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_intrinsics—vec_get<T>,hashmap_set<V>explicit type args [Jan 2026] -
generic_param_syntax— AllowVec<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_operator—x |> f |> g -
list_comprehensions—[expr for x in iter if cond] -
labeled_loops—break :label,continue :label -
type_dot_method—Type.method()associated functions -
regex_patterns— Pattern matching integration -
postfix_guards—return x if cond,break unless cond[Jan 2026] -
match_arm_brace_blocks— Multi-statement match armsA => { 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_sweetness—s.size,s[i],s.find(),s.to_i[Jan 2026] -
int_to_string_sweetness—n.to_sinteger to string [Jan 2026] -
slice_syntax—s[a:b],s[:b],s[a:]string slicing [Jan 2026] -
collection_literals—[]empty Vec,{:}empty HashMap,{"key": val}map literals [Jan 2026] -
vec_sweetness—v.sizefor Vec length [Jan 2026] -
hashmap_sweetness—m.size,m["key"]HashMap access [Jan 2026]
Concurrency
-
thread_spawn_intrinsic—thread_spawn(fn) -
thread_join_intrinsic—thread_join(handle) -
mutex_type—Mutex<T>with lock/unlock -
channel_type—Channel<T>send/receive -
atomic_intrinsics—atomic_load,atomic_store,atomic_cas -
atomic_operations— Full atomic ops suite -
lambda_closure_spawn— Spawn with closures -
parallel_intrinsics—parallel_map,parallel_for,parallel_reduce -
parallel_map— Parallel array transformation
FFI & C Interop
-
extern_c_declarations—extern "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_intrinsics—map,filter,reduce, etc. -
regex_string_methods—match,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_intrinsic—regex_matchesiteration [Jan 2026] -
regex_find_all_intrinsic—regex_find_all(str, pattern)returns Vec[Jan 2026] -
regex_replace—regex_replace(str, pattern, replacement)replaces first match [Jan 2026] -
regex_replace_all—regex_replace_all(str, pattern, replacement)replaces all [Jan 2026] -
regex_split—regex_split(str, pattern)splits on pattern [Jan 2026] -
regex_count—regex_count(str, pattern)counts occurrences [Jan 2026] -
regex_find_match_option—find_matchreturnsOption<Match>[Jan 2026] -
regex_matches_bool—str.matches(pattern)returns Bool [Jan 2026] -
regex_match_positions— Match start/end index positions viaregex_match_start()/regex_match_end()[Jan 2026] -
regex_backreferences— Backreferences\1-\9in native regex engine, =~? now uses native engine [Jan 2026]
Error Handling
-
try_catch_syntax—try/catchblocks -
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_statement—defer exprcleanup on function exit [Jan 2026] -
heap_annotation—@heapannotation for heap-allocated arrays/structs [Jan 2026] -
arena_blocks— Block-based allocation witharena_block_count[Jan 2026]
Enumerable
-
HashMap enumerable—hashmap_size,hashmap_keys,hashmap_values -
HashMap dogfooding— HashMap used in self-hosted compiler [Jan 2026] -
Set data structure—set_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—--debugflag for DWARF metadata -
package_manifest—quartz.tomlpackage config [Jan 2026] — std/toml/manifest.qz -
docgen_ref_md— Automated ref.md from doc comments — Extract@syntax,@example,@gotchafrom##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_loop—continuein for..in loops now advances iterator [Jan 2026] -
do_end_match_arms— Match arms withdo...endblocks [Jan 2026]
Compiler Bugfixes (v5.1.1) — Gemini batch [Jan 27, 2026]
-
match_arm_return_codegen—returninside match arms stored stale MIRValue instead of body expression -
to_str_string_double_wrap—to_stron 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-\9in native Thompson NFA engine- Added
STATE_BACKREFstate type, parsed inparse_escape - Handle in
addthread()as position-advancing epsilon transition - Track thread position (
spfield) for correct group 0 end bounds
- Added
-
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_keysandhashmap_valuesallocated fixed 16-element buffer - Symptom: Crash in
edit_distancewhen “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)
- Root cause:
-
vec_set_bounds_checking— Added bounds checking tovec_setin self-hosted codegen- Symptom: Silent heap corruption on out-of-bounds vector writes
- Fix: Check
idx >= 0 AND idx < sizebefore 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 unbufferedwrite()syscalls- Root cause:
print_int()usedprintf()(stdio buffered) andputs()used Cputs()(stdio buffered), whileprint()/eprint()/eputs()used rawwrite()(unbuffered). Interleaved output appeared in wrong order. - Fix:
print_intnow usessprintfto stack buffer +write().putsnow usesstrlen+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.
- Root cause:
-
enum_multi_payload_constructor— Enum variants with multiple fields now store all payloads- Root cause:
mir.cenum constructor only allocated 2 words (tag + 1 field) and storedpayloads[0]. ForBoth(a: Int, b: Int), the second field was lost (read as heap garbage). - Fix: Allocate
1 + payload_countwords, 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.
- Root cause:
-
test_infra_success_semantics—RunResult.success?now means “exited normally” not “exit code 0”- Root cause: Tests using non-zero exit codes to signal results (e.g.,
return 1for success) were falsely reported as failures becausesuccess?checkedexitstatus == 0. - Fix: Changed to
run_status.exited?— process exited normally, regardless of exit code. - File:
spec/spec_helper.rb(lli path and clang path)
- Root cause: Tests using non-zero exit codes to signal results (e.g.,
-
test_vec_get_string— Fixed 6 specs usingvec_get()onVec<String>to usevec_get_string()- Files:
regex_matches_spec.rb(5 tests),doc_comments_spec.rb(1 test)
- Files:
-
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)
- Files:
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:
returninside 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_typein typecheck.c when typechecking lambda bodies - File:
quartz-bootstrap/src/typecheck.clines 3438-3459 - Impact: Unblocks
make_adder_chainand similar HOF patterns returning closures
- Root cause:
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.strused pointer operands instead of i64 - Fix: Added
ptrtoint i8* to i64conversions before subtraction - Also fixed: Missing
@strstrdeclaration in LLVM IR - Note: Used single-line
cg_emit()with embedded\nto avoid C bootstrap parser bug
- Root cause:
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 BEFORElower_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,undefinedcaught heap-use-after-free at mir.c:1415 - Verification: 5 consecutive runs produce identical MD5 (5a5caae8ab736e208cc6891d8b3b4fe0)
- Root cause:
Compiler Bugfixes (v5.3.2) — FFI/cimport stability [Jan 30, 2026]
-
cross_module_generic_return— Module.Type and Module.Enumin 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_key—HashMap<K,V>with generic key type- Fixed: mir.h type_arg in intrinsic struct, mir_codegen.c int key comparison
-
hashmap_generic_value—HashMap<K,V>with generic value type- Already working via type_arg propagation
P3 — Generic Parameter Syntax [Jan 29, 2026]
-
generic_param_syntax— AllowVec<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
- Fixed: tc_parse_type_annotation (C) and tc_parse_type (Quartz) now handle Vec
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
| Item | Status | Impact |
|---|---|---|
pcre2_integration | Done | Replaced native Thompson NFA with PCRE2 |
regex_named_groups | Done | (?<name>...) syntax works |
non_capturing_groups | Done | (?:...) syntax works |
regex_lookahead | Available | (?=...) and (?!...) now supported |
regex_unicode | Available | \p{L}, \p{N} property escapes work |
Implementation:
- PCRE2 statically linked (~500KB libpcre2-8.a)
- Replaced
regex.cThompson 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.
| Phase | Feature | Syntax | Status |
|---|---|---|---|
| 1 | Vec index get | v[i] | Done |
| 1 | Vec index set | v[i] = x | Done |
| 2 | Vec method calls | v.push(x), v.size, v.get(i), v.set(i, x) | Done |
| 2 | HashMap method calls | m.set(k, v), m.get(k), m.size(), m.has(k) | Done |
| 3 | HashMap index get | m["key"] | Done |
| 3 | HashMap index set | m["key"] = x | Done |
| 4 | Regex captures | $0, $1…$9 | Done |
| 5 | String methods | s.size, s[i] | Pending |
| 6 | Collection literals | [], {} | Pending |
Implementation: Type-prefixed UFCS via alias_name on type handles.
VecandHashMapareInthandles withalias_namesetv.push(x)resolves tovec_push(v, x)based on receiver typem["key"]rewrites tohashmap_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_extern—extern "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.getenvconflict)
- String args to CPtr params: auto-wrapped with
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—@heapannotation 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_index—mir_lower_safe_field_accessfield index resolution [Jan 2026]- Resolved: HIR correctly desugars
x?.fieldto match expressions athir.qz:605-642 - MIR code at
mir.qz:3363is dead code (never executed after HIR desugar) - Tests pass:
nil_coalescing_spec.rb:146-240— all 4 safe navigation tests passing
- Resolved: HIR correctly desugars
Planned Fixes
-
self_hosted_codegen_heap_corruption— Gen1 crashes on complex programs (FIXED v5.3.8)- Root cause:
hashmap_keysbuffer overflow (16 elements, 156 builtins) - Fix: Dynamic allocation based on hashmap capacity
- Root cause:
-
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_ambiguity—module.function(arg)uses UFCS unexpectedly (FIXED: addedis_ufcsflag to distinguish UFCS calls from regular calls) -
wildcard_pattern_support—_pattern not fully supported -
cross_module_generic_return—module.Option<T>in return position -
reserved_word_default—defaultis reserved, must usefallback
Technical Debt Audit
Systematic review of C bootstrap and self-hosted compiler for shortcuts, incomplete implementations, and correctness issues.
Resolved Issues:
-> Fixed: MIR now dispatches based on typehir.c:56-99— Array iteration uses channel semantics-> Fixed: HIR disabled, MIR handles all iteration loweringhir.qz:354-404—desugar_forstub
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_guards—def foo(x) when x > 0(Elixir-style preconditions) -
match_arm_guards—n if n > 0 => "positive"(conditional match arms) [Jan 2026] -
binding_guards—guard 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)
| Category | Count | Details |
|---|---|---|
| Arena/Heap | 2 | @heap annotation (heap-allocated arrays/structs) |
| Networking | 7 | TCP socket, HTTP client — stdlib modules not yet built |
| Regex Captures | 0 | ✓ COMPLETE — $0-$9 capture variables, runtime linked to PCRE2 |
| FFI | 11 | repr(C) alignment, string interop, C header imports, vec type inference/chaining |
| Type Aliases | 0 | ✓ COMPLETE — Option |
| Visibility | 1 | Type alias public visibility |
| Regex =~? | 2 | Named capture, match expression integration |
| Method Syntax | 0 | ✓ 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
| Version | Date | Milestone |
|---|---|---|
| v5.5.7 | Feb 2, 2026 | 0 failures (was 44). I/O buffering consistency (all write()), enum multi-payload constructor fix, test infra fixes. 1822 tests. |
| v5.5.3 | Feb 2, 2026 | Macro hygiene Phase 5.9.5 complete. Gensym, scope isolation, unquote preserves user bindings. 12 macro tests. |
| v5.5.2 | Feb 2, 2026 | Macro system Phase 5.9.3-5.9.4 complete. String templates (#{param}) and quote/unquote both working. 10 macro tests. |
| v5.5.1 | Feb 2, 2026 | Macro system Phase 5.9.1-5.9.2 complete. AST nodes, parser, 8 specs. Keyword collision fix (quote → dquote). |
| v5.5.0 | Feb 2, 2026 | Macro system design complete. Pivot from expr!/expr? operators to $try/$unwrap macros. !/? reclaimed for identifiers. |
| v5.4.5 | Feb 2, 2026 | Force-unwrap expr! complete: parser, typecheck, MIR codegen. Test harness fix exposes 13 pre-existing crashes. |
| v5.4.4 | Feb 1, 2026 | Self-hosted codegen fix: ptr/i64 type mismatch in str_replace, strstr declaration |
| v5.4.3 | Feb 1, 2026 | Phase 5.8: ! suffix in identifiers, C-style truthiness, UFCS predicate design approved |
| v5.4.2 | Feb 1, 2026 | Phase 5.7: Unified Collection API - String/HashMap UFCS in BOTH compilers |
| v5.10.0 | Feb 3, 2026 | Self-hosted macros (lexer/parser/expander/pipeline), transitive closure capture fix, 6 new closure tests. 1858 tests |
| v5.9.9 | Feb 3, 2026 | panic()→stderr, block expressions, $debug expression-returning, built-in macros. 1852 tests |
| v5.4.1 | Feb 1, 2026 | Phase 5.7: Unified Collection API - String/HashMap UFCS methods in C bootstrap |
| v5.3.12 | Feb 1, 2026 | UFCS transformation Phase 2 started: mir.qz complete (478 vec_* calls → UFCS) |
| v5.3.11 | Feb 1, 2026 | NodeKind alignment (66 values), tc_is_i64_type fix for Vec/HashMap, fixpoint restored |
| v5.3.8 | Jan 31, 2026 | hashmap_keys/values buffer overflow fix, vec_set bounds checking |
| v5.3.5 | Jan 31, 2026 | Channel iteration fix (is_channel_type accepts Int alias). 1708 tests, 0 failures |
| v5.3.6 | Jan 31, 2026 | Match guard clauses (n if n > 5 =>), binding patterns. 1718 tests, 0 failures |
| v5.3.4 | Jan 31, 2026 | C bootstrap UAF fix (typecontext_destroy moved after codegen) |
| v5.2.9 | Jan 30, 2026 | P5 Sweetness Phase 4: Regex capture variables $0-$9. 1687 tests |
| v5.2.6 | Jan 30, 2026 | Native regex backreferences \1-\9, =~? uses native engine (POSIX ERE lacks backrefs). 1629 tests |
| v5.2.5 | Jan 30, 2026 | POSIX regex declarations fix - 7 capture operator tests unblocked. 1623 tests |
| v5.2.1 | Jan 28, 2026 | tc_suggest_identifier fixed, regex intrinsics fully ported. 1038 functions, 1611 tests |
| v5.2.0 | Jan 28, 2026 | Hashmap intrinsics ported to self-hosted, regex_find_all codegen fix. 1611 tests |
| v5.1.1 | Jan 27, 2026 | 0 failures! Match arm return codegen, to_str String fix (Gemini batch). 1577 tests |
| v5.1.0 | Jan 27, 2026 | Never type (!), dynamic MIR allocation, safe field access lowering |
| v5.0.5 | Jan 25, 2026 | HashMap dogfooding, dynamic resize, Vec |
| v5.0.0 | Jan 22, 2026 | Parallel iteration, pipeline ` |
| v4.0.0 | Jan 18, 2026 | DWARF debug info, type checker parity |
| v3.0.0 | Jan 17, 2026 | Full type inference |
| v2.5.0 | Jan 17, 2026 | Match codegen for simple enums, docstrings |
| v2.4.0 | Jan 15, 2026 | Compiler parity (778 functions both) |
| v2.0.0 | Jan 11, 2026 | Quartz compiler becomes primary |
| v1.0.0 | Jan 2, 2026 | Self-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
| Rank | Item | Scope | Effort | Rationale |
|---|---|---|---|---|
| — | — | ✓ DONE (v5.12.3). All vec_push/len/get → .push()/.size/[]. | ||
| — | — | ✓ DONE (v5.12.4). 566 patterns converted, ~1100 lines saved. | ||
| 1 | Dogfooding: Collection Literals | 🟢 | Medium | vec_new() + vec_push() → [1, 2, 3]. Makes compiler source readable. |
| 2 | Dogfooding: Map Literals | 🟢 | Medium | hashmap_new() + hashmap_set() → {key: 1}. |
| 3 | Dogfooding: Range Loops | 🟢 | Small | Most already converted. Remaining are complex (non-unit step, mid-loop modify). |
| 4 | Self-Hosted TY_NEVER Gaps | 🟢 | Medium | typecheck.qz doesn’t handle Never in match arms. Blocks macro-heavy code. |
| 5 | Closure-as-Fn Return Unification | 🟡 | Medium | Returning closure as Fn(Int): Int. 1 pending test. Bootstrap only if compiler uses it. |
| 6 | Method Syntax Pending (6) | 🟢 | Small | s.size, s.to_i, s.find, n.to_s. Triage — some may already pass. |
| 7 | Regex Capture Variables (8) | 🟢 | Medium | $0-$9, named groups. Codegen exists, may just need tests fixed. |
| 8 | $try/$unwrap for Result<T,E> | 🟢 | Small | Extend macros for Ok/Err alongside Some/None. |
| 9 | Cross-Module Type Aliases | 🟡 | Medium | type TomlResult = Result<T, E> across modules. 2 pending. |
| 10 | FFI Completion (4) | 🔴 | Large | repr(C) alignment, C interop. Needs bootstrap intrinsics. |
| 11 | Networking Stdlib (5) | 🟢 | Large | TCP, HTTP. Pure library code, no compiler changes. |
| 12 | Tooling: REPL, LSP | 🟢 | Very Large | External tools. Last priority. |
Pending Tests Summary (Feb 3, 2026)
| Category | Count | Notes |
|---|---|---|
| Phase 4 capture groups | 8 | $n regex captures |
| Phase 2 string ops | 6 | s.find, s.slice, n.to_s |
| Networking | 5 | tcp_connect, http_get, URL parsing |
| FFI/C interop | 4 | import C struct/enum/macro/typedef |
| String/allocation | 4 | @heap strings, embedded nulls, cstr conversion |
| Type system | 3 | type aliases, chained assignment, index type check |
| Other | 10 | repr(C) struct, chained methods, match expressions |
| Closure edge case | 1 | Closure-as-Fn return type unification (NEW v5.10.0) |
| Total | 41 |
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 viawrite(2, ...). Dedicated test confirms zero stdout output on panic. - Block expressions: Fixed Feb 3 —
do...endand{ }blocks work as expressions. 5 tests inblock_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.chas its owntypecheck_expr/typecheck_stmtthat runs viacodegen_validate()before MIR pipeline. Must be updated for any new language feature that affects expression types.