Skip to content

[pull] master from GaijinEntertainment:master#1028

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

[pull] master from GaijinEntertainment:master#1028
pull[bot] merged 11 commits into
forksnd:masterfrom
GaijinEntertainment:master

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented May 23, 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 11 commits May 23, 2026 06:41
)

Two independent bugs surfaced together by the linter's
`no_infer_time_folding=true` + `no_optimizations=true` policy combo.

1. Compiler: `concept_assert` / `static_assert` cond never gets folded.
   `typeinfo sizeof(T) <= typeinfo sizeof(U)` rewrites to `ExprConstInt <=
   ExprConstInt` at infer time, but `InferTypes::visit(ExprOp1/Op2/Op3)`
   only folds when `enableInferTimeFolding` is on. With lint disabling
   it and `no_optimizations` skipping `ConstFolding`, the binop stayed
   unfolded and `ContractFolding::visit(ExprStaticAssert*)` raised the
   spurious 30151. Mirror the existing static_if save+force-enable+
   restore pattern in `preVisit`/`visit(ExprStaticAssert*)` so the cond
   subtree always folds.

2. Daslib: 6 sites used `if (typeinfo X)` instead of `static_if`. These
   relied on the same infer-time folding to elide the dead branch
   (whose body references fields/operations only valid in the true
   branch's universe). Under lint flags, both branches survive and
   the dead one fails to resolve. Convert all to `static_if`:
   `decs_boost.das:244` (`a._aka`), `builtin.das:403/892/914/1183`,
   `json_boost.das:477`.

`extended_checks` gates the lint step to `matrix.target == 'linux'`, so
this surfaced as "linux x64 only" in CI; verified platform-independent
locally on Windows daslang.exe.

Also: 2 pre-existing STYLE028 hits in `decs_boost.das` (`self->implement`
-> `implement`), required by the "every changed .das file lint-clean" PR
rule once the PR touches that file.

Regression fixture: `tests/_issue_2830_lint_repro.das`. CI lint runs
over changed .das files; the fixture exercises the failing path on
this PR and continues to exercise it on any future PR that touches it.

Verified locally on WSL Ubuntu2404-CI (clang 18.1.3, Release):
  - lint clean on all 4 repro variants
  - tests/decs   245/245 pass
  - tests/json   266/266 pass
  - tests/lint   8/8 + utils/lint/tests 38/38 pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lint-static-assert-cond-fold

ast,daslib: fix spurious 30151 on concept_assert under lint flags (#2830)
Two slices closing the final m4-vs-m3f gaps from #2824's residual outliers.

Slice 1 — trivial-let elision (closes sum_aggregate_m4 1.3ns gap):
When `_select(_.userName)` peels to a single `decs_tup.<field>` reference,
rename the chain bind directly to the iter var instead of synthesizing
`decs_sel_N`. wrap_decs_chain skips emitting the `let decs_sel_N = car_price`
binding entirely; the action's `acc += <iterVar>` references the iter var
natively. Required extending DecsTupUsageScanner with an iter-var-→-user-name
reverse map so bare iter-var refs still seed the pruner (previously: empty
usedNames fell through to unpruned-default, defeating the elision).

Slice 2 — reverse_take skip-into-tail (closes reverse_take_m4 5.2× gap):
For `from_decs(...).reverse().take(N).to_array()` with no where/select,
emit a two-pass invoke: pass 1 sums `arch.size` (no entity load), pass 2
uses for_each_archetype_find to skip whole archetypes whose cumulative
size still fits below the skip threshold, then a per-iter skip-counter
through the partial archetype, push into a takeN-sized buffer, and `return
true` to stop iteration once buf is full. reverse_inplace runs on the
small N buffer at end, not the full source. where/select fall through to
the legacy buffer+reverse_inplace+resize emit unchanged.

Bench (INTERP, 100K rows, ns/op):
- sum_aggregate_m4    3.4 → 2.1   matches m3f (was the systemic 1.3ns gap)
- reverse_take_m4    48.0 → 9.2   5.2× win, allocs 42B → 1B
- select_where_sum_m4 7.5 → 7.5   matches m3f (elision benefits this too)
- contains_match_m4   2.1 → 1.4   beats m3f at 2.2
- chained_where_m4    6.6 → 6.6   no regression
- count_aggregate_m4  4.1 → 4.1   no regression

Tests:
- New splice-shape assertions: trivial-let elision (no decs_sel binding
  for `_select(_.val).sum()` and `_where(_)._select(_.val).sum()`)
- New splice-shape for skip-into-tail (for_each_archetype_find count==1,
  decs_skips local presence)
- New parity tests: multi-archetype reverse+take, take(N>total),
  empty source — covers the whole-archetype-skip + partial-archetype +
  early-return arms

1388/1388 linq + 245/245 decs + 782/782 dasSQLITE green INTERP. MCP + CI
lint clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
#2835 fixed the typer-pass-order #2830 that originally tripped this test
on the extended_checks (linux, 64) lane. With master now containing the
fix, the test compiles cleanly on all lanes. Re-adding it covers a case
the current suite missed:

  from_decs_template(type<Row>)._where(_.a >= 0)._where(_.b >= 0).count()

Three chained single-field _where_s — all 3 fields read via field access,
no whole-var ref. The splice must keep all 3 get_ros (no slot pruning)
but elide the named-tuple bind (no decs_tup in the body, iter vars read
directly). Lesson saved to memory: not every CI lane runs every test, so
"platform-specific" failures often mean "we only check this on one
platform" — not that the bug itself is platform-specific.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The original test created one A-only and one B-only archetype, but
the query `from_decs_template(type<RevTakeMultiArchA>)` only matches A
— the B archetype never enters for_each_archetype, so the cross-
archetype skipping arm wasn't actually exercised.

Now creates two MATCHING archetypes: both have `rev2_id` (so both
satisfy the query), but the second group also has the rev2_b_* extras
which lands it in a separate archetype class. With A1=4 + A2=5 →
totalCount=9, take(3) → skip=6: A1 (size 4) skipped via the size-sum
arm, A2 enters with skipsLeft=2 → drains 2, pushes 3, returns true.
Exercises both the whole-archetype-skip and partial-archetype +
early-exit paths.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PR b42a952 (daslib: tighten unsafe usage) condensed the enum-name
lookup loop to a single `return unsafe(reinterpret<EnumTT>(ef.value)) if (name == ef.name)`,
which left `var res : auto(EnumTT) = default<EnumT>` as dead code — only
used to bind `EnumTT` (an alias of `EnumT` since `default<EnumT>` has
type `EnumT`).

Drop the dead var and inline the rename: `reinterpret<EnumTT>` →
`reinterpret<EnumT>`. No behavior change.

Verified by `tests/json/test_json_edge.das::test_enum_json` (128 tests
pass; covers both the string-name path and the round-trip path at
:630/:634).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…rivial-let-and-reverse-take

linq_fold: trivial-let elision + reverse_take skip-into-tail — closes the m4 ladder
…dead-var-cleanup

daslib: drop dead `var res` in json_boost.from_JV&lt;EnumT&gt;
For `from_decs._order_by(KEY).take(N).to_array()` with an inline-able key,
emit a bounded heap of size N maintained during the for_each_archetype walk
instead of materializing the full M-element buffer and then partial-sorting.
For `from_decs._order_by(KEY).first()` / `first_or_default(d)`, emit a
streaming-min: single `best` + `seen` flag instead of buf + min_by.

Cuts 100K push_clones (full DecsCar struct + string alloc) down to ~N
push_clones (only when the element wins the heap test).

Bench results (INTERP, 100K rows, ns/op):
  sort_first_m4         72.0 → 23.9 (3.0×, BEATS m3f 41.3)
  sort_take_m4          52.1 → 30.6 (1.7×, +7.9 vs m3f 22.7)
  order_take_desc_m4    52.1 → 30.5 (1.7×, +8.2 vs m3f 22.3)
  select_where_order_take_m4   35.1 → 25.1 (1.4×, +3.5 vs m3f 21.6)

Adds two thin re-exports in linq.das (`spliced_push_heap`, `spliced_pop_heap`)
so the splice can call sort_boost::{push,pop}_heap from any user module
without requiring sort_boost directly. The bounded-heap less-test uses a
new `make_inline_less_call` helper that templates the key body twice with
direct operand expressions — no block dispatch on the hot path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
reserve(takeN) for a user-supplied take(N) on a decs source of unknown
cardinality risks a large upfront allocation when N >> actual source
size — same OOM trap that top_n_by_with_cmp's iterator variant already
documents (linq.das:482-484). The fill phase grows geometrically to
min(N, M) in O(log) reallocs anyway, and at our common N (≤100) the
bench delta is in the noise floor (sort_take_m4 30.6 → 29.8, within
measurement noise).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ecs-bounded-heap-order

linq_fold: bounded-heap / streaming-min for plan_decs_order_family
@pull pull Bot locked and limited conversation to collaborators May 23, 2026
@pull pull Bot added the ⤵️ pull label May 23, 2026
@pull pull Bot merged commit 3446016 into forksnd:master May 23, 2026
1 of 5 checks passed
@pull pull Bot had a problem deploying to github-pages May 23, 2026 20: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