[pull] master from GaijinEntertainment:master#1037
Merged
Conversation
…verse + plan_distinct
Introduce a two-layer split for the `_fold` splice planner:
1. Pattern recognition — declared via `splice_patterns` data table.
Each row is a `SplicePattern { name, chain : array<Slot>, requires
: array<RequiresPredicate>, emit : EmitFn }`. Slots are
`(SlotMatcher, SlotCardinality, capture_name, arity)` variants.
One generic `match_pattern(p, calls, top) : MatchResult` walker
replaces the 8–30 tracking-flag + if/elif walls in each plan_*.
2. Code generation — reusable emit archetypes parameterized by a
`SourceAdapter` (Array stub in PR A; widens to Decs/DecsFind/Zip
/DecsJoin in PR C/D). One archetype per recognized chain shape.
`plan_reverse` migrates to 5 pattern rows + 5 emit archetypes (Ra
counter, Rb walk-overwrite-last scalar, R6 backward-index walk, R-2a
backward walk with dset gate, R1-R4 buffer + reverse_inplace catch-all).
`plan_distinct` migrates to 2 pattern rows + 1 emit archetype with
internal terminator-shape dispatch. Both stubs delete the imperative
bodies and dispatch through their per-plan pattern tables.
The full 4-PR roadmap and design decisions live in `daslib/linq_fold.md`
(masterplan, living doc). Subsequent PRs B/C/D migrate the remaining
11 plan_* functions and collapse the per-plan tables into one flat
`splice_patterns`.
Behavior unchanged — pure refactor. 600+ tests across 14 linq test
files pass; per-archetype + walker integrity tests added in
`tests/linq/test_linq_fold_pattern_walker.das`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…(no capture needed)
PR A's emit archetypes and predicates are pure — none capture any state.
Lambda was preemptive coverage for hypothetical PR B-D closure factories
(make_arity_eq, make_emit_terminal_select_wrapped, etc.) but forces every
[init] row to wrap a real function in a useless lambda:
emit = @(var c, var top, src, at) => emit_x(c, top, src, at)
vs. with function<> typedef:
emit = @@<EmitFn> emit_x
Same readability, half the LOC per row, no heap allocation for the
function pointer itself (8-byte workhorse value). Predicates likewise
switch from `@(...) {...}` to `@@(...) {...}` and declare as `let`
(workhorse-immutable storage).
When PR B-D actually hits a closure-bearing predicate or emit wrapper,
options are: (a) sibling `EmitLambda = lambda<>` typedef + dispatch on
which kind, (b) variant-RequiresEntry sum type with closure cases.
Both are zero-cost-for-PR-A-shape and reachable from function<>.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three real bugs + one doc-drift fix on the iterator-wrap path that the
imperative bodies had wired up via `expr._type.isIterator` and the refactor
hard-coded to `false`. PR C was the wrong place to defer it — return-shape
is a fold-level concern, not a source-adapter one.
- Kernel: introduce `EmitCtx { top; src; expr_is_iterator }` and thread it
through `EmitFn`. Stubs build it per pattern attempt, pre-cloning `top`
(replacing the `[clone(top)]` annotation which was load-bearing under
direct call but never fired through `invoke()`).
- Bug fix: `emit_reverse_buffer_inplace` and `emit_hashtable_dedup` now
read `ctx.expr_is_iterator` and pass it to `buffer_return`, so
`_fold(seq |> reverse())` / `_fold(seq.distinct())` (no terminator)
return iterator as the chain does instead of array.
- Predicate library: convert the three module-level
`let X : RequiresPredicate = @@(...) { ... }` inline lambdas to named
`def` functions wrapped with `@@<RequiresPredicate>` at use sites. The
inline form produced `_localfunction_*` symbols that the LLVM JIT pass
couldn't resolve — this was breaking every CI build lane.
- Regression test: `tests/linq/test_linq_fold_iterator_wrap.das` —
3 tests / 6 sub-runs asserting `typeinfo typename(got) == "iterator<int>"`
on reverse / distinct / distinct_by chains with no terminator. Verified to
fail when buffer_return wrap is reverted.
- Masterplan `daslib/linq_fold.md`: kernel snippet refreshed to match shipped
code — Captures, MatchResult, EmitCtx, named-predicate idiom. The four
Copilot inline comments on lines 2564 / 2581 / 2881 (linq_fold.das) and
95 (linq_fold.md) are all addressed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The new public `match_pattern` walker has `array<tuple<ExprCall?; LinqCall?>>`
in its signature. With `LinqCall` private, the Sphinx doc generator emits an
undefined-label warning ('struct-linq_fold-linqcall') in the auto-generated
RST, and CI's `Build latex files` step fails with `-W` (warnings-as-errors).
Drop `private` on the struct itself; the `linqCalls` table that holds the
records stays private. Closes the only Sphinx warning surfaced by PR A.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CI's `daslang -exe utils/aot/main.das` was failing with `Internal jit error. Failed to get IR for functions ... emit_reverse_*, emit_hashtable_dedup, ...`. Root cause: `populate_plan_*_patterns` was `[init]` (program startup), making it runtime-reachable. The pattern rows hold `@@<EmitFn>` addresses of `[macro_function]` emit fns whose bodies contain quote() expressions the LLVM JIT cannot lower. The whole transitive closure of macro-time helpers got dragged onto the JIT graph and failed. Switching to `[_macro]` runs the populator at compile time only (before any user `_fold(...)` expands) and keeps the function pointer references off the runtime JIT graph entirely. Add an idempotency guard so re-entry is a no-op. Tests: drop the four runtime-table inspection tests — `plan_reverse_patterns` / `plan_distinct_patterns` are now macro-time-only state, empty at runtime by design. Kept the alias_table test (runtime-literal-initialized) and the chain_prefix_of / synthetic-shadow tests (kernel logic exercised directly). End-to-end pattern selection stays covered by the existing test_linq_fold_*.das suite — each user chain exercises emit fns through `_fold`. Local verification: - `bin/daslang -exe utils/aot/main.das` builds clean (was the failing CI step) - `bin/daslang -jit dastest/dastest.das -- --test ...` (JIT-mode dastest): 6/6 iterator-wrap + 10/10 walker + 36/36 theme8 + 385/385 core fold pass Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- audio_boost.das set3D: revert channel-converter input to 2u (HRTF always
emits stereo; uint(source.channels) corrupts mono 3D sources).
- audio_boost.das end_batch: restore panic("no batch"); drop the redundant
inner `for it; delete it` loop (outer `delete *global_batch` already
finalizes nested arrays).
- audio_boost.das begin_batch/end_batch: mark deprecated, recommend
`batch(cb)`.
- audio_boost.das play_*_from_pcm_stream: unify to sid : SID = INVALID_SID
+ ternary so generate_sound_sid() is only called when caller didn't pass
a SID.
- strudel_player.das + strudel_midi_player.das: add `options persistent_heap`
+ `options gc` (linear allocator leaks on the audio/sequencer threads).
- Lint sweep on the two strudel files surfaced by the options addition:
drop redundant float()/string() casts (PERF020) and unsafe() wraps on
lock_box_create/job_status_create (STYLE024).
Three more fixes flagged by Copilot on the latest commits: - emit_reverse_backward_index_walk (R6, `reverse + take`) — was hard-coding `buffer_return(bufName, false)`. Same root cause as R1's emit_reverse_buffer_inplace / emit_hashtable_dedup fixes: my earlier analysis incorrectly claimed `implicit_to_array` predicate forces array context. It does the OPPOSITE — true when no terminator is captured, i.e. the chain ends bare (could be iterator-typed in outer context). Use `ctx.expr_is_iterator`. - emit_reverse_backward_walk_dset_gate (R-2a, `reverse + distinct[_by]` on array source) — same fix. - chain_prefix_of — was returning true for equal-length chains (the guard used `>` not `>=`). Two patterns with identical chains but different `requires` predicates are both legitimately reachable (walker falls through on requires-failure), so equal-length is not a shadowing relation. Enforce strict prefix (`length(a) >= length(b)` → false). Regression tests added to test_linq_fold_iterator_wrap.das: - test_iterwrap_reverse_distinct_array_source — covers R-2a path - test_iterwrap_reverse_take_array_source — covers R6 path Both verified to fail when their buffer_return wrap is reverted. Local verification: - 10/10 iterator_wrap, 10/10 walker, 36/36 theme8, 385/385 core fold pass - `bin/daslang -exe utils/aot/main.das` builds clean (no JIT regression) - lint + format clean Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Four small fixes flagged on top of R2: - `take_arg_is_int` — guard `take.arguments[1]._type != null` before reading `baseType`. Matches the defensive pattern used elsewhere in the file; avoids a potential macro-time null deref on partially-typed expressions. - `emit_reverse_backward_index_walk` — the takeN clamp ternary spliced `c["take"].arguments[1]` three times. With a side-effecting `take(...)` arg that fires three times instead of once. Bind to a `rtakeLim` local and reuse (mirrors `emit_hashtable_dedup`'s `takeLim` pattern). - `emit_reverse_buffer_inplace` — same 3x splice in the resize clamp. Same fix: bind to a `rev_takeLim` local via `qmacro_block_to_array` and reuse. - masterplan `linq_fold.md` naming table — `Captures` row still showed the old `table<string; tuple<ExprCall?; LinqCall?>>` shape; updated to current `table<string; ExprCall?>` and added `MatchResult` / `EmitCtx` rows. Local verification: - 10/10 iterator_wrap, 10/10 walker, 36/36 theme8, 385/385 core fold pass - lint clean Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New daslib module shipping a `with_` macro that binds array/table
element refs inside a block with an automatic container lock around the
body — push/erase/resize/clear inside the body panic at runtime instead
of silently dangling, matching the safety the typer's
error[31300] (local-ref-to-non-local-expression-is-unsafe) was trying
to enforce.
Surface:
with_(arr[0]) { _.f1 = 99 } // default `_` binding
with_(arr[0]) $(elem) { elem.f1 = 99 } // named binding
with_(arrA[0], arrB[1]) $(a, b) { ... } // multi-arg positional (up to 3)
let v = with_(arr[0]) { return _.f1 } // return-value (single-arg)
with_(tab["k"]) $(v) { v.f1 = 555 } // table; upserts if missing
Phase 1 cuts:
- Container must be arr[i] or tab[k] ExprAt — locals, struct
fields on locals, function-call results refused with a hint to use
built-in `with`.
- Workhorse-element containers (array<int> etc.) refused: mutation
through a by-value block param wouldn't propagate; the user should
write arr[i] = value directly.
- At most one table-keyed arg per call — a 2nd insert/erase would
rehash and invalidate the pinned entry, so the macro refuses any
second table arg.
- Multi-arg + value-returning body refused (helper proliferation);
rewrite as a single-arg with_.
Implementation notes:
- 6 generic helpers (_with_locked_arr1_v / _arr1_r / _tab1_v /
_tab1_r / _arr2_v / _arr3_v) wrap lock + invoke + unlock in
function-level `finally`. Helpers use __builtin_array_lock_mutable
/ __builtin_table_lock_mutable universally (the const-cast forms
accept both const and var containers in one generic instantiation).
- Table helpers resolve the element ref BEFORE locking (in `unsafe`)
because t[k] from inside a locked table hits the insert path and
panics even on existing keys.
- Bodies that panic still leak the lock — daslang's `finally` is
broken on panic per issue #2532, same as
daslib/array_boost::array_view etc. Will tighten once #2532 lands.
Coverage: 31 tests across 5 functional files + 6 expect-fail compile
tests for every refusal path. Tutorial 18 + RST seealso links land in
tutorials/macros/ and doc/source/reference/tutorials/macros/. AOT
registration added to tests/aot/CMakeLists.txt. Module registration
added to doc/reflections/das2rst.das (regen needs a build with dasHV
present; the change matches the existing pattern for `defer`).
Inspired by spiiin's blog macro (https://spiiin.github.io/blog/1637349975/)
but adds the missing lock + multi-arg + macro-time refusals.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot flagged that the new R1-R4 + Rb pattern chains accept BOTH a pre-reverse `_select(f)` and a post-reverse `_select(g)` in the same match, where master's imperative `plan_reverse` had an explicit `!seenSelect` guard that bailed those chains to tier-2 cascade. This is an intentional extension over master, not a recognition bug. The two selects compose cleanly — `pushExpr` uses the pre-projection, the post-select projects the reversed survivors at return. No semantic conflict; just plain function composition over the buffer. Net result: strictly faster (splice instead of cascade) for an obvious chain shape that master couldn't fuse. - 2 new tests in test_linq_fold_terminal_select.das demonstrating both patterns (R1-R4 with `to_array`, Rb with `first`) splice correctly with both selects. - linq_fold.md decision log entry documenting the intentional extension. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per Boris's review: the splice-emit fns weren't being exercised across the full source × terminator matrix. The prior iter_wrap tests covered only the iter-source corners (and partial array-source). Add the missing corners systematically — 17 tests / 34 sub-runs — so each buffer-emitting pattern has all four (or all relevant) cells of the matrix covered: reverse() — 4 corners (iter|array src × iter|array terminator) distinct() / by — 4 corners + 1 distinct_by iter-only carryover reverse() + distinct() — 4 corners reverse() + take(N) — 4 corners `typeinfo typename(got)` is the load-bearing assertion. Array-bound tests use `let got <-` (no consuming for-loop on the binding) and the assertion strings reflect the resulting `array<int> const` binding type. The iter→iter corners cover PR A R1's `buffer_return(..., ctx.expr_is_iterator)` fix on emit_reverse_buffer_inplace and emit_hashtable_dedup; the array→iter corners cover R2's same fix on emit_reverse_backward_walk_dset_gate and emit_reverse_backward_index_walk. The array<int>-const corners protect against future over-correction (always wrapping with `to_sequence_move`). PR B onward should follow this naming convention for new emit fns: test_matrix_<shape>_<src>_to_<term>. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two scope cuts that go together:
1. Drop the `let v = with_(arr[0]) { return _.f }` shape. The user can
trivially write `var v : T; with_(arr[0]) { v = _.f }` and the
return-value path was dragging in two helpers, a body-walks-for-return
detector, an arity-vs-return refusal, and a class of generic-inference
bugs the void form sidesteps.
2. Drop the workhorse-element refusal. With the helper sig now
`block<(var x : TT&) : void>` and the macro emitting block params as
parser-shaped `auto -const removeConstant=true` (matching exactly what
`$(a)` produces directly), daslang's typer drives the inference all
the way to `int&` / `float&` / etc. — no special-case in the macro,
workhorse mutation propagates correctly.
Net diff to the surface: same API minus return-form. Workhorse arrays
and tables now Just Work:
var ints = [1, 2, 3]
with_(ints[1]) { _ = 222 } // ints == [1, 222, 3]
var tab : table<string; int>
tab |> insert("alpha", 10)
with_(tab["alpha"]) $(v) { v = 200 } // tab["alpha"] == 200
Files dropped: `_with_locked_arr1_r` / `_with_locked_tab1_r` helpers,
`block_returns_value` walker, `failed_with_multi_return.das`,
`failed_with_workhorse.das`, `test_with_return.das`.
New: `tests/with_boost/test_with_workhorse.das` (5 tests covering
single-arg / named / float / multi-arg / mixed workhorse+struct).
Tutorial Section 5 and RST Section 3 swap return-value content for
workhorse content; expected-output updated.
28/28 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per-channel runtime routing decision driven by a configurable budget (default 32). Each frame, the closest-to-head channels win the HRTF slots; the rest run simulated 3D (constant-power pan + distance attenuation, no convolution). Set via set_hrtf_budget(n). Sticky margin in rank space (budget + max(2, budget/10)) prevents flapping when channels swap rank between frames, while clamping to 0 when budget=0 so "all simulated" actually clears in-flight HRTF channels. HRTF/simulated branch in mix(): when is_hrtf is true the existing HRTF processing emits stereo for the existing 2-channel converter; when false the HRTF stage is skipped and a second converter built once in set3D (source.channels -> MA_CHANNELS) handles the mono-to-stereo upmix at native rate instead of an interpreted daslang loop. Calibrates the HRTF broadband gain at azimuth=0, elevation=0 once in initialize_mixer using a 1 kHz sine probe (2048 frames, measure last 1536 to skip the 256-sample crossfade region). Simulated-path normalizer is hrirGain * sqrt(2) to compensate the -3 dB constant-power center pan, sanity-clamped to [0.25, 4.0] with a sqrt(2) fallback. DC was the wrong probe in the first attempt: HRIRs attenuate DC by ~10x, so the measured RMS was unrepresentative of the broadband response. Cross-context stats via a caller-provided LockBox. set_audio_stats_box(box) registers a box on the audio context; the audio thread publishes one AudioSystemStats snapshot per ~1-second window with utilization_pct, hrtf_count, total_3d. The example reads via box |> grab(s) on the main thread and prints once per second. Addresses Copilot review comments on #2879: - R1: drop ma_volume_mixer_set_linear_pan(false) from setup3D. The per-channel pan-mode is now driven by the budget; setting it false globally introduced a -3 dB loudness regression on all 3D channels. - R2: derive pause_fade_step field default from MA_SAMPLE_RATE (was hard-coded 1/96, correct only at 48 kHz). Tests: tests/audio/test_hrtf_budget.das covers hrtf_budget_classify exhaustively for budget=0/32/999, sticky-margin behavior, and the regression case (budget=0 must clear in-flight HRTF channels rather than preserving them under sticky-margin). Example update (examples/audio/hrtf/main.das): - B key cycles HRTF budget: mixed top-32 / all simulated / all HRTF. - Per-second stats line printed: utilization, hrtf/total split. - Pre-existing lint sweep: PERF020 redundant int casts on GLFW key constants, STYLE013 SoundSource named-arg ctor, STYLE016/STYLE005/ STYLE024 cleanups. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
release only decrements the LockBox refcount; the actual delete requires lock_box_remove (or it leaks the C++ JobStatus + Feature + 3 smart pointers). Lifecycle now wraps the audio system: - main creates stats_box (rc=1) - enters with_audio_system; set_audio_stats_box adds share-ref (rc=2) - main loop runs, exits with_audio_system block - audio_system_finalize; audio thread releases share-ref (rc=2 to 1) - outer defer; lock_box_remove (rc=1 to 0, delete) Also adds: - get() (read-only) replaces grab() (one-shot consume) for the periodic stats poll - --max-frames N CLI option on the example for headless repro of shutdown-leak debugging Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address Copilot R4 review batch: - Privacy sweep (closes R4-1, R4-2 + your question on public surface): the walker test only needs Slot/SlotMatcher/SlotCardinality + m_*/c_* constructors, SplicePattern, Captures/EmitCtx/EmitFn typedefs, chain_prefix_of, check_pattern_table_reachable, alias_table. Everything else moves to private: LinqCall (reverts 74c5b5c), SourceAdapter, MatchResult, RequiresPredicate, match_pattern, call_norm_name, slot_matchers_equal, slots_structurally_match, the predicate library (array_source / take_arg_is_int / no_terminator), 3 var pattern tables, 6 emit_* fns, both populate_* fns. R4-1 and R4-2 (null-deref worries on synthetic test inputs reaching public call_norm_name / match_pattern) dissolve — only the macro pipeline calls them now, with well-formed AST. As bonus, removing LinqCall from match_pattern's public signature drops the Sphinx cross-ref dependency it was carrying. - R4-3: emit_reverse_walk_overwrite_scalar — defensive bail when projection or top type is partially-typed (null _type / firstType), matching the guard pattern in emit_reverse_backward_index_walk. - R4-6: rename implicit_to_array → no_terminator. The old name read like a return-shape predicate; return shape is actually driven by ctx.expr_is_iterator. Updated linq_fold.md naming table to match. - R4-5: linq_fold.md status — flip the migration-phases table rows for PR A phase 0 + phase 1 from "not started" to "complete" to align with the Status section. Updated the Tests / exports philosophy section to enumerate the actual public surface after this sweep. Test plan: - compile_check clean on daslib/linq_fold.das + test_linq_fold_pattern_walker - lint clean - test_linq_fold_pattern_walker / _iterator_wrap / _terminal_select green - test_linq_fold_ast (228 / 228), _theme8 (36 / 36), _theme3_c1_c5 (24 / 24), _theme45 (32 / 32), _non_copyable_default (6 / 6) all green Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Addresses Copilot R3: the docstring claimed null clears stats publication, but the body unconditionally called add_ref/set on the box, crashing on null. Add an early-return branch that pushes a 0 box pointer; the audio thread's command handler already reinterprets that as null and clears g_stats_box. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewrite of the with_ macro after the helper-per-arity approach hit its
ceiling. Inline emission scales to any N container args mixing array +
table freely, removes the 4 _with_locked_* helpers entirely, and fixes
a latent bug where multiple $e(subexpr) splices re-evaluated the same
container expression (fatal for array literals — three different temp
arrays got locked, invoked, unlocked).
Emission shape, fully spelled out at each call site:
{
var __with_c_0 & = unsafe(arr1) // pre-bind each container
var __with_c_1 & = unsafe(arr2) // once, ref a local thereafter
var __with_tref_2 & = unsafe(tab[k]) // table: pre-resolve element
__builtin_array_lock_mutable(__with_c_0)
__builtin_array_lock_mutable(__with_c_1)
__builtin_table_lock_mutable(__with_c_2)
invoke(<user_block>, __with_c_0[i1], __with_c_1[i2], __with_tref_2)
__builtin_table_unlock_mutable(__with_c_2) // reversed
__builtin_array_unlock_mutable(__with_c_1)
__builtin_array_unlock_mutable(__with_c_0)
}
No `finally` — daslang panic is fatal (not a C++/JS exception), so if
the invoke panics the program is exiting anyway and skipped unlocks
don't matter. This is the correct semantics, not a bug — see CLAUDE.md
update and the now-closed issue #2532.
Container constraint: subexpr must be an ExprVar-rooted lvalue chain.
Array literals / function-call results have temp lifetime that ends
with the surrounding expression, so ref-binding them would dangle.
Refused at macro time via is_lvalue_chain() — failed_with_array_literal
covers the diagnostic.
Block-param typing: every param is pinned to the container's element
type with ref flag set, so workhorse types bind as int& / float& / etc.
and mutation propagates. No struct-vs-workhorse special case in the
macro.
Tests (30 total, all passing):
- test_with_array.das (6), test_with_table.das (5),
test_with_workhorse.das (5), test_with_lock_panics.das (7)
- test_with_n_arg.das (3) — NEW — 5/7 arrays, mixed 4-array+1-table
proving inline emission has no arity cap
- failed_with_*.das (5) refuse tests, including new
failed_with_array_literal.das for the temp-lifetime refusal
Docs / memory / issues:
- CLAUDE.md: new "Panic is fatal, not an exception" paragraph under
Error handling, documenting that try/recover is for
diagnostics-before-exit and `finally` skipping on panic is by design
- feedback_finally_skipped_on_panic.md flipped from "bug, issue #2532"
to "by design, closed not-planned"
- feedback_no_try_recover_for_soft_fail.md tightened framing
- tutorial Section 7 reworded — was demonstrating try/recover catching
the lock panic, now documents the would-panic shape in a comment
(consistent with the no-recover-and-continue framing)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
R4: rename g_stats_window_time_us to _ms — the variable accumulates milliseconds (the dt_ms input from get_time_usec()/1000), but the `_us` suffix and surrounding comments implied microseconds. The math worked because everything was ms; the rename removes a unit-bug trap. R5: skip 3D channels in the global_volume handler's per-channel ramp loop. update_hrtf overwrites volume_mixer for every is3D channel each audio callback (reading g_volume into the target), so the 25ms ramp applied here for 3D channels was immediately discarded next callback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- tests/aot/CMakeLists.txt: WITH_BOOST_AOT_GENERATED_SRC was generated
via DAS_AOT but never added to test_aot's source list or
SOURCE_GROUP_FILES — fixed both so the new AOT stubs actually
compile/link into test_aot. (Copilot catch — real bug.)
- tutorials/macros/18_with_boost.das: "Covers" list dropped the
return-value bullet and the workhorse-refusal bullet (both
inaccurate post-rewrite); added workhorse + tables + lock bullets.
- doc/source/reference/tutorials/macros/18_with_boost.rst: Section 1
no longer claims workhorse types are refused.
- tests/with_boost/test_with_array.das: test_field_chain_container
now actually exercises an ExprField-rooted lvalue chain
(obj.children[i]) — was using plain arr[1], which didn't match
the test's own description.
- tests/with_boost/failed_with_two_tables.das: comment now describes
the actual rule (max one table-keyed arg per call) instead of
"2nd-arg is a table" which read like a one-off restriction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- R5-1 / R5-2: guard both populate_plan_*_patterns [_macro] initializers
with is_compiling_macros_in_module("linq_fold"), matching the
established daslib convention (ast_boost.das:1079, match.das:764,
validate_code.das:183). Combined with the existing !empty() idempotency
check via || to satisfy STYLE016.
- R5-3: decision log entry for Captures was stale — claimed
tuple<ExprCall?; LinqCall?> but the actual typedef simplified to
ExprCall? during PR A R1 (LinqCall record recovered on demand via
linqCalls[call_norm_name(c)]). Updated the entry to reflect the
shipped shape and note the dropped initial sketch.
Test plan:
- compile_check + lint clean
- test_linq_fold_pattern_walker / _iterator_wrap / _terminal_select /
_theme8_fusion_arms all green
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two real fixes from the second Copilot review pass:
- daslib/with_boost.das: `is_lvalue_chain` no longer follows ExprAt /
ExprSafeAt hops. A subexpr like `outer[i].inner[j]` would have
been accepted, locking only `inner` — leaving `outer` mutable so
a body could push to `outer`, reallocate it, and dangle the
`inner` ref. Now refused at macro time; new
`failed_with_nested_array.das` covers the diagnostic.
- daslib/with_boost.das: dropped the module-header line about
"temporary-typed (Foo#) containers are bound without a lock" —
obsolete since the rewrite. Every accepted container now goes
through the same pre-bind + lock + invoke + unlock path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
R6: reset the rolling window (g_stats_window_time_ms, g_stats_window_samples) when system_stats_box is swapped or cleared. Without this, a re-registered box could publish its first utilization computed against accumulator state left over from the previous registration. R7: drop g_stats_window_count — it was incremented but never read. The flush gate is realTimeMs >= 1000, not a callback-count threshold; the variable and its misleading "once >=N callbacks we publish" comment were vestiges of an earlier design. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
das2rst.das generates a stub module-with_boost.rst when the module is registered but has no handmade description; CI's "Check for // stub in handmade docs" step caught this. Modeled on module-defer.rst — short prose description + require snippet + a small runnable example.
- R6-1: real bug in emit_reverse_walk_overwrite_scalar — when the chain has BOTH a post-reverse _select(g) AND first_or_default(d), the user's `d` is already typed at the post-select element type (that's the user-facing contract for first_or_default). The emit was applying termsel(d) for dRetExpr, which double-applies g and either miscomputes or breaks typing. Fix: dRetExpr = qmacro(dBindName) — bare, never project. lastRetExpr (the found-branch) still projects correctly. Regression coverage added in test_linq_fold_terminal_select.das: test_reverse_pre_and_post_select_first_or_default_nonempty (exercises the found branch) and _first_or_default_empty (exercises the default branch — empty where filter, expected -99, would have returned -98 under the bug). - R6-2: slot_matchers_equal treated `one_of` as order-sensitive (element-by-element equality). Since `one_of` is semantically a set, two `one_of` matchers with the same elements in different order should compare equal — otherwise chain_prefix_of / check_pattern_table_reachable miss real shadowing cases. Fixed to set-membership (same length + every element in a is in b). Forward-looking — current pattern rows use only `literal` and `alias` matchers. - R6-3: masterplan kernel-section heading said "PUBLIC for testability", but R4 narrowed the actual public surface to a subset (the Tests / exports philosophy section reflects this). Updated the kernel heading to point at that section as the authoritative visibility statement and noted the snippet omits `private` for readability. Test plan: - compile_check + lint clean - terminal_select 28/28 (incl. 2 new first_or_default tests) - pattern_walker / iterator_wrap / theme8_fusion_arms green Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three Sphinx errors from the CI doc build:
- doc/source/stdlib/generated/with_boost.rst:6 + :64 "Unknown
target name: 'with'" — das2rst.das passed
`"with_ macro: locked array/table element binding"` as the
module description; RST parsed the bare `with_` (trailing
underscore = hyperlink reference syntax) as a broken target.
Wrapped in double-backticks: ``with_``.
- doc/source/reference/tutorials/macros/18_with_boost.rst:9
"Title overline too short" — title source (incl em-dash) was
65 chars, overline was 64. Bumped overline / underline length
and dropped the stray leading space on the title line.
- doc/source/stdlib/generated/with_boost.rst "document isn't
included in any toctree" — added the generated file to
sec_annotations.rst alongside defer (closest neighbour in the
"Annotations and Contracts" bucket; with_ is a scope/lifecycle
helper of the same family).
The asan/aot CI jobs run `test_aot -use-aot dastest --use-aot`, which sets `policies.fail_on_no_aot = true`. Without a generated AOT stub, linking fails with `error[50101]: AOT link failed on <test fn>`. Adding the test to AOT_STRUDEL_FILES wires it into the same AOT generation pipeline as every other audio-dependent test file. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- R7-1: any_terminator_family alias originally listed count / long_count / sum / first / first_or_default, but the only consumer (plan_distinct_patterns → emit_hashtable_dedup) rejects first[_or_default] with return null. The walker matched, the emit returned null, the loop fell through to the next pattern or to cascade — wasted match work. Rename + narrow: distinct_terminator_family = [count, long_count, sum]. Walker test updated to assert the new name + 3-element length. - R7-2: masterplan grammar-kernel snippets showed the projected end-state (all PR B/C/D aliases + parameterized take_arg_is_int) without flagging which entries are live today vs planned. Annotated both tables with per-row status (PR A ✓ / planned) and added a note pointing at the authoritative live list in linq_fold.das. Removed the misleading any_terminator_family entry; documented why take_arg_is_int is hard-wired today (single consumer) and when to promote to factory. Test plan: - compile_check + lint clean - pattern_walker / iterator_wrap / terminal_select / theme8 / theme45 green Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
das2rst's document_call_macro falls back to emitting bare "Function annotation <name>" text when the class has no //! docstring that write_to_detail can stash into detail/function_annotation-*.rst. For `with_`, the trailing underscore at end-of-line then parses as an RST hyperlink reference, producing: generated/with_boost.rst:64: ERROR: Unknown target name: "with". Add a //! docstring inside the WithMacro class body — matching the LpipeMacro shape in daslib/lpipe.das — so the fallback never fires. The docstring keeps `with_` wrapped in double-backticks (inline literal), preventing the same parse hazard. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Default-`_` rewrite path now clones `userBlock.finalList` alongside
`userBlock.list`. The parser doesn't accept `with_(...) {...} finally {...}`
(the trailing `finally` is rejected as a syntax error in call-arg position),
so this is defensive AST hygiene rather than a user-reachable bug — but a
future macro that constructs a finalList-bearing block via qmacro and
passes it to `with_` would have silently lost the cleanup section.
- Tutorial RST: drop the hard-coded panic string `"can't push into locked array"`
(the runtime message is actually `"can't resize locked array"`). Replace with
a neutral `// panics — array is locked` so the text doesn't go stale.
- Tutorial das + RST: clarify the single-table rule. The macro refuses the
2nd table-keyed arg even when the two tables are statically distinct (no
alias analysis to prove distinctness), not just the same-table case.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…R B) Copilot R8 flagged a real perf regression: pre-PR-A imperative plan_reverse / plan_distinct accepted N consecutive _where calls and merged predicates via merge_where_cond (decs mirror plan_decs_reverse still does). PR A's pattern rows allow a single optional where_ slot, so chains like ..._where(p1)._where(p2).reverse()... no longer splice and cascade. Correctness unchanged (cascade works); medium severity (uncommon shape — users typically write _where(p1 && p2) directly). Fix is well-scoped: collapse_chained_wheres pre-pass mirroring collapse_chained_selects (~30 LOC), call from both stubs, no walker / emit / pattern-row changes. Added KR-1 to a new "Known regressions to address in follow-ups" section in linq_fold.md with the fix sketch and owner PR (PR B). PR description updated to call out the regression explicitly under the "Behavior near-unchanged" claim. No code change — defer is the agreed action with the user. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- macro_verify call.arguments[i]._type is non-null before clone_type (defensive guard; in practice the typer fills it whenever subexpr._type is filled, but the cheap check rules out null deref). - Stamp rewrittenInner.returnType to void in the explicit-params branch too. The default-_ branch already constructed newBlock with void; the else branch was inheriting whatever returnType the user-block had. Now both shapes consistently reject `return value` inside the body via typer rather than silently accepting it. - Use make_unique_private_name(...) for the per-container locals (__with_c_<i>, __with_tref_<i>) so name collision with a user var of the same name is impossible. Matches the hygiene convention from assert_once / async_boost / coroutines / match. - test_with_lock_panics.das: add `options no_unused_block_arguments = false` since the $(a) / $(v) / $(va, vb) block params exist for syntax coverage but the bodies only exercise the lock-vs-mutation path on the container. Also retire the stale "(daslang issue #2532)" reference in the file docstring — #2532 was closed not-planned because finally-skipping-panic is by design (panic-is-fatal policy from CLAUDE.md). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-followup audio: HRTF budget + simulated-3D mixing, plus #2872 features + review fixes
…atterns-foundation linq_fold: PR A — pattern-table refactor foundation + migrate plan_reverse + plan_distinct
…ates
Foundation for the next round of pattern-table migrations. Standalone-shippable
even before any plan_* body actually uses the new aliases/predicates: the
collapse_chained_wheres helper alone closes KR-1 (the PR A regression on
`..._where(p1)._where(p2)...` chains).
Changes:
- New helper `collapse_chained_wheres` (~60 LOC, mirror of `collapse_chained_selects`
shape but composes via `merge_where_cond` instead of function compose).
Wired into `plan_reverse` and `plan_distinct` stubs alongside the existing
`collapse_chained_selects` call. No has_sideeffects bail needed — composition
doesn't duplicate either predicate body (each runs at most once per element,
same as the imperative chain with short-circuit `&&`). **Closes KR-1.**
- New aliases for upcoming `plan_loop_or_count` + `plan_order_family` rows:
`order_family`, `range_op_family`, `accum_family`, `early_exit_family`,
`loop_terminator_family`. Per the masterplan, aliases land when first
consumer needs them — these all get a consumer in the PR B1 / B2 migrations.
- New predicates:
- `inline_cmp_available` — true only for `order_by[_descending]` with an
inline-splice-able key lambda (consumed by `order_streaming_min` /
`order_bounded_heap` rows in PR B2)
- `has_where_or_distinct` — disambiguates `order_fused_prefilter` row from
bare `order_buffer_helper_dispatch` (consumed by PR B2)
- PR B sketch in `linq_fold.md` carrying row shapes / emit archetypes / LOC
budget / co-occurrence audit notes. Status section split: PR B1 (this PR)
closes KR-1 + foundation + plan_loop_or_count; PR B2 follow-up handles
plan_order_family in isolation.
Tests:
- NEW `tests/linq/test_linq_fold_collapse_chained_wheres.das` (10 tests /
18 sub-runs): N=2 and N=3 chains on both `plan_reverse` and `plan_distinct`
surfaces; edge cases (single where unchanged, wheres separated by select
don't collapse).
Test plan:
- compile_check + lint clean
- All 18 KR-1 regression tests pass
- All PR A tests still green: pattern_walker 10/10, iterator_wrap 34/34,
terminal_select 28/28, theme8_fusion_arms 36/36
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…1ab4 daslib/with_boost: with_ macro (inline-emit, any-arity, mixed array+table)
Kernel extensions enabling variable-shape chain heads in pattern rows:
- `Captures` is now a wrapper struct `{ single, many }`. `single` holds
c_one/c_opt captures; `many` holds c_chain captures (`array<ExprCall?>`).
Mechanical migration of ~47 PR A access sites: `c["x"]` → `c.single["x"]`.
- New `SlotCardinality.chain` arm + `c_chain()` constructor + `slot_chain_of(
names, cap)` convenience helper. Walker gained a greedy match-while-in-set
branch; empty match still creates the `many` entry so emit fns can rely on
`c.many |> key_exists("…")`. Private `slot_matches_call` extracted from the
walker, reused by both scalar and chain arms. `slots_structurally_match`
lint helper extended to compare all 3 cardinality arms.
- `emit_array_lane` signature refactored: `var expr : Expression?` → `isIter :
bool`. The only use of `expr` was reading `_type.isIterator`; the new emit
fn passes `ctx.expr_is_iterator` directly.
Migration of `plan_loop_or_count`:
- Imperative ~210-LOC body deleted. New stub: flatten → collapse_chained_*
pre-passes → match_pattern → invoke emit. Single pattern row with c_chain
head matching `["where_", "select"]` greedy, then canonical-order positional
slots (skip / skip_while / take_while / take / post_take_where / term).
- `emit_loop_or_count_lane` (~180 LOC, verbatim lift) walks `c.many["head"]`
applying the same where_/select arm logic (AND-merge, chained-select
rebinding, where-after-select projection-replace) the imperative loop did.
Range ops + post-take-where + terminator read from `c.single[…]`. Fast paths
(length / any-empty) and lane dispatch (counter / array / accumulator /
early-exit) unchanged.
- `loop_terminator_family` alias gained the 6 missing EARLY_EXIT terminators
(`last` / `single` / `element_at` × `_or_default`). First cut missed them;
matrix run caught it via `test_linq_fold_ast` "expected 1 for-loop, got 0"
failures (terminator wasn't matching → planner cascaded to tier-2).
KR-1 closure: `collapse_chained_wheres` (shipped in foundation commit) is now
wired into all 3 stubs (plan_reverse, plan_distinct, plan_loop_or_count).
Tests:
- `tests/linq/test_linq_fold_pattern_walker.das` — 3 new c_chain tests
(constructor shape, lint helper distinguishes c_chain, reachable-table
accepts c_chain heads). Semantic c_chain coverage is end-to-end via
test_linq_fold_loop_or_count.
- `tests/linq/test_linq_fold_loop_or_count.das` (new) — 11 tests / 22 sub-runs
covering canonical 4 lanes, where-after-select rebinding, multiple wheres
post-select, range chains, post-take-where, length / any-empty fast paths.
Validation: full linq matrix (26 files, 1467 sub-runs) + decs matrix (25
files, 235 sub-runs) green. Lint clean.
Masterplan updated: PR B1 marked complete; PR B2 (`plan_order_family`) stays
deferred (foundation it needs already shipped in B1). Kernel snippet, naming
table, walker contract, alias table, predicate library, decision log, and
KR-1 row all reflect what shipped.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pre-push hook caught spacing on `@@<EmitFn>` literals; the formatter canonicalizes to `@@ < EmitFn >`. Mechanical fix; no code change. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pre-push lint caught var→let opportunity on the structural-match test arrays. The arrays are passed by-reference to chain_prefix_of and never reassigned. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…attern-table-prb linq_fold PR B1: c_chain cardinality + plan_loop_or_count migration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )