fio UTF-8 everywhere; fread absorbs pipes; collect banner rides force_inscope_pod - #3604
Conversation
das strings are UTF-8 by convention - every fio boundary now converts explicitly instead of letting Windows interpret the bytes in the ANSI codepage. das_to_path/ path_to_das wrap every std::filesystem call (16 sites - misread names in, ANSI system_error out, both gone); dir/chdir/getcwd/mkdir/rmdir and the ec-variants go through the wide CRT; rmdir_rec walks wide; normalizeFileName uses GetFullPathNameW. The dir_rec representability skip is no longer needed - names always convert. tests/fio/fio_utf8.das pins the surface (20 subtests, 9 were red on Windows). fread sized reads from fstat().st_size, which is 0 for pipes - a silent empty read. The stat is already in hand, so one bit-test picks the path: regular files keep the exact single-read, everything else reads to EOF with a growing buffer - the industry shape (Python readall, C# ReadAllBytes, Rust fs::read all treat st_size as a hint). fread_to_eof existed only to work around this and is deleted; all 19 callers migrate to plain fread and shed their unsafe wraps. tests/fio/popen_argv.das is the pipe pin. Rides along: do_grep_usage split for STYLE038, a LINT016 ':=' fix in the parallel lint test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…llect_local The E4.5 finalize banner lowered erase/clear/shrink drops to per-element delete - instantiating and running finalizer codegen that old user code never triggered. Replace the mechanism: builtin_collect_local, the no-memset sibling of builtin_collect_local_and_zero, frees a dropped element's owned heap through the GcPod walk, which by construction cannot execute user code (pointers and lambdas are not followed; the container disposes of the slot bytes itself). The gate moves with it: needs_container_finalize now rides force_inscope_pod - the same policy that governs collect on move-assign and scope exit - not default_init_containers, which stays the construct-half knob. isPodDelete + isSafeToDelete still bound what is collectable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the fio boundary to treat das strings as UTF-8 consistently (not ANSI on Windows), folds the former fread_to_eof behavior into fread so pipes/streams are read correctly, and reworks the container “finalize banner” into a “collect banner” that uses builtin_collect_local and is gated by force_inscope_pod.
Changes:
- Windows fio: route
std::filesystemand CRT path operations through explicit UTF-8↔UTF-16 conversion (wide CRT / wide iteration) and add a new UTF-8 surface test suite. - fio: make
freadpipe-safe by reading-to-EOF for non-regular files (and size-0 regulars), deletefread_to_eof, and migrate in-tree callers. - Containers: introduce
builtin_collect_localand switch erase/clear/shrink/pop collection to it; moveneeds_container_finalizegating toforce_inscope_pod.
Reviewed changes
Copilot reviewed 35 out of 35 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| utils/preflight/main.das | Updates popen capture to use new pipe-safe fread. |
| utils/mcp/tools/grep_usage.das | Uses fread for tool probing and refactors output formatting into a helper. |
| utils/mcp/tools/cpp_format_file.das | Uses fread for clang-format --version probing. |
| utils/jobque-timeline/tl_launch.das | Uses fread for popen output capture. |
| utils/dasHerd/watcher/tests/test_watcher_protocol_integration.das | Uses fread for protocol-test capture. |
| utils/dasHerd/watcher/tests/test_ptyhost.das | Uses fread for ptyhost launcher capture. |
| utils/dasHerd/watcher/tests/test_changelist_git_cycle.das | Uses fread for git-cycle capture. |
| utils/dasHerd/watcher/repository_core.das | Uses fread for git output capture. |
| utils/dasHerd/chunk1/benchmark_session_host.das | Uses fread for file and subprocess stream draining. |
| utils/dasHerd/chunk0/git_terminal_probe.das | Uses fread for probe output draining. |
| utils/common/parallel_workers.das | Uses fread for worker subprocess capture. |
| tests/lint/test_parallel_equivalence.das | Uses fread for popen output capture in lint test. |
| tests/language/container_init_traits.das | Pins needs_container_finalize to the force_inscope_pod policy in tests. |
| tests/language/container_init_off.das | Updates expectations/docs around policy split (construct vs collect). |
| tests/language/container_finalize.das | Updates banner contract text and enables force_inscope_pod for heap-delta assertions. |
| tests/fio/popen_argv.das | Pins pipe behavior via fread in popen tests. |
| tests/fio/fio_utf8.das | New test suite covering UTF-8 filenames across fio APIs (mkdir/stat/dir/dir_rec/path helpers/mutations/cwd). |
| src/simulate/simulate_gc.cpp | Adds builtin_collect_local (GcPod walk without zeroing) for container slot collection. |
| src/misc/sysos.cpp | Switches normalizeFileName to wide WinAPI for UTF-8 correctness (but see review comment re: strict UTF-8 validation). |
| src/builtin/module_builtin_runtime.cpp | Registers builtin_collect_local interop. |
| src/builtin/module_builtin_fio.cpp | Implements UTF-8 path conversions for filesystem boundaries; makes fread pipe-safe; migrates Windows dir/rmdir_rec and other ops to wide rails; removes fread_to_eof export. |
| src/ast/ast_infer_type.cpp | Changes needs_container_finalize trait to depend on force_inscope_pod (not default_init_containers). |
| modules/dasLLAMA/tests/run.das | Migrates pipe drain to fread. |
| modules/dasLLAMA/performance/profile_common.das | Updates docs/comments + uses fread for capture. |
| modules/dasLLAMA/benchmarks/setup_lcpp_ref.das | Updates docs/comments + uses fread for capture. |
| modules/dasImgui/widgets/imgui_playwright.das | Uses fread for host capture/drain to avoid pipe-size issues. |
| modules/dasImgui/text/imgui_text_edit_provider_cpp.das | Uses fread for compiler/version probing and check output capture. |
| include/daScript/simulate/aot_builtin.h | Declares builtin_collect_local for AOT surface. |
| include/daScript/simulate/aot_builtin_fio.h | Removes builtin_fread_to_eof declaration. |
| doc/source/stdlib/handmade/function-fio-fread_to_eof-0x69d02a1f4419cdb.rst | Removes fread_to_eof stdlib documentation stub. |
| doc/source/reference/language/options.rst | Documents collect-vs-construct policy split and expands force_inscope_pod semantics. |
| doc/source/reference/language/generic_programming.rst | Updates needs_container_finalize trait definition to match new gating. |
| daslib/fio.das | Migrates popen capture helper(s) to fread. |
| daslib/builtin.das | Switches erase/clear/shrink collection to builtin_collect_local. |
| CLAUDE.md | Updates “collect banner” documentation to match new behavior/gating. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // das strings are UTF-8; the ANSI variant misreads non-ANSI names, so go wide | ||
| wchar_t wideName[MAX_PATH]; | ||
| if ( !MultiByteToWideChar(CP_UTF8, 0, fileName ? fileName : "", -1, wideName, MAX_PATH) ) | ||
| return ""; |
There was a problem hiding this comment.
Fixed in d7badf1 - MB_ERR_INVALID_CHARS now, matching utf8_file_path_to_wide; invalid input returns empty instead of a replacement-char path.
MB_ERR_INVALID_CHARS instead of the permissive conversion - invalid input returns empty rather than silently normalizing a replacement-char path (Copilot review consistency catch). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| addExtern<DAS_BIND_FUN(builtin_spawn_argv)>(*this, lib, "spawn_argv", | ||
| SideEffects::modifyExternal, "builtin_spawn_argv") | ||
| ->args({"args","context","at"})->unsafeOperation = true; | ||
| addExtern<DAS_BIND_FUN(builtin_popen_argv)>(*this, lib, "popen_argv", | ||
| SideEffects::modifyExternal, "builtin_popen_argv") | ||
| ->args({"args","timeout","scope","context","at"})->unsafeOperation = true; | ||
| addExtern<DAS_BIND_FUN(builtin_popen_argv_pipe)>(*this, lib, "popen_argv_pipe", | ||
| SideEffects::modifyExternal, "builtin_popen_argv_pipe") | ||
| ->args({"args","scope","context","at"})->unsafeOperation = true; |
There was a problem hiding this comment.
Fixed in 422290c - both telegram example files migrated (the sweep had covered daslib/tests/utils/modules but not examples/); a whole-tree grep confirms zero remaining call sites, and both files dry-run compile clean.
The caller sweep covered daslib/tests/utils/modules but not examples/ (Copilot review catch). Both files dry-run compile clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The CI changed-file lint gate flagged pre-existing STYLE037/038 in files the migration touched: setup_lcpp_ref main (81 lines) extracts build_flavor; run.das main (complexity 23) extracts build_env_prefix; run_terminal_probe (92 lines) extracts finish_probe_sample; test_ptyhost_lifecycle (150 lines) extracts expect_rejected (deduping the three adversarial clients), wait_for_stamp, and probe_shutdown_and_stamps. ptyhost lifecycle test runs green live; the CLIs dry-run compile clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.
Suppressed comments (3)
modules/dasLLAMA/performance/profile_common.das:385
- This comment still describes the old fread behavior (size-via-seek and empty output on non-seekable pipes). In this PR, fio::fread was updated to read non-regular files/streams to EOF, so the comment is now incorrect.
// Run `cmd`, return its trimmed stdout (first line only when `first_line`). Uses fread (fio's
// pipe-safe reader) — fread(f) whole-file sizes via seek and returns empty on a non-seekable pipe.
daslib/builtin.das:2166
- This comment still calls this a "finalize banner", but the implementation now uses builtin_collect_local (collection via GcPod, no user finalizers). Renaming it here keeps the terminology consistent with the rest of the PR and avoids implying user finalizers may run.
// the finalize banner: release every value's heap (raw-pointer values carved out)
static_if (typeinfo needs_container_finalize(type<VT>)) {
modules/dasLLAMA/performance/profile_common.das:349
- This docstring says fread was losing multi‑KB pipe output on Windows; after this PR’s fio fix, fread(f) is now pipe-safe, so that rationale is no longer accurate. Please update the wording to avoid suggesting a still-present bug.
This issue also appears on line 384 of the same file.
//! Capture twin of [[run_and_stream]] — argv spawn (no shell parse, the Windows cmd quoting
//! traps can't bite), stdout+stderr merged by popen_argv, whole output returned trimmed.
//! Reads via the same fgets loop as run_and_stream — fread lost multi-KB pipe output
//! on Windows (zen2: a 95-s onnx child's rows captured as empty; the line loop never has).
…e fixed bug Copilot suppressed-comment catches: builtin.das table/array banner comments said 'finalize banner' after the mechanism became collect; profile_common's two capture helpers described fread's pre-fix pipe behavior as current. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two bundled slices, per the CI-batching call.
fio: UTF-8 everywhere
das strings are UTF-8 by convention; every fio boundary now converts explicitly instead of letting Windows interpret the bytes in the ANSI codepage:
das_to_path/path_to_daswrap all 16std::filesystemcall sites — fixes both directions (UTF-8 names misread on the way in;path::string()throwingsystem_errorfor unrepresentable names on the way out). Built onMultiByteToWideChar/WideCharToMultiByte, so invalid input degrades to an empty path / U+FFFD instead of throwing.dir/chdir/getcwd/mkdir/rmdirand the error-variants go through the wide CRT;rmdir_recwalks wide;normalizeFileNameusesGetFullPathNameW.dir_recrepresentability skip (7b5de4c) is gone — names always convert now, so files stop being invisible.tests/fio/fio_utf8.daspins the surface: 20 subtests over Cyrillic/CJK/emoji names (mkdir, stat, round-trip, dir, dir_rec, path helpers, rename, copy, remove, rmdir, file_size, cwd). 9 were red on Windows before the fix.fread absorbs pipes; fread_to_eof deleted
freadsized reads fromfstat().st_size, which is 0 for pipes — a silent empty read (this killed the cpp text-edit provider throughpopen_argv). The stat is already fetched, so the detection is one bit-test: regular files keep the exact single-read path, everything else reads to EOF with a growing buffer — the same shape Pythonreadall, C#ReadAllBytes, and Rustfs::readuse (st_size is a hint, not a contract).fread_to_eofexisted only as the workaround and is deleted; all 19 callers migrate to plainfreadand shed theirunsafewraps.tests/fio/popen_argv.dasis the pipe pin.containers: the collect banner rides force_inscope_pod
Fallout follow-up to #3602: the finalize banner lowered erase/clear/shrink drops to per-element
delete, instantiating and running finalizer codegen old user code never triggered. Replaced withbuiltin_collect_local— the no-memset sibling ofbuiltin_collect_local_and_zero— which frees a dropped element's owned heap through the GcPod walk and cannot execute user code by construction (pointers and lambdas are not followed; the container disposes of the slot bytes itself). The gate moves with it:needs_container_finalizeridesforce_inscope_pod(the policy already governing collect on move-assign and scope exit, default OFF), decoupled fromdefault_init_containers, which stays the construct-half knob.isPodDelete + isSafeToDeletestill bound what is collectable.Verification
tests/fio/fio_utf8.das: 20/20 (was 9 red); fio dir: 222 passed, 0 failed, 1 skipped.options force_inscope_pod): all green.sphinx-build -Wgreen for HTML and LaTeX..dasfiles.Heads-up for external repos:
fread_to_eofis gone — replace withfread(drop theunsafe).🤖 Generated with Claude Code