Quartz v5.25

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:

  • sendposix_send (or socket_send — wrappers at line 395-401 already exist as socket_send/socket_recv)
  • recvposix_recv
  • Also rename sendtoposix_sendto, recvfromposix_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:

  1. quake guard (fixpoint — the compiler itself doesn’t use these externs)
  2. Run each unblocked spec individually (subprocess specs may hit the separate %push IR bug — see note below)
  3. Verify std/ffi/socket.qz’s own socket_send/socket_recv wrappers still work (they call the renamed externs)

Key files

FileWhat
std/ffi/socket.qz:168,174The extern declarations to rename
std/ffi/socket.qz:395-401Existing wrapper functions that call the externs
self-hosted/middle/typecheck_builtins.qz:820-821send/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:

  1. typecheck_generics.qz:608-613 — marks Iterator-bounded type params
  2. mir_lower.qz:3180-3193 — during specialization, flags params via ibparams
  3. mir_lower_stmt_handlers.qz:1232-1242 — for NODE_FOR, checks if iterator is bounded → calls lower_closure_iteration()
  4. mir_lower_iter.qz:615-722lower_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

  1. Write a minimal reproducer (3-line bounded generic with for x in iter)
  2. Compile with --dump-mir and --dump-ir to see where the block structure breaks
  3. Fix in mir_lower_iter.qz
  4. Run all 8 tests in generic_ufcs_dispatch_spec.qz

Key files

FileLinesWhat
spec/qspec/generic_ufcs_dispatch_spec.qzall8 test cases
self-hosted/backend/mir_lower_iter.qz615-722lower_closure_iteration() — likely fix site
self-hosted/backend/mir_lower_stmt_handlers.qz1232-1242NODE_FOR bounded-param dispatch
self-hosted/middle/typecheck_generics.qz608-613Iterator-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

HoleAST NodeWhat’s MissingFix
#4NODE_WHILEMove states not restored after loop body errorWire tc_move_on_call into the while-loop body handler
#5List comprehensionMove tracking inside comp bodiesWire tc_move_on_call into comprehension body lowering
#8NODE_RETURNReturn expression position doesn’t mark value as movedAdd tc_move_on_call to return handler for Drop types
#10NODE_MATCHMatch subject not consumedAdd tc_move_on_call for Drop-typed match subjects
#11 partialNLL + moveDouble-move after NLL borrow release should errorVerify NLL borrow release properly re-enables move tracking
#12Return borrow escapeMulti-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:

  1. Find the handler for the AST node (e.g., NODE_WHILE handler in typecheck_walk.qz)
  2. Add tc_move_on_call(tc, var_name, context_str, ...) at the appropriate point
  3. Run the spec to verify the error fires

Error codes: QZ1212 (use-after-move), QZ1210 (borrow escape).

Key files

FileWhat
spec/qspec/s25_low_holes_spec.qzTests for open holes
spec/qspec/s25_safety_holes_spec.qzTests for fixed holes (regression guard)
self-hosted/middle/typecheck_registry.qztc_move_value(), tc_move_on_call()
self-hosted/middle/typecheck_walk.qzAST statement handlers
self-hosted/middle/typecheck_expr_handlers.qzAST 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

  1. modules_spec: Tests file resolution using spec/qspec/fixtures/math_helper.qz. Needs the fixtures directory passed via -I. The subprocess test helper at std/qspec/subprocess.qz:508-514 may not pass the right paths.

  2. accept_worker_spec: Uses import std/net/http_server (hierarchical path). File exists at std/net/http_server.qz but 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

  1. Read resolver.qz:278-316 (hierarchical path handling)
  2. For accept_worker_spec: the fix is likely to search include_path + "/" + hierarchical_path + ".qz" by splitting the hierarchical path and walking the directory tree from each include dir
  3. For modules_spec: verify the subprocess test helper passes -I spec/qspec/fixtures

Key files

FileLinesWhat
self-hosted/resolver.qz278-316Hierarchical path resolution
spec/qspec/modules_spec.qz~151File resolution test
spec/qspec/accept_worker_spec.qz1Multi-level import
std/qspec/subprocess.qz508-514Include 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:

  1. Collection stubs (reversed, sorted, unique, flatten, etc.) — collection_stubs_spec, 21 tests, stdlib table stakes
  2. VS Code extension — .vsix build, syntax highlighting
  3. Stdlib narrative guide — “how do I…” documentation
  4. Pattern matrix exhaustiveness — non-exhaustive match detection
  5. String ergonomicsString + Int auto-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 %push IR 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 guard before every commit.