Skip to content

[pull] master from GaijinEntertainment:master#1018

Merged
pull[bot] merged 26 commits into
forksnd:masterfrom
GaijinEntertainment:master
May 21, 2026
Merged

[pull] master from GaijinEntertainment:master#1018
pull[bot] merged 26 commits into
forksnd:masterfrom
GaijinEntertainment:master

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented May 21, 2026

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 : )

borisbat and others added 26 commits May 20, 2026 12:21
First commit on the longarr phase 8c branch. Widens the decs storage
type-level surface to int64 and rewrites the FixedArrayIterator C++
runtime to int64 sizes. This is preparation -- the actual >INT_MAX
runtime scenario is gated by the entityLookup resize cascade
(decsState.entityLookup |> resize(int(id_base) + count) caps total
entities at INT_MAX globally) and EntityId.id : uint. Both follow in
subsequent commits on this same branch before the PR opens.

Changes:

daslib/decs.das
  Archetype.size : int -> int64        (the audit's silent-corruption site)
  entityLookup tuple .index : int -> int64
  get_eid(... index : int) -> int64
  remove_entity(... di : int) -> int64
  decs_array(... capacity : int) -> int64 (routes through _i64 builder)
  entity_count() : int keeps int return + panics if total > INT_MAX
                  (mirrors length() / long_length() surface contract)
  long_entity_count() : int64 NEW -- int64-safe accessor
  for_eid_archetype invoke(blk, ..., int(lookup.index)) cast at boundary
  create_entities_from_cmp invoke(fill_blk, ..., int(eidx), ...) cast at boundary
  get_default_ro / get_optional cap at INT_MAX with panic guard before
                  passing to repeat/repeat_ref (functional.das int64
                  widening is a separate followup)
  Stride multiplications (eidx * c.stride, arch.size * c.stride, etc.)
                  pick up int64(c.stride) casts at every site
  Comparisons with literal 0 widened to 0_l
  range(arch.size) -> range64(0_l, arch.size)

daslib/decs_state.das
  EcsArchetypeView.size : int -> int64
  Switched _builtin_make_temp_array -> _i64 variant for the dump path

daslib/builtin.das
  each(a : auto(TT)[]) passes int64(typeinfo dim(a)) to the widened
  iterator binding -- the only other in-tree caller of
  _builtin_make_fixed_array_iterator besides decs.das.

C++ runtime (the "rewrite said iterator to use 64 bit" piece):
  include/daScript/simulate/runtime_array.h
    FixedArrayIterator::size           : uint32_t -> uint64_t
    SimNode_FixedArrayIterator::size   : uint32_t -> uint64_t
  include/daScript/simulate/aot_builtin.h
    builtin_make_fixed_array_iterator(... int size, ...) -> int64_t size
  src/builtin/module_builtin_runtime.cpp
    Same widening in the impl; negative size clamps to empty (same
    pattern as builtin_make_temp_array_i64).

Tests
  tests/decs/test_archetype.das
    Five comparison literals widened to *_l (existing tests, same logic).
  tests/decs/test_archetype_size_type.das NEW
    static_assert type-contract probes locking Archetype.size : int64,
    long_entity_count() : int64, entity_count() : int. Uses
    stripped_typename so the test doesn't depend on PR #2764 (typeinfo
    is_int64) landing first.
  tests/decs/test_archetype_bulk_create_int64.das NEW
    Small-N (100K + 1K) runtime regression covering bulk-create + delete
    arithmetic neighbors. The genuine >INT_MAX memory-gated probe waits
    for the cascade widening to enable past-INT_MAX creation.

Test status: tests/decs/ 244/244 pass. tests/language/ 1003/1003 pass.
tests/long_array_table/ 45/45 pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…MAX cap

Second commit on the longarr phase 8c branch. Widens the daslib-layer
cascade that gated entity creation at INT_MAX globally. After this,
entity allocation is capped only by EntityId.id : uint = UINT_MAX,
which is loudly panicked instead of silently wrapping to
INVALID_ENTITY_ID.

The architectural widening of EntityId.id : uint -> uint64 stays out
of scope (ABI break for embedders).

daslib/decs.das changes:

new_entity_id() per-entity path
  length(entityFreeList) -> long_length(entityFreeList) -- last-element
                            index, can't panic past INT_MAX freeList
  length(entityLookup)   -> long_length(entityLookup) for id allocation
  Adds UINT_MAX cap with panic("decs: entityLookup reached UINT_MAX
  entries... EntityId.id (uint) widening is required") -- replaces the
  silent wrap to id=0 (INVALID_ENTITY_ID).

create_entities_from_cmp bulk path
  uint(length(entityLookup)) -> long_length() with id_base_long : int64
  resize(int(id_base) + count) -> resize(id_base_long + int64(count))
                                  -- routes through the i64 resize
                                  overload, no INT_MAX truncation
  Same UINT_MAX cap with a tailored panic message including current
  count and requested batch size.

is_alive(eid) bounds check
  int(eid.id) >= length(entityLookup) -> int64(eid.id) >= long_length(...)
  Prevents wrap when entityLookup > INT_MAX entries.

Test status: tests/decs/ 244/244 still pass; cascade widening is
upward-compatible at small N.

The genuine memory-gated >INT_MAX runtime test follows in the next
commit (requires ~50 GB to push entityLookup past INT_MAX with a
single-byte payload archetype, gated on DASLANG_HUGE_HEAP_TESTS=1).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Third (and likely final pre-PR) commit on the longarr phase 8c branch.

Cascade fix:
  Three sites in daslib/decs.das resized component byte storage via
    c.data |> resize(length(c.data) + c.stride * count)
  with `length(c.data) : int`. Past INT_MAX bytes per archetype (which
  is reachable as soon as arch.size * stride > INT_MAX), length() would
  panic per the long-array surface contract. Widened to:
    c.data |> resize(long_length(c.data) + int64(c.stride) * int64(count))
  Routes through the int64 resize overload; sites covered:
    create_entity (per-entity resize, +1 stride)
    create_entities (bulk pre-resize, +stride*count)
    create_entities_from_cmp (same, bulk path).

Test:
  tests/decs/test_huge_entity_count_int64.das NEW
  Memory-gated probe (DASLANG_HUGE_HEAP_TESTS=1, ~46 GB) that creates
  2.2 G entities in two 1.1 G chunks (each fits in int) via the bulk
  create_entities path. After the second chunk total > INT_MAX. Asserts:
    long_entity_count() returns int64 total      (the int64-safe surface)
    decsState.allArchetypes[0].size matches      (the widened field
                                                  carries the value
                                                  without wrap)
  The entity_count() panic side stays documented (try/recover banned
  for panic UX testing per the soft-fail rule). The CI no-op pass is
  preserved via the inline huge_enabled() gate.

This is the test that proves the actual win of phase 8c: without
PR-C, this scenario would silently corrupt arch.size or panic in the
length/resize cascade before reaching 2.2 G entities. With PR-C,
storage round-trips end-to-end.

Test status: tests/decs/ 245/245 pass (CI no-op skip works without
the env var). Sanity-sweep tests/language/ 1003/1003,
tests/long_array_table/ 45/45 confirm no regression in the broader
FixedArrayIterator-touching suite.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… INT_MAX

The part 3 test design was wrong: [decs_template] splits each struct
field into a SEPARATE component. A 10-int struct creates 10 components
at 4-byte stride each (400 MB per c.data per component, never crosses
INT_MAX). The probe ran 100 M create_entities iterations with 10
set calls per entity (~1 B set calls total) but the c.data cascade
under test was never exercised. The probe also crashed (exit 5 deep in
the SimNode_BlockNF block-invoke recursion -- likely OOM on per-component
overhead at 11 components * 100 M entries).

Rewrite uses ONE field of type float4[4] = 64-byte stride (the maximum
per ComponentValue.data layout). 35 M entities * 64 B = 2.24 GB c.data
per component, crossing INT_MAX (2.147 GB). One component = one
c.data array, so the `length(c.data) + c.stride * count` arithmetic
on master (pre-PR-C) would overflow int immediately on the first
create_entities resize: 64 * 35_000_000 = 2_240_000_000 > INT_MAX.

Memory: ~3 GB (2.24 GB c.data + 700 MB entityLookup). Runs in ~115 s
on the local workstation. Verified locally with
DASLANG_HUGE_HEAP_TESTS=1:
  --- PASS 'test_long_length_resize_cascade' (114.160343s)

This is the test that proves part 3's c.data widening
(`long_length(c.data) + int64(c.stride) * int64(count)`) actually
delivers the win at the byte-storage layer. Without PR-C, this test
would fail on master.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The dasfmt rule inserts a space between `>` and `.field` after a
default<T> / type<T> closing angle bracket. The parser accepts both
forms, so this is purely cosmetic but it tripped the pre-push
formatter --verify gate.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…contracts

Part 5's formatter-inserted space (`default<Archetype> .size`) trips the
lint compile path with "static assert condition is not constexpr or const"
even though dastest accepts it. Switching to runtime `t |> equal` with
the same typeinfo predicates: the typeinfo strings still resolve at
compile-time and the comparison is effectively const-folded, just at
test runtime. The contract is preserved -- a future revert of the
widening flips this test red the same way.

Also avoids the LINT002 unused-var warning by giving the
type-witness-only local a leading underscore (`var _a : Archetype`).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…type.das

Three lint warnings in the file PR-C already touches (per the
mandatory-lint policy + no-preexisting-excuses rule). All pre-existing
on master.

  LINT002: unused (k, v) in `for (k, v in keys(...), values(...))` -> _k, _v
           (we only count entries, not use the keys/values)
  PERF006: `for (i in range(4)) { ... eids |> push(eid) }` without
           prior reserve -> add `eids |> reserve(4)` before the loop.

No behavioral change; tests/decs/ stays at 245/245.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three issues addressed in one commit:

1. CI failure on darwin/linux_arm AOT step:
   daslib/linq_fold.das:3087 — `emit_decs_count_archsize` emits
   `accName += archName.size` where archName.size is int64 (PR-C) and
   accName is the int accumulator backing `count() : int`. Cast at the
   += site (`int($i(archName).size)`); users who need int64-safe totals
   chain `long_count()` (which already emits int64 accumulator). Fixes
   the linq AOT batch that failed across the macOS/arm CI lanes.

2. Copilot C1 — off-by-one in entity allocation cap:
   daslib/decs.das:419 used `next_id >= 0xFFFF_FFFF` (prevents
   allocating id == UINT_MAX) while line 1043 used `>` (allows it).
   Extracted `MAX_ENTITY_ID = int64(0xFFFF_FFFF_l)` near
   INVALID_ENTITY_ID and made both per-entity and bulk paths consistent
   on `> MAX_ENTITY_ID`. EntityId.id : uint range is [0, UINT_MAX]
   inclusive; the panic guards the actual overflow boundary.

3. Copilot C2 — silent int truncation at block-invoke boundaries:
   daslib/decs.das:682 (for_eid_archetype) and :1050
   (create_entities_from_cmp). With the cascade widened, lookup.index /
   eidx can exceed INT_MAX up to UINT_MAX, so `int(...)` casts silently
   truncate. Replaced the misleading "today's cap keeps it ≤ INT_MAX"
   comment with a panic guard before the cast (mirrors
   get_default_ro / get_optional pattern). Long-* block variants with
   int64 sig are the followup; until then the cap is loud.

Test status: tests/decs/ 245/245, tests/linq/ 1163/1163 — confirmed
locally with the new build.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… double accumulator)

Both `emit_accumulator_lane` (array-side) and `emit_decs_accumulator` (decs-side)
diverged from the canonical `linq.das:1545-1574` `average` implementation:
- Accumulator was `accType` (often int → overflow risk), should be `double`
- Counter was `int`, should be `uint64`
- Empty source returned NaN (from `0.0lf / 0.0lf`), should return `0.0lf` via cnt==0 guard

Aligned both sites. Macro-time `accType.baseType == Type.tDouble` cast-skip
preserved so already-double chains avoid PERF020 redundant-cast trip on the hot path.

Un-skips the deferred regression test `test_zip_average_empty_returns_zero` from
PR #2742's review, flips `test_average_accumulator.average: empty → NaN` to
expect `0.0lf`, and adds `test_unroll_average_empty_returns_zero` for the
decs-side path.

Both PR #2765 and PR #2766 Copilot reviewers flagged this 2-site divergence;
deferring was intentional (kept resurrection PRs scoped to cherry-picking).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three issues addressed together (round 2 of CI failures + Copilot review):

1. CI failure on darwin/linux_arm AOT cpp compilation:
   AOT-generated cpp calls `builtin_make_temp_array_i64(...)` (emitted
   from daslib/decs.das `decs_array` via PR-C part 1) but
   `include/daScript/simulate/aot_builtin.h:99` only forward-declared
   the int variant. clang errors with "use of undeclared identifier
   'builtin_make_temp_array_i64'". Added the int64 forward declaration
   alongside the existing one. The cpp definition exists in
   module_builtin_runtime.cpp:1684 already.

2. Copilot C3 — UINT_MAX cap off-by-one in create_entities_from_cmp:
   `id_base_long + int64(count) > MAX_ENTITY_ID` rejects the boundary
   case where the LAST allocated id == UINT_MAX (id_base ==
   MAX_ENTITY_ID && count == 1). Highest id allocated is `id_base +
   count - 1`, not `id_base + count`. Changed comparison to
   `id_base_long + int64(count) - 1_l > MAX_ENTITY_ID`.

3. Copilot C4 — INT_MAX cap off-by-one for fill_blk (same pattern):
   `base + int64(count) > int64(INT_MAX)` rejects the boundary
   `base == INT_MAX && count == 1` where eidx = INT_MAX is a valid
   int cast. Changed to `base + int64(count) - 1_l > int64(INT_MAX)`.

The per-entity new_entity_id cap (`next_id > MAX_ENTITY_ID`) is
correct as-is — next_id IS the id being allocated, so the boundary
`next_id == MAX_ENTITY_ID` is legitimately allowed by `>`.

Test status: tests/decs/ 245/245 still pass locally. CI re-runs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…verage-empty-guard

linq_fold: align average semantics with linq.das (empty-source guard, double accumulator)
…se8c-decs-archetype-size

phase 8c: decs Archetype.size int64 + entityLookup cascade + FixedArrayIterator widening
Extends `plan_decs_unroll` to recognize trailing `take(N)` / `skip(N)` after the
where/select chain. New `extract_decs_ranges` peels them into `DecsRangeInfo`;
counter inits hoist above `for_each_archetype` so they span archetypes;
per-element guards wrap `perElement` BEFORE chain so ranges apply to the
post-`where_` stream (mirrors array-side `wrap_with_condition(wrap_with_ranges(...))`).

When `takeExpr != null` the outer call switches to `for_each_archetype_find`
with a `: bool` lambda so the take-cap stop propagates across archetypes — the
inner `return true` exits both the for-loop and the lambda in one shot.

All 4 non-bare-count emit paths threaded: `emit_decs_accumulator`,
`emit_decs_early_exit`, `emit_decs_min_max_by`, `emit_decs_to_array`. Bare
`count` via arch.size shortcut still bails on any chain ops including ranges.

Tests: 11 new in `tests/linq/test_linq_from_decs.das` — take/skip/skip+take
parity, where+take, select+take+sum, take+first, take+to_array, take(-N)
short-circuit, skip-beyond-end, plus AST-shape gates confirming take routes to
`for_each_archetype_find` and skip-only stays on plain `for_each_archetype`.

Verification:
- format clean, lint clean
- interp: 41 + 11 new = 52/52 in test_linq_from_decs.das; 777/777 across all 11 linq test files
- AOT: 1239/1239 via `bin/test_aot -use-aot dastest/dastest.das -- --use-aot --test tests/linq`

Bench numbers (take_count / skip_take / take_count_filtered / take_sum_aggregate)
all collapse to 0 ns/op — splice fires (AST gates confirm) but the compiler
folds the tight chain when `accept(rows)` doesn't break the constant path.
Numeric wins blocked on the plan.md Task 1 anti-DCE sweep.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…splice fires)

Original claim was wrong. AST dump shows the splice IS firing and actively
building the full 1000-element result array (for_each_archetype_find +
decs_takec counter + push_clone). The 0 ns/op number is the bench framework's
body_time / n_iters reporting hitting its measurement floor — same as the m3f
array splice (also 0 ns/op, identical shape). Old m4 baseline 36 ns/op → new
sub-1 ns/op ≈ 36× actual win, just below bench resolution.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ins+take + take-limit hoist

Three correctness fixes flagged in Copilot review of PR #2770:

1. **Early-exit boolean confusion when take present (review #1).** When `takeExpr != null`,
   `any`/`all`/`contains` now route their answer through explicit `decs_found` state
   instead of `for_each_archetype_find`'s return value. Take-cap exits also emit
   `return true` (to stop the archetype walk), so the find result was ambiguous:
   `take(3).any(_>100)` on a 7-element source where no element matches would
   return true (the take-cap hit, not a match). Same trap for `contains` (false
   positive) and `all` (false negative — take-cap looks like "counterexample
   found"). Fix: in the takeExpr-present branch, set explicit `decs_found = true`
   on the actual match path; tail returns `decs_found` (or `!decs_found` for
   `all`); the find call's result is used only as iteration control.

2. **Take limit re-evaluated per element (review #2).** `wrap_inner_for_with_decs_ranges`
   embedded `clone_expression(rangeInfo.takeExpr)` directly in the per-element
   guard, so the take expression evaluated on every iteration. `take(src, total:int)`
   semantics evaluate `total` once at call time. Fix: hoist take limit into a
   `let decs_takel = $e(takeExpr)` in `decs_range_prelude` (alongside the
   existing skip hoist); per-element guard compares against the hoisted local.

3. **Missing coverage (review #3).** 7 new behavioral tests + 1 hoist regression
   test in tests/linq/test_linq_from_decs.das:
   - `take(0).any()` returns false on non-empty source
   - `take(3).any(_ > 100)` returns false when no prefix match (catches review-1 bug)
   - `take(3).any(_ == 1)` returns true when match in prefix
   - `take(3).all(_ >= 0)` returns true when prefix passes (catches review-1 bug)
   - `take(3).all(_ <= 1)` returns false when prefix has counterexample
   - `take(5).contains(2)` returns true on hit
   - `take(2).contains(100)` returns false on miss (catches review-1 bug)
   - `take(side_effect_fn())` evaluates the take expression exactly once (catches review-2 bug)

Verification:
- format clean, lint clean
- interp: 60/60 in test_linq_from_decs.das (was 52, +8 new)
- AOT: 1247/1247 via bin/test_aot (was 1239, +8)
- ast_dump on target_unroll_take_all_fail_fold confirms explicit-state shape:
  `var decs_found = false; ... if (!pred) { decs_found = true; return true } ...
  return !decs_found` with the for_each_archetype_find result discarded

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Closes the last int-only gaps in the daslib/builtin.das array surface
that prevent code from reaching insertion positions past INT_MAX. The
underlying C++ runtime (`__builtin_array_push` / `_push_zero` and
`__builtin_array_resize_i64`) has been int64-capable since the Phase 2+3
landings; only the daslang wrappers needed widening.

Widened (int -> int | int64 + static_if (typeinfo is_int(at)) dispatch,
so the int64 branch never pays an int64(at) ExprCall cost):
  * push(arr, value, at)               -- copy + move variants (lines 113, 126)
  * emplace(arr, var value, at)        -- line 224
  * push_clone(arr, value, at)         -- copy + move variants (lines 274, 287)
  * resize_and_init(arr, newSize)      -- + 2-arg initValue variant (lines 53, 69)

For resize_and_init, the static_if also picks the matching backing call
(__builtin_array_resize vs _i64), length() vs long_length(), range() vs
range64() -- the int variant stays on the int-flavor SimNode path
unchanged.

Tests:
  * tests/long_array_table/test_int64_overloads.das -- 5 new
    overload-resolution unit tests (push/emplace/push_clone at int64,
    resize_and_init int64 + initValue). Fails on master with "no matching
    overload"; passes here.
  * tests/long_array_table/test_huge_array_push_emplace_clone.das --
    extended with 3 huge insertion-path probes (push/emplace/push_clone
    at position > INT_MAX). Memory-gated on DASLANG_HUGE_HEAP_TESTS=1;
    verified locally on a 2.2 GB uint8 array (6 tests, 2.6s).
  * tests/long_array_table/test_huge_array_erase.das -- NEW. 2 probes
    for the already-widened erase(at:int64) / erase(at,count:int64)
    paths past INT_MAX. Same gate; verified locally (2 tests, 0.8s).

Side-effects:
  * doc/source/reference/language/generic_programming.rst -- adds
    typeinfo is_int / is_int64 to the trait list (carry-over from
    PR #2764 deferral).
  * daslib/builtin.das -- swept all 31 pre-existing lint warnings
    (STYLE024/025/026/005) per the bundling pattern. 6 of the clone /
    clone_to_move variants carry an explicit `// nolint:STYLE025` with
    the reason -- `var clone_dest : TT -#` is conditionally unsafe (the
    is_unsafe_when_uninitialized type set), and the unsafe block scope
    cannot be narrower than the var-decl.

Regression locally: 1785 tests pass across
  long_array_table (58) + decs (245) + linq (1228) + daslib (92) +
  algorithm (162). `dasfmt --verify` clean on full tree (17470 files).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ecs-unroll-slice5a

linq_fold: plan_decs_unroll Slice 5a — take/skip ranges on decs
…andmade

Two CI failures from the initial PR-builtin commit:

1. tests/language/lock_array.das fails on darwin26/darwin15/linux_arm and
   wasm with `error[31000]: address of reference requires unsafe` at
   daslib/builtin.das lock_data. The STYLE025 sweep narrowed
   `unsafe { invoke(blk, reinterpret<TT?#> addr(a[0]), len) }` to
   `invoke(blk, unsafe(reinterpret<TT?#> addr(a[0])), len)`, but `addr()`
   needs its own unsafe context too -- the lint's STYLE025 leaf detection
   doesn't recognize addr() as an unsafe op, so the rule fires on what is
   actually a 2-unsafe-leaf block (justified). Reverted both lock_data
   variants (var + non-var arrays) back to block form with `// nolint:
   STYLE025` and the reason. The empty-array null branch keeps the
   narrowed `unsafe(reinterpret<...> null)` since no addr is involved.

   Caught locally by `dastest -- --test tests/language`. Pre-push only
   ran tests/long_array_table + tests/decs + tests/linq + tests/daslib +
   tests/algorithm and missed lock_array which lives under tests/language
   -- expanded local pre-push test scope going forward.

2. `Check for // stub in handmade docs` CI step found a new stub at
   doc/source/stdlib/handmade/function-builtin-resize_and_init-0xba1d8e
   a89988acec.rst. The int|int64 widening changed the canonical signature
   hash so das2rst.das emitted a fresh stub. Per the documentation_rst
   skill, daslib uses handmade-flow for per-symbol docs -- filled the
   stub with the same 1-line description as the prior int-only hashes
   (default-init resize). The 2-arg `initValue` variant continued to
   resolve to its existing handmade file (unchanged hash).

Verified locally:
  * lint clean on daslib/builtin.das (0 warnings)
  * tests/language -- 1003/1003 pass (was 1 error on master + this PR)
  * tests/long_array_table -- 58/58 pass
  * das2rst.das -- no stubs remaining

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…y skipped

The pre-push hook runs `daslang utils/lint/main.das -- <changed> --quiet`
on every changed `.das` file. `utils/lint/main.das:88` hard-codes
`daslib/builtin.das` to the `is_skip_file` set (lint can't compile it
standalone; it's parsed as the implicit-prelude module).

Before this fix: if the only changed `.das` is `daslib/builtin.das`,
`scan_das_files` skips it, `files` ends empty, lint exits 1 with
"Error: no .das files found". That's a false negative -- the user
DID nothing wrong; the skip-list filtered out the only input.

After this fix: `scan_das_files` tracks `var skipped : int&`. If
`files` is empty at the end AND `skipped > 0`, lint exits 0 silently.
If `files` is empty AND `skipped == 0` (genuine "no .das at the
path"), the original error path stays.

Caught while pushing part 2/N of PR #2771 -- the only changed `.das`
was `daslib/builtin.das` and pre-push refused to push despite local
verification.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Slice 5b extends DecsRangeInfo (added in #2770) with predicate-driven
ranges (skipWhileCond/takeWhileCond). extract_decs_ranges now walks the
suffix in canonical order skip→skip_while→take_while→take, bails when
a select appears in the prefix (mirrors array-side seenSelect bail at
linq_fold.das:1615/1623). Predicates peel against the source tuple
tupName, so the post-where stream is visible but selects are forbidden.

wrap_inner_for_with_decs_ranges + decs_range_prelude gain skippingName
(one-way flag, hoisted at invoke scope so it persists across archetypes).
Per-element guard order matches array-side wrap_with_ranges:
take-cap → skip-counter → skip_while flag → take_while break → takeC++.

All 4 emit fns (accumulator, early_exit, min_max_by, to_array) extend
the outer dispatch to switch to for_each_archetype_find when takeExpr
!= null OR takeWhileCond != null (both trigger inner `return true`).
useExplicitState in emit_decs_early_exit extends correspondingly so
any/all/contains + take_while still distinguish "real match" from
"take_while-stop" via explicit foundName state.

Bench: take_while_match m4 55 → 8 ns/op (6.9× win over eager bridge).
Coverage: 21 new tests (60 → 81 in test_linq_from_decs.das) — including
AST shape gates (take_while → _find; skip_while-only → for_each_archetype),
explicit-state regression guards (take_while + any/all/contains), and
edge cases (always-true/always-false predicates, skip+take_while,
skip_while+take, where+take_while). 1268/1268 linq interp + 1647/1647
AOT (linq+decs+aot). Pre-existing STYLE015 in emit_decs_count_archsize
condensed inline.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ltin-int-int64-at

phase 8d: builtin.das push/emplace/push_clone/resize_and_init int|int64
…ally exercise the splice

Copilot caught that the take_while+_select+terminator tests (any/all/contains/
sum/first/to_array variants) were falling through to the tier-2 eager bridge,
NOT exercising the new explicit-state routing in emit_decs_early_exit. Root
cause: canonical chain order forbids _select after a range op
(extract_decs_ranges bails at the non-range op in suffix walk), and the array
side enforces the same rule (linq_fold.das:1623).

Fix:
- any/all rewritten to predicate over the source tuple directly:
  `_take_while(_.val<3)._any(_.val==2)` instead of
  `_take_while(_.val<3)._select(_.val)._any(_==2)`. Now splices through
  emit_decs_early_exit's useExplicitState path (verified via ast_dump).
- sum/first/to_array: sum-after-take_while is unsplicable on either array or
  decs side (need _select to project, but select-after-while bails). Dropped
  sum variant; replaced first/to_array to operate on source tuples (test field
  access on the returned tuple). Added min_by variant — exercises
  emit_decs_min_max_by under take_while routing through for_each_archetype_find.
- contains: source-tuple equality not defined; dropped (any/all already
  exercise the same useExplicitState branch).
- New `test_unroll_take_while_any_splice_shape` AST gate asserts:
  to_sequence count == 0 (splice fires, not tier-2),
  for_each_archetype_find count == 1 (take_while-stop early-exit dispatch),
  decs_found token count == 3 (explicit-state routing emits var+set+return).

Coverage: 60 → 80 (was 81 pre-rewrite, -1 for dropped contains, +2 for
min_by/splice_shape, net +20). 1267 linq interp + 1512 AOT+decs green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…boost)

Closes the int-only hotspots across three daslib helper modules.

== array_boost.das ==

  * array_helper (private, both var + const variants): `_::length(arr)` ->
    `_::long_length(arr)` + `_builtin_make_temp_array_i64`. This is the
    path the public `temp_array(arr)` delegates to, so passing a > INT_MAX
    element source now Just Works instead of panicking on length().
  * `temp_array(data, lenA, a)` (both var + const): consolidated the two
    int + two int64 overloads to two `int | int64` signatures with
    `static_if (typeinfo is_int(lenA))` dispatch. Mirrors the PR-builtin
    pattern.
  * `array_view(bytes, offset, length, blk)` (both const + var-bytes):
    consolidated to one `int | int64` signature per byte-variant with
    static_if dispatch. The int branch's signed-overflow guard
    `(offset + length) > length(bytes)` -> `uint(offset) + uint(length) >
    uint(_::length(bytes))` (avoids signed overflow UB on adversarial
    inputs even within INT_MAX-bound arrays). The int64 branch lifts the
    pre-existing uint64-arithmetic bounds check.

== json_boost.das ==

  * `from_JV` generic-array deserialization at line 475: `reserve(arr |>
    length)` -> `reserve(arr |> long_length)`. Past INT_MAX, the int
    return silently truncated the reservation and corrupted capacity.
  * `JV(value)` array builder at line 628: same `reserve(length)` ->
    `reserve(long_length)`. The `is_dim` static_elif branch keeps
    `reserve(typeinfo dim(value))` since fixed-size arrays are bounded
    by their dim() and `long_length` is array<T>-only.
  * Two PERF020 lint hits at lines 63 + 75 in null_coalescing/with_string
    (template instantiations where TT == double / TT == int64 make the
    `TT(...)` cast a no-op) -- annotated `// nolint:PERF020 generic` per
    the convention. Sweep cost while we were here.

== strings_boost.das ==

  * `jaccard(a, b : table<string>)` HIGH-severity overflow at line 219:
    `length(a) + length(b) - intersect` int math wrapped on huge sets.
    Widened `var intersect = 0_l` (int64) + `long_length(a) +
    long_length(b)`. Float ratio output unchanged.
  * `levenshtein_distance` / `_fast` and `replace_multiple` left as-is:
    each guarded at the top by `let len = length(s)` which panics with
    a clear message before any wider corruption. O(N) / O(N^2) on > INT_MAX
    chars isn't a real use case; the panic gate is correct behavior.

== Tests ==

  * `tests/long_array_table/test_int64_overloads.das` extended with
    `test_temp_array_data_lenA_int64` + `test_array_view_int64` (small-N
    overload-resolution + correctness).
  * `tests/long_array_table/test_huge_temp_array.das` (NEW) -- memory-gated
    probe over a 2.2 GB uint8 source: `temp_array(arr)` and
    `temp_array(data, HUGE_N, type<uint8>)` both verify round-trip read
    at index 0 and index `HUGE_N - 1`.
  * `tests/long_array_table/test_huge_array_view.das` (NEW) -- memory-gated
    probe: `array_view` at offset > INT_MAX, const + mutable view variants.

Regression locally: 3211 tests pass across long_array_table (64) +
decs (245) + linq (1247) + daslib (92) + strings (361) + json (266) +
language (1003). All four huge probes verified with
DASLANG_HUGE_HEAP_TESTS=1 on a 2.2 GB array (4 tests, 1.6s).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Tutorial 33_algorithm.das exercises `temp_array(int[5])` via algorithm.reverse
and algorithm.min_element. PR-G switched array_helper to long_length +
_builtin_make_temp_array_i64 unconditionally; long_length only accepts
array<T>/table<K;V>, so fixed-array callers got "no matching functions" at
tutorial dry-run on darwin15 extended_checks.

Split with static_if (typeinfo is_array(arr)):
- array<T> path: long_length + _builtin_make_temp_array_i64 (huge-array safe)
- fixed-array path: length + _builtin_make_temp_array (size known at compile
  time, always int-bounded)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ecs-unroll-slice5b

linq_fold: plan_decs_unroll Slice 5b — take_while/skip_while on decs
…lib-boost-int-int64

phase 8e: daslib boost widenings (array_boost / json_boost / strings_boost)
@pull pull Bot locked and limited conversation to collaborators May 21, 2026
@pull pull Bot added the ⤵️ pull label May 21, 2026
@pull pull Bot merged commit e46461c into forksnd:master May 21, 2026
@pull pull Bot had a problem deploying to github-pages May 21, 2026 02:58 Error
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant