Next Session — Stack-Ranked Remaining Work
Baseline: 29628f7e (post 15b cascade-stop fix, trunk)
Session shape: Attack items 1-4 in order. Each is independent — commit after each one.
Tree state: Clean, guard stamp valid, smoke green.
Pre-flight (2 min)
cd /Users/mathisto/projects/quartz
git log --oneline -3 # 29628f7e at top
git status # clean
./self-hosted/bin/quake guard:check # stamp valid
Item 1: SEND-RECV-SHADOW (1-2h)
The bug
extern "C" def send(sockfd, buf, len, flags) and extern "C" def recv(sockfd, buf, len, flags) in std/ffi/socket.qz:168,174 shadow the 2-arg channel builtins send(ch, val) and recv(ch). The externs win because they’re registered as concrete functions in the resolver’s all_funcs vector, which is checked after builtins in typecheck_expr_handlers.qz:2126-2132.
Any spec importing the prelude chain that uses bare send(ch, val) or recv(ch) gets QZ0200: requires at least 4 arguments, got 2.
Blocked specs
At minimum: backpressure_spec, semaphore_spec, graceful_shutdown_spec. Likely also: concurrency_spec, channel_handoff_spec, unbounded_channel_spec, async_channel_spec, send_sync_spec, async_spill_regression_spec.
Fix
Option A (recommended, simplest): Rename the POSIX externs in std/ffi/socket.qz:
send→posix_send(orsocket_send— wrappers at line 395-401 already exist assocket_send/socket_recv)recv→posix_recv- Also rename
sendto→posix_sendto,recvfrom→posix_recvfrom(preventive)
Zero external users of the socket API. The existing socket_send/socket_recv wrapper functions at lines 395-401 are the intended public API anyway.
Option B: Add arity-aware resolution so send(ch, val) with 2 args resolves to the builtin. More complex, touches the resolver.
Verification
After renaming:
quake guard(fixpoint — the compiler itself doesn’t use these externs)- Run each unblocked spec individually (subprocess specs may hit the separate
%pushIR bug — see note below) - Verify
std/ffi/socket.qz’s ownsocket_send/socket_recvwrappers still work (they call the renamed externs)
Key files
| File | What |
|---|---|
std/ffi/socket.qz:168,174 | The extern declarations to rename |
std/ffi/socket.qz:395-401 | Existing wrapper functions that call the externs |
self-hosted/middle/typecheck_builtins.qz:820-821 | send/recv builtin registration (don’t change) |
Item 2: Generic UFCS Dispatch (1-2d)
The bug
spec/qspec/generic_ufcs_dispatch_spec.qz — 8 tests, all fail. Bounded generic functions like def sum_iter<T: Iterator>(iter: T): Int with for x in iter in the body emit malformed code.
Root cause (from investigation)
The issue is in how for-in loops dispatch on Iterator-bounded type params. The dispatch chain:
typecheck_generics.qz:608-613— marks Iterator-bounded type paramsmir_lower.qz:3180-3193— during specialization, flags params viaibparamsmir_lower_stmt_handlers.qz:1232-1242— for NODE_FOR, checks if iterator is bounded → callslower_closure_iteration()mir_lower_iter.qz:615-722—lower_closure_iteration()generates closure-call blocks
The ROADMAP says the enddef token in the output suggests “iterator closure synthesis breaks the block structure.” Start by compiling a minimal bounded-generic-with-for-in program and examining the IR output. The bug is likely in lower_closure_iteration() around lines 696-722 — block terminator or scope management.
Approach
- Write a minimal reproducer (3-line bounded generic with
for x in iter) - Compile with
--dump-mirand--dump-irto see where the block structure breaks - Fix in
mir_lower_iter.qz - Run all 8 tests in
generic_ufcs_dispatch_spec.qz
Key files
| File | Lines | What |
|---|---|---|
spec/qspec/generic_ufcs_dispatch_spec.qz | all | 8 test cases |
self-hosted/backend/mir_lower_iter.qz | 615-722 | lower_closure_iteration() — likely fix site |
self-hosted/backend/mir_lower_stmt_handlers.qz | 1232-1242 | NODE_FOR bounded-param dispatch |
self-hosted/middle/typecheck_generics.qz | 608-613 | Iterator-bound tracking |
Note: This spec uses import * from qspec/subprocess. Subprocess-based specs currently hit a separate %push IR bug in the qspec infrastructure (see note at bottom). You may need to convert tests to direct (non-subprocess) form to verify, or fix the %push issue first.
Item 3: Move Semantics Enforcement (2-3d)
The bug
6 safety holes remain open in the move checker. The move/borrow infrastructure exists (tc_move_value, tc_move_on_call in typecheck_registry.qz) but isn’t wired into all AST node handlers.
Open holes
| Hole | AST Node | What’s Missing | Fix |
|---|---|---|---|
| #4 | NODE_WHILE | Move states not restored after loop body error | Wire tc_move_on_call into the while-loop body handler |
| #5 | List comprehension | Move tracking inside comp bodies | Wire tc_move_on_call into comprehension body lowering |
| #8 | NODE_RETURN | Return expression position doesn’t mark value as moved | Add tc_move_on_call to return handler for Drop types |
| #10 | NODE_MATCH | Match subject not consumed | Add tc_move_on_call for Drop-typed match subjects |
| #11 partial | NLL + move | Double-move after NLL borrow release should error | Verify NLL borrow release properly re-enables move tracking |
| #12 | Return borrow escape | Multi-level borrow escape (fn A returns &local, fn B calls A) | Extend QZ1210 checks to transitive borrow escape |
Already fixed (don’t re-break)
Holes #3, #6, #7, #9, #13 are all passing. These are in s25_safety_holes_spec.qz.
Approach
The move checker is in self-hosted/middle/typecheck_walk.qz and typecheck_expr_handlers.qz. The pattern for each hole is:
- Find the handler for the AST node (e.g., NODE_WHILE handler in
typecheck_walk.qz) - Add
tc_move_on_call(tc, var_name, context_str, ...)at the appropriate point - Run the spec to verify the error fires
Error codes: QZ1212 (use-after-move), QZ1210 (borrow escape).
Key files
| File | What |
|---|---|
spec/qspec/s25_low_holes_spec.qz | Tests for open holes |
spec/qspec/s25_safety_holes_spec.qz | Tests for fixed holes (regression guard) |
self-hosted/middle/typecheck_registry.qz | tc_move_value(), tc_move_on_call() |
self-hosted/middle/typecheck_walk.qz | AST statement handlers |
self-hosted/middle/typecheck_expr_handlers.qz | AST expression handlers |
Note: Both s25 specs use import * from qspec/subprocess and currently hit the %push IR bug. You’ll need to either fix the %push issue first, or convert these tests to non-subprocess form to verify your changes.
Item 4: Module Path Resolution (Small)
The bugs
-
modules_spec: Tests file resolution usingspec/qspec/fixtures/math_helper.qz. Needs the fixtures directory passed via-I. The subprocess test helper atstd/qspec/subprocess.qz:508-514may not pass the right paths. -
accept_worker_spec: Usesimport std/net/http_server(hierarchical path). File exists atstd/net/http_server.qzbut resolution fails.
Root cause
In self-hosted/resolver.qz:278-316: hierarchical path resolution (resolve_is_hierarchical() detects / in path) only tries base_path/module_name.qz and base_path/module_name/mod.qz, then searches -I include_paths. For std/net/http_server, it needs either -I . (project root) or smarter prefix resolution.
Approach
- Read
resolver.qz:278-316(hierarchical path handling) - For
accept_worker_spec: the fix is likely to searchinclude_path + "/" + hierarchical_path + ".qz"by splitting the hierarchical path and walking the directory tree from each include dir - For
modules_spec: verify the subprocess test helper passes-I spec/qspec/fixtures
Key files
| File | Lines | What |
|---|---|---|
self-hosted/resolver.qz | 278-316 | Hierarchical path resolution |
spec/qspec/modules_spec.qz | ~151 | File resolution test |
spec/qspec/accept_worker_spec.qz | 1 | Multi-level import |
std/qspec/subprocess.qz | 508-514 | Include path setup for subprocess tests |
Known blocker: %push IR bug in qspec subprocess infrastructure
Several subprocess-based specs (s25_low_holes, s25_safety_holes, generic_ufcs_dispatch, and others) produce llc: use of undefined value '%push'. This is a UFCS codegen bug where result.push(...) in std/qspec/subprocess.qz:1096 emits an indirect call through a %push local variable that was never declared.
This is pre-existing (the affected specs never compiled successfully) and appears to be a name-resolution collision in the qspec subprocess module’s import chain. It is NOT caused by the 15b fix.
If this blocks your work on Items 2-3: Either fix it first (grep for push in the import chain to find the collision) or convert the affected tests from subprocess-based assert_run_exits to direct assert_eq-based tests that don’t need subprocess compilation.
After Items 1-4
The next tier (from the prioritized list) is:
- Collection stubs (reversed, sorted, unique, flatten, etc.) —
collection_stubs_spec, 21 tests, stdlib table stakes - VS Code extension — .vsix build, syntax highlighting
- Stdlib narrative guide — “how do I…” documentation
- Pattern matrix exhaustiveness — non-exhaustive match detection
- String ergonomics —
String + Intauto-coerce
Prime directives check
- D1: Items 1-4 are highest-impact for language usability. SEND-RECV-SHADOW is trivial but unblocks 6+ specs.
- D2: Research done for all 4 items. Root causes identified.
- D5: The
%pushIR bug is real. Don’t claim specs pass without running them. - D6: Every hole gets filled or filed. Update ROADMAP after each fix.
- D8:
quake guardbefore every commit.