[pull] master from GaijinEntertainment:master#1011
Merged
Conversation
…h bench suite
* New daslang_insert_only_hash_{map,set} in include/das_hash_map/das_hash_map.h:
strict subset of the regular API (no erase, no tombstones, no rehash_same_capacity).
reserve_slot drops the HASH_KILLED branch + insertI tracking; iterator skip is
"== HASH_EMPTY" instead of "<= HASH_KILLED". Same layout, same load factor cap,
same find_index. Same hashing.
* Switch 8 Module class fields in include/daScript/ast/ast.h that are never
erased anywhere in the codebase (audited via grep across src/ and modules/):
handleTypes, callThis, typeInfoMacros, annotationData, requireModule,
typeMacros, readMacros, options.
Type signal that these tables are grow-only by design.
* include/daScript/das_config.h: add das_insert_only_{hash_,}{map,set} aliases.
Graceful std::unordered_* fallback under DAS_CUSTOM_HASH=0 (API superset).
* include/daScript/misc/das_common.h: ordered() overloads for the new types.
* include/daScript/ast/ast_serializer.h + .cpp: AstSerializer operator<< +
serialize_hash_map overloads for the insert-only map.
* examples/hash/: standalone bench suite (modeled on examples/sort/) with
three executables - main matrix (std vs das vs absl, 5 key shapes,
insert/churn/find x10, 270 cells); hash function vs table mechanics
isolation (2x2 of {das_hash_map, absl::flat_hash_map} x {daslang_hash,
absl::Hash}); insert-only vs regular comparison on find x10.
Tests: 8422 dastest passes, 7811 AOT tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mp serializer Addresses Copilot review on PR #2722. Under DAS_CUSTOM_HASH=0, das_insert_only_hash_{map,set} alias to the same std::unordered_{map,set} as das_hash_{map,set}, so the new ordered() and AstSerializer overloads collide with the existing ones. Wrap the insert-only definitions/declarations with #if DAS_CUSTOM_HASH at 3 sites: * include/daScript/misc/das_common.h - ordered() map+set overloads * include/daScript/ast/ast_serializer.h - serialize_hash_map / operator<< * src/builtin/module_builtin_ast_serialize.cpp - corresponding definitions In fallback mode the existing das_hash_map overloads already serve insert-only arguments because the underlying type is identical. Bump AstSerializer::getVersion 83 -> 84 since Module::serialize now uses das_insert_only_hash_map for annotationData and requireModule; old archives must not deserialize into the new fields. Also drop the misleading "Does NOT need Abseil" comment from examples/hash/CMakeLists.txt - FetchContent_MakeAvailable(absl) runs at configure time regardless of which bench target is built. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…aussig-a8bc10 das_hash_map: insert-only variants + Module field switch + hash bench suite
Captures three patterns surfaced during PR #2721 review: - group-by-first-key-wins-single-hash-tab-dummy-addr-compare: the `tab?[uk] ?? dummy` + addr-compare idiom that brought groupby_count 76→37 ns/op. Documents per-element hash-op count + miss-path commit semantics + AST-test fingerprint (`key_exists==0`, `unique_key>=1`). - streaming-dedup-take-guard-outer-loop-not-fresh-key-branch: where to place the take(N) break-guard in distinct+take splice (outer loop, not inside fresh-key branch); explains the adversarial-input gap the bench couldn't catch. - linq-bench-eager-vs-lazy-distinct-arr-vs-each-arr: why `arr|>distinct` and `each(arr)|>distinct` produce wildly different bench numbers — array-shape distinct is eager, iterator-shape is lazy. Required source-shape match across m1/m3/m3f lanes for apples-to-apples. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…up from #2721 Two interlocking changes in `plan_group_by`: 1. **Miss/hit emission restructure** (no behavior change for existing arms). The per-element splice splits on `addr(entry) == addr(dummy)`: on miss, run `missInit` (acc starts at the first-element value); on hit, run `hitUpdate` (incremental update). Observationally equivalent for the pre-existing count/length/long_count/sum arms — same final state — but the per-branch split lets each reducer pick its own per-side shape independently. Unlocks A.2 below and the future min/max/first arms queued for the next PR. Existing G2/G3/G8 AST tests stay green with a new `count_call(body, "key_exists") == 0` assertion pinning the single-hash hot path from PR #2721 (the `tab?[uk] ?? dummy` + addr-compare idiom). 2. **Inner-select-sum recognizer**. `is_bucket_reducer_call` extended to match `sum(select(<bind>._1, <lambda>))` — the AST shape after the typer resolves `_._1 |> select(<lambda>) |> sum()`. On match, plan_group_by peels the inner lambda via `fold_linq_cond(innerLambda, itName)` and splices the body directly into `entry._1 += <body>` (hit) / `entry._1 = <body>` (miss). Accumulator type derived from the OUTER sum call's `_type` (the inner projection's typer-resolved result type) — the peeled body's `_type` is null because apply_template doesn't re-type-infer. Both bare body `_._1|>select|>sum` and named-tuple wrap `(K=_._0, S=_._1|>select|>sum)` supported. **Headline (100K rows, INTERP):** | Benchmark | m1 sql | m3 linq | m3f prev | m3f this PR | Win | |---|---:|---:|---:|---:|---| | groupby_sum | 174 | 101 | 108 | **36** | 3× over prev, 2.8× over m3, 4.8× over SQL | | groupby_count (regression check) | 142 | 71 | 37 | **36** | parity — A.1 restructure preserves the count splice | groupby_sum now matches groupby_count's ~36 ns/op: same number of allocs (1, the result array), same per-element work (single hash + entry._1 mutation), no per-bucket array materialization. **Deferred to follow-up PRs** (per `~/.claude/plans/modular-stirring-tide.md`): - PR-A2: bare `_._1|>min/max/first` + inner-select-min/max/first; multi- reducer named tuples like `(K=..., N=..|>length, S=..|>sum, M=..|>min)` - Separate follow-up: `average` reducer (needs 2-slot per-key acc + post-process division) **Test coverage added:** - tests/linq/test_linq_fold.das `test_group_by_inner_select_sum_fold_parity` with 4 subtests: bare body, named-tuple wrap, empty source, reference parity vs plain LINQ chain - tests/linq/test_linq_fold_ast.das G4 bare + G4b named-tuple AST tests asserting splice fired (no `group_by_lazy` / `select` calls in body, 2 for-loops, single-hash fingerprint) 239 + 97 tests pass in both interp and AOT modes; lint + format clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nner-select-sum linq_fold group_by: inner-select-sum splice — closes #2721 deferred follow-up
Generalizes plan_group_by along two axes:
A.3 — per-reducer dispatch. `is_bucket_reducer_call` extended to 8 reducer
shapes: bare {sum, min, max, first} and inner-select {sum, min, max, first}
(`<reducer>(select(<bind>._1, <lambda>))`). New `emit_reducer_branches`
helper produces per-reducer (missInit, hitUpdate) pairs:
- min/max: direct `<` / `>` on workhorse acc types; `_::less` fallback for
non-workhorse (matches the reference at linq.das:1224,1308).
- first: miss-init only (hitUpdate null). Subsequent same-key elements are
ignored, exploiting the first-key-wins guarantee from PR #2721.
- inner-select min/max: bind the projection result to a per-element temp so
the inner body evaluates exactly once per source element — matches the
reference's `select` laziness, and avoids re-running side effects.
- first_or_default is rejected at the arity check (2 args) and cascades.
A.4 — N+1-slot named tuples. The 2-slot recognizer (key + 1 reducer) is
replaced by `recognize_reducer_specs` returning `array<ReducerSpec>`. The
named-tuple form now accepts arbitrarily many reducer slots (key at _0,
reducers at _1.._N). The planner walks the spec array once and concatenates
per-slot missInit + hitUpdate statements into the per-element loop —
N reducers fused into one pass instead of N separate passes.
Field-access into dynamic slots (`entry._{slot}`) is built programmatically
via `mk_slot_ref` since qmacro has no dynamic-field-name splice. The
loop-body emission is now conditional on whether any reducer contributes a
hit update — first-only chains drop the else branch entirely.
Bail paths (all cascade to tier 2):
- key not at slot 0 of the named tuple
- unrecognized reducer in any slot (e.g. `average`)
- first_or_default (2-arg, fails arity)
Tests:
- 18 new parity tests in test_group_by_min_max_first_fold_parity and
test_group_by_multi_reducer_fold_parity (bare, named, inner-select,
multi-reducer, empty source, reference parity)
- 5 new AST-shape tests: G5a (min workhorse direct compare), G5b (min
non-workhorse `_::less`), G6 (first no hit compare), G7 (multi-reducer
fused pass), G10 (first_or_default cascade)
Both PR-A1 baselines (groupby_count, groupby_sum) hold parity.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t + multi-reducer 4 new 100K-row benchmarks (m1 SQL / m3 plain LINQ / m3f splice): - groupby_min: 175 / 111 / 42 ns/op (2.6× over m3, 4.2× over SQL) - groupby_max: 173 / 108 / 43 ns/op (2.5× over m3, 4.0× over SQL) - groupby_first: — / 71 / 36 ns/op (2.0× over m3; no direct SQL aggregator) - groupby_multi_reducer: 189 / 139 / 53 ns/op (3 reducers fused into 1 pass; 2.6× over m3, 3.6× over SQL) All 6 splice variants land within ~36–53 ns/op — per-element work bounded by the single hash op + slot mutations regardless of which reducer or how many. Multi-reducer pays ~5 ns per extra slot, still beats SQL by 3.6×. LINQ.md: refreshed Phase status table, baseline rows, Phase 3+ subsection. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…in-max-first-multi-reducer linq_fold group_by: min/max/first reducers + multi-reducer fused pass
Closes the explicit deferred-bail from PR #2721 onward: any upstream call between source and group_by_lazy used to cascade to tier 2. PR-B walks the upstream segment and fuses chains of where_*/select* into the per-element table-update loop. Walk: - For each `select`: bind the previous projection (if any) to a fresh `var v_N := projection_{N-1}` intermediate, then compute the new projection peeled to reference the latest bind name. Update elemType to the post-projection type so the table value-type witness matches what the key block will receive. - For each `where_`: bind any pending projection first (so the predicate references a stable name — no double-eval), then AND-merge the peeled predicate into whereCond. - Anything else (distinct, order_by, etc.): bail. Final bind: if a projection is still unbound after the walk, bind it now so itName (the post-projection name) is stable. The inner-select reducer recognizer (PR-A1/A2) folds its inner lambda body against this itName — without the bind, the recognizer would dangle on a name the typer hasn't seen. Per-element emission split into core + wrapping. The 3-stmt core (`let k = invoke(key, it); let uk = unique_key(k); unsafe { table update }`) is wrapped with `if (whereCond) { core }`, then prepended with intermediateBinds, and spliced into the for-loop body via $b(bodyStmts). The for-loop bind name is now `srcItName` (was `itName`); `itName` aliases the post-projection name (== srcItName when no upstream selects). Tests: - 8 new parity tests in test_group_by_upstream_fusion_fold_parity: where-only, select-only, where→select, select→where, chained selects, Person-fixture parity, empty result, count terminator. - 4 new AST-shape tests: G11 (upstream where, no runtime where_ call), G12 (upstream select, no runtime select call), G13 (combined where+select), distinct-upstream cascade fingerprint. All PR-A1/PR-A2 baselines (groupby_count, groupby_sum, groupby_min, groupby_max, groupby_first, groupby_multi_reducer) hold parity — the per-element-body split into core + wrapping is observationally equivalent when intermediateBinds is empty and whereCond is null. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…re/select fusion 3 new 100K-row benchmarks (m1 SQL / m3 plain LINQ / m3f splice): - groupby_where_count: 75 / 65 / 23 ns/op (2.8× over m3, 3.3× over SQL) - groupby_where_sum: 86 / 80 / 23 ns/op (3.5× over m3, 3.7× over SQL) - groupby_select_sum: — / 110 / 58 ns/op (1.9× over m3; m1 omitted — _sql LINQ-to-SQL requires `_group_by(_.Field)`, no expression keys) The where-fused benchmarks drop to ~23 ns/op — faster than the no-upstream groupby_count (36 ns/op) because the where filter halves the table-update count (only ~50% pass `price > 500`). Fewer hash ops × same per-survivor cost = lower per-element averaged time. LINQ.md: refreshed Phase status table, baseline rows, Phase 3+ subsection. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ard)
Fix correctness bug in PR-B's per-element emission: the initial walk
collected all binds into a flat intermediateBinds list and a single
AND-merged whereCond, then emitted `binds...; if (whereCond) { core }`.
That executes EVERY select before any where guard, so a chain like:
_where(_ != 0)._select(10 / _)._group_by(...)
would run `10 / _` for ALL source elements, including `_ == 0` (divide
by zero). Reference LINQ evaluates `select` lazily per element — so a
select that follows a where must only run on where-survivors.
Fix: segment-based walk + inside-out emission. The walk produces
(binds, trailing_where) pairs — a `where` closes the current segment
with itself as trailing_where; a `select` that follows a where starts
a new segment (its bind is emitted INSIDE the where's guard).
Consecutive wheres with no intervening select AND-merge into the same
segment's trailing_where. Final emission walks segments in reverse,
wrapping the core body inside-out so each where's `if { ... }` brackets
exactly the binds + nested guards that follow it.
Regression tests added FIRST (per feedback_correctness_bug_add_test_first):
- where → select with side-effect counter: asserts the projection fires
EXACTLY once per where-survivor (5), not once per source element (10).
- select → where → select: asserts only the FIRST select runs
unconditionally; the SECOND fires only on the where's survivors.
Both tests failed on the broken implementation (got 10, expected 5) and
pass after the fix. All 269 parity + 106 AST + 18 group_by tests stay
green; all 6 PR-A1/PR-A2/PR-B benchmarks unchanged — the lazy emission
has zero perf cost (same hash ops + slot mutations per survivor).
LINQ.md: refreshed the BufferGroupBy core row (Copilot caught the
inconsistency with PR-A2/PR-B's new rows below it — the core row still
claimed upstream where/select bailed, contradicting subsequent rows).
Single current status for the phase-table.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolves the "two daslang trees can't both run daslang-live" interference
that the parallel-checkout plan surfaced. After this:
daslang-live my.das --live-port 19090 # binary form
daslang script.das -- --live-port 19090 # scripts using live_api
coexist with another instance on the default 9090.
Port precedence (highest first), unified across C++ binary and .das module
via an env-var roundtrip so the single-instance lock and the HTTP bind
can never disagree:
1. live_api_set_port(p) (programmatic, always wins)
2. script argv --live-port N (find_flag_raw_value, full argv)
3. env DASLANG_LIVE_PORT (daslang-live re-exports its
resolved port into this env)
4. get_das_root() /daslang-live.cfg.json "port" key
5. default 9090
C++ daslang-live binary parses --live-port pre-doubledash, writes the
resolved port back into DASLANG_LIVE_PORT, and keys acquire_single_instance()
on that port (Windows: daslang-live-single-instance-<port> mutex; POSIX:
/tmp/daslang-live-<port>.lock).
MCP do_live_launch now forwards its port arg to the spawned binary --
fixes the deferred-test root cause documented in the mouse-card
daslang-live-no-port-flag-single-instance-lock-e2e-spawn-tests-infeasible.md
(now marked RESOLVED).
Tests cover parse_port_string boundaries, --live-port and --live-port=N
forms, JSON config parsing, precedence chains, and the
live_api_set_port-after-init smoke. 78/78 pass interpreted.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nly guard Per Copilot review on #2726: - parse_port_strict() in daslang-live binary replaces atoi for both --live-port arg and DASLANG_LIVE_PORT env. atoi silently accepted '9090abc' -> 9090; the new parser rejects trailing garbage to match the .das side's try_to_int. - Accept --live-port=N alongside --live-port N for parity with find_flag_raw_value on the .das side. - do_live_launch now rejects non-digit / over-5-char port at entry so a caller-supplied port cannot smuggle quotes / spaces / metacharacters into the shell-ready argv string passed to system(). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…roupby-upstream-fusion linq_fold group_by: fuse upstream where_/select* into per-element loop
CI darwin Release failure (run 26078968013): test_aot reported error[50101] AOT link failed on every live_api function referenced by tests/live_host/test_port_override.das (the first AOT test to require live/live_api). Root cause: live_api.das was missing from AOT_LIVE_HOST_MODULE_FILES in tests/aot/CMakeLists.txt. Without an AOT-cpp build of live_api itself, the test_port_override.das.cpp recorded hashes that disagreed with what test_aot.exe computed at simulate time -> link miss on live_api_set_port / parse_port_string / parse_argv_port / etc. Pre-existing live_host tests only require live_host (the C++ DLL bridge), so the gap was invisible until now. Fix: add modules/dasLiveHost/live/live_api.das to the list, same spot as live_api_builtins / live_api_stdio. Local reconfigure generates test_aot_live_host_modules_live_api.das.cpp under modules/dasLiveHost/live/_aot_generated/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
plan_group_by now accepts an optional `_having(pred)` between `_group_by_lazy(k)` and
`_select(group_proj)`. The predicate is peeled and rewritten against the per-key
accumulator slots: `<hb>._0` → `kv._0`, `<reducer>(<hb>._1)` /
`<reducer>(select(<hb>._1, <lam>))` → `kv._{spec.slot}` when the reducer name matches an
existing select-side spec. The rewritten predicate wraps `push_clone(outputExpr)` in the
result-build loop (and increments a counter loop in the `count` terminator lane). Per-
element table-update is untouched — the filter runs once per distinct key.
Bails to tier 2 when the predicate references a reducer with no matching select slot, or
touches the bucket value in any non-reducer shape (raw `_`, raw `_._1`, unrecognized
field). Both cases stay correct via the cascade.
Closes the SQL `HAVING` shape against the splice path. Headline benchmark
`groupby_having_count` lands at 36 ns/op — 2.2× over m3 plain LINQ, 3.9× over m1 SQL —
matching the no-having `groupby_count` since the per-element work is identical.
Tests: 8 parity subtests + 1 cascade subtest in test_linq_fold.das, 4 AST-shape tests in
test_linq_fold_ast.das (length-match splices, count-terminator splices, unmatched-reducer
cascades, raw-bucket cascades). All 390 linq_fold tests green in interpreter + AOT.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rewrite_having_pred only walks ExprCall/ExprField/ExprVar/ExprOp1-3 (plus the ExprRef2Value peel). For any other node type — `ExprAt`, `ExprInvoke` from a non- peelable lambda, `ExprIfThenElse`, etc. — it falls through to `return expr` unchanged. If that subtree contains references to the synthetic `hb` bind, those references leak into the result-build splice and the typer fails with `can't locate variable '\`hb\`…'`. Adds `HbScanner : AstVisitor` (modeled on `RemoveDerefVisitor` in templates_boost.das) and a thin `expr_uses_var(e, name)` helper. plan_group_by calls it after rewrite_having_pred — if any ExprVar named `hbName` survived the rewrite, bail and let the chain cascade to tier 2 (which is always correct via the array-shape pipeline). Failing-test-first per the correctness-bug-rule: test_group_by_having_unhandled_node_cascades uses `_._1[0] == 1` (ExprAt indexing into the bucket array) — without the scan the compile-time error from the previous run was `can't locate variable '\`hb\`0x829\`0x49'`. With the scan, the chain cascades and produces the correct filtered result. Closes Copilot review comment on PR #2727. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per Boris's direction on PR #2726 R2: keep daslang-live stateless -- drop the env-var roundtrip and the config-file fallback. Both sides (C++ daslang-live + .das live_api) now scan the same source (full argv, including post-doubledash) for --live-port, so the lock key and the HTTP bind cannot drift. Correctness bugs fixed (each with a repro test per the "correctness-bug -> repro test" rule): - Thread 4: MCP do_live_launch's port_is_safe accepted "0", "65536", "99999" -- daslang-live would error after 10s of polling. Renamed to port_in_range and now actually parses + range-checks [1, 65535]. - Thread 5: C++ arg loop stopped at doubledash while .das scanned full argv, so `daslang-live foo.das -- --live-port 19090` would bind 19090 on the .das side but key the C++ lock on 9090. New find_live_port_in_argv() scans the full argv. parse_argv_port test pins the post-doubledash case on the .das side. - Thread 8: env-roundtrip masked the config-file fallthrough. Resolved by removing both -- no more env, no more config file. Doc-only (Threads 6, 7): live_api_set_port "always wins after init" was misleading. Server binds at LiveApiAgent constructor time and does not rebind. Reworded the docstring on live_api_set_port and the [init] function comment to say "before agent constructs". RST audit (Boris's "MCP server and live documentation might be out of sync" request): - mcp.rst: project_root was referenced 18+ times in protocol.das but documented 0 times. Added a Common parameters subsection listing project + project_root once for all tools that accept them. - mcp.rst: Live-reload control table rewritten with per-tool arg surface (file/project/project_root/port on live_launch; full on live_reload; paused on live_pause; name/args on live_command; commands on live_commands). - daslang_live.rst: CLI reference filled in to match main.cpp (-project_root, -v1syntax, -track-allocations, -heap-report, --no-dyn-modules, --no-dump-leaks, --live-port, -h/--help). - daslang_live.rst: single-instance lock note now mentions port-keying (daslang-live-single-instance-<port> / /tmp/daslang-live-<port>.lock). - daslang_live.rst: REST API section documents the 3-level precedence chain (programmatic > argv > default) and the "before constructor" caveat on live_api_set_port. skills/daslang_live.md updated to match the simplified chain. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per Copilot review on PR #2726 R3: - Thread 9 (real correctness bug): C++ find_live_port_in_argv kept 'last VALID', while .das clargs.find_flag_raw_value keeps 'last RAW (validated to 0 on bad input)'. A tail like '--live-port 19090 --live-port abc' would have C++ key its lock on 19090 while .das defaulted to 9090 -- lock-vs-bind drift. Dropped the if(p>0) guard so the C++ scan is also last-occurrence-wins-unconditional. Repro test added per the correctness-bug rule: parse_argv_port treating invalid trailing values as 0. - Thread 10 (doc fix): g_resolvedLivePort comment still mentioned a 'DASLANG_LIVE_PORT env handoff' that was removed in cf58023. Reworded. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…roupby-having linq_fold group_by: splice _having between group_by_lazy and select
…ducer
Extends plan_group_by to splice the `average` reducer (and `inner-select-average`)
on top of PR-A1's miss/hit emission shape + PR-A2's N+1-slot reducer-spec walker.
Each average slot holds a `tuple<double; uint64>` (running sum + element count)
internally; result-build divides at output. The user-facing return type (`double`
for bare form, `tuple<...; Avg : double; ...>` for named form) is preserved so
the buffer's declared shape matches the user's chain.
- `is_bucket_reducer_call` recognizes `average` in both bare and inner-select arms.
- `slot_acc_type` overrides the slot type to `tuple<double; uint64>` for average.
- `emit_reducer_branches` emits `entry._{slot} = (double(it), 1ul)` on miss and
`entry._{slot}._0 += double(it); entry._{slot}._1 ++` on hit; the inner-select
variant evaluates the inner projection once into a `let vavg`-bound temp.
- `plan_group_by` scans for average slots; when present, the named-tuple
tabValueType clones the user's body type and substitutes only the average
slots, and the result-build output is a programmatically-constructed
`ExprMakeTuple` with per-slot divide for average slots.
- `rewrite_having_pred` routes average-slot references through a shared
`mk_slot_output_expr` so `_._1 |> average > 50.0` in a having predicate works
the same way as `_._1 |> sum > 50` does.
Bucket non-emptiness is structurally guaranteed (a key only enters the table
when at least one source element produced it), so `count` is always ≥1 — no
divide-by-zero guard needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
groupby_average headline at 100K INTERP: m1=173 / m3=106 / m3f=52 ns/op (2.0× over m3, 3.3× over m1 SQL). Regression checks against groupby_count, groupby_sum, groupby_min, and groupby_multi_reducer stay at their PR-A1/A2 baselines (36/36/43/52 ns/op). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…esis For (_._0, _._1|>average) — positional tuple, no field names — the recognizer still accepts the body since it only checks ExprMakeTuple shape + bind._0 at slot 0. But the avg+named result-build synthesis was deriving slotCount from argNames, which is empty for positional tuples → loop didn't iterate and the recordNames[0] write hit an empty array, crashing the splice at macro time. Fix: derive slotCount from argTypes (populated for any tuple shape); only fill recordNames when argNames is populated (preserves positional shape on output). Per the correctness-bug rule: a failing parity subtest for the positional form is added first — without the fix it breaks the whole file compile, with the fix it passes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…hen-9bbe12 daslang-live: --live-port flag + port-keyed lock + .das resolution chain
…roupby-average linq_fold group_by: average reducer splice (2-slot acc + post-process divide)
…y sources
Closes the documented reverse_take regression (m3f 34 vs m3 22 ns/op at
TAKE_N=10 / N=100K). The R1-R4 path pushed every source element into a buffer
then did O(length) reverse_inplace + resize(N) — wasting work proportional to
length - N.
Adds an R6 arm in plan_reverse taken when:
- take(N) is present at chain tail,
- no upstream where_,
- no upstream select,
- source type is isGoodArrayType || isArray (need O(1) indexing).
R6 emission walks indices [len-1 .. max(0, len-N)) directly into the buffer:
let __len = length(src)
let __takeN = N <= 0 ? 0 : (N < __len ? N : __len)
var buf : array<T>
buf |> reserve(__takeN)
for (k in 0 .. __takeN) {
buf |> push_clone(src[__len - 1 - k])
}
return buf
Take-bound clamp preserves take(N<=0) → empty and take(N>length) → full
semantics. Iterator sources fall through to R1-R4 (verified by a new negative
AST test).
m3f drops 34 → 0 ns/op (sub-nanosecond per element averaged over the 10
visited indices regardless of N) — beats m3's lazy-generator approach too.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Updates the reverse_take footnote, refreshes the operator-coverage row, drops the deferred entry, and adds a Phase 3+ PR-C section with the backward index loop emission shape + bail conditions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three doc/test improvements from the review:
1. Phase 3+ headline table — add a follow-up row `reverse_take (PR-C,
backward index loop)` showing m3f=0, following the same convention used
for `groupby_sum (PR-A1, inner-select-sum)` at line 378. The original
regression row stays as historical record.
2. plan_reverse terminator table — split the `take(N) |> to_array` row
into two rows distinguishing the R6 array-source path (backward index
loop) from the R1-R4 iterator/has-filter/has-select fallback path
(push + reverse_inplace + resize). Avoids conflicting guidance between
the early reference table and the dedicated PR-C section.
3. `test_reverse_take_iterator_source_uses_reverse_inplace` — reinforce
the negative test with the same shape pins the old positive test had:
`count_inner_for_loops == 1`, `count_outer_let_vars == 1`,
`count_call("take") == 0`. Catches drift in both directions: R6
mis-firing on iterator sources OR R1-R4 emission shape changing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…everse-take-backward-index linq_fold reverse: backward index loop for reverse |> take(N) on array sources (PR-C)
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 : )