Skip to content

[pull] master from GaijinEntertainment:master#1013

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

[pull] master from GaijinEntertainment:master#1013
pull[bot] merged 15 commits into
forksnd:masterfrom
GaijinEntertainment:master

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented May 19, 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 15 commits May 19, 2026 03:05
Flip two predicates on tLambda in TypeDecl:
- canCopy() false -> true: lambda is a fat pointer (one char* to a heap
  capture frame), so '=' aliases cheaply; pass-by-value is free.
- isSafeToDelete() true -> false: aliases mean 'delete lam' must be
  gated by unsafe, matching raw-pointer and class delete.

This cascades through composites: array<lambda<...>>, structs with a
lambda field, tuples/variants containing a lambda all inherit the
unsafe-delete rule (Structure::isSafeToDelete walks fields, and the
existing tArray/tTable branches recurse into element types).

Side-effect (intended UX win): capturing a lambda from inside another
lambda no longer needs an explicit @capture(<- inner) annotation -
default capture_any now picks copy (pointer alias) instead of forced
move. Existing @capture(<- ...) / @capture(& ...) annotations stay
valid.

Sites that newly require unsafe-delete:
- daslib/archive.das: 4 generic 'serialize' overloads (struct/tuple/
  variant/table)
- daslib/decs.das: restart / after_gc / commit
- daslib/heartbeat.das: set_heartbeat
- tests: aot/test_lambdas, language/lambda_capture, lambda_capture_modes,
  jit_tests/invoke

New positive coverage (interpreter + AOT mirror):
- tests/language/lambda_copy.das, tests/aot/test_lambda_copy.das
  - '=' aliases the capture frame (proven by alternating invocations)
  - pass-by-value (independent local vars per invoke)
  - struct copy aliases lambda field
  - implicit capture-of-lambda inside another lambda

Doc updates:
- doc/source/reference/language/lambdas.rst
- doc/source/reference/language/move_copy_clone.rst (type table row)
- doc/source/reference/tutorials/14_lambdas.rst
- doc/source/reference/tutorials/20_lifetime.rst (delete-needs-unsafe)
- tutorials/language/14_lambdas.das (drop "lambdas can't be copied"
  comment, add copy/alias note)
- CLAUDE.md (Pass-by-value section)

Build infra (bundled - pre-existing limitations that surfaced):
- CMakeLists.txt DAS_AOT_EXT: chunk AOT batches (60 files each) to
  stay under Windows' 32K command-line limit when worktree/CI paths
  are deep (absolute paths blow it; chunked add_custom_command runs
  COMMAND-per-batch sequentially).
- tests/aot/CMakeLists.txt: gate test_aot_live_host* AOT under
  IF(NOT DAS_HV_DISABLED), skip daslib/just_in_time.das from the
  daslib AOT batch when DAS_LLVM_DISABLED=ON. Both AOT-compile by
  requiring external modules (dashv, LLVM.dll) and fail
  unconditionally without them.

Verification: ctest 35/35; interpreter 7804/7810 (6 platform-skip);
AOT (--use-aot) 7193/7199 (6 platform-skip). HV enabled, LLVM off.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-9be7f9

lambda: copyable (alias), delete requires unsafe
Closes the `_while` row in the test_linq_partition.das coverage checklist.
take_while(pred) breaks the loop on the first false-pred element; skip_while(pred)
flips a one-way `skipping` flag on the first false then emits everything after.

How it lands:

1. wrap_with_skip_take → wrap_with_ranges, append_skip_take_prelude →
   append_ranges_prelude. Both helpers gain skipWhileCond / takeWhileCond /
   skippingName args. All 4 lane builders thread the new state through their
   signatures — predicate-driven ranges are now first-class alongside count-driven.

2. Per-match prefix order in the loop body:
   (1) take-guard, (2) skip counter, (3) skip_while flip (NEW), (4) take_while
   break (NEW), (5) takeCount++, body. takeCount++ moved below the predicate
   gates so skip_while-skipped and take_while-rejected elements don't eat the
   take(N) budget. Pure skip/take chains are unchanged (the new prefixes are
   no-ops when both predicates are null).

3. classify_terminator extension: take_while and skip_while join where_/
   select/take/skip in the ARRAY-trailing arm so `_take_while(p).to_array()`
   splices into the array lane instead of cascading.

4. Canonical chain order rejected by plan_loop_or_count:
   [where_/select]* → skip? → skip_while? → take_while? → take? → terminator.
   Each later op rejects predecessors that violate this order; out-of-order
   chains cascade to tier 2 (correct, just no splice).

Bail cases (cascade to tier 2):
- `_select(...)._take_while(...)` or `_select(...)._skip_while(...)` —
  predicate currently peels with itName; lifting to chained-bind names is a
  follow-up that requires moving select binds above the predicate gates.
- Multiple take_while or multiple skip_while in one chain.
- skip / take appearing AFTER skip_while / take_while.

Headline (100K rows, INTERP):

| Benchmark         | m1 sql | m3 linq | m3f this PR | Win |
|---                |-------:|--------:|------------:|----:|
| take_while_match  |    7   |   23    |       **2** | 11.5× over m3 / 3.5× over m1 SQL |
| skip_while_match  |    3   |   20    |       **5** | 4× over m3 (sqlite COUNT-WHERE index-scan still 1.67× faster) |

THRESHOLD = 50000 against n = 100000, so take_while breaks halfway and
skip_while flips halfway. take_while_match is the standout: splice exits
the for-loop on the first false-pred element (~50k of 100k source rows),
while m3 still pays for the full take_while_impl array allocation + the
subsequent count length read.

Test plan:
- Parity tests (tests/linq/test_linq_fold.das): 24 new subtests across 2
  [test] functions covering bare/where/select/skip-take/empty/all-true/
  all-false/cascade-bails for both operators and each lane.
- AST-shape tests (tests/linq/test_linq_fold_ast.das): 8 new tests + 9
  target_* helpers covering splice + cascade fingerprints.
- Interpreter: 369/369 fold + 133/133 ast + wider tests/linq/*.das sweep
  (15 files, all green).
- AOT (test_aot -use-aot): 369/369 fold + 133/133 ast green.
- Lint + format: clean across all touched files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bundled cosmetic fixes from PR #2732's Copilot review (PR-F is queued and
will land before this PR-G stack):

- benchmarks/sql/single_match.das:9 — comment claimed `LIMIT 2` but the SQL
  used `_first()` (LIMIT 1, fixture-side id uniqueness already asserts
  exactly-one). Reworded to match the code.
- benchmarks/sql/aggregate_match.das:9 — comment said "max(price - min)" but
  the reducer is `sum(price)`. Reworded to "sum of prices".

The 3rd Copilot suggestion — renaming the test label "aggregate: sum of
doubles" → "aggregate: sum with multiplier" — lands in the PR-G commit
ahead of this one since that file is already touched there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dasImgui cards (PR-D + table rail + popup/loop ID patterns):

- aot-header-hand-maintained-not-regenerated: aot_dasIMGUI.h must
  carry hand-written DAS_MOD_API decls when new C++ helpers are
  bound; not regenerated from anything.
- ffi-const-ref-return-and-decltype-name-collision: the failure
  modes of `addExtern<DAS_BIND_FUN(...)>` — runtime "missing
  WrapType implementation" on const-ref returns and compile-time
  `decltype incorrect argument` on namespace/identifier collision.
- popup-id-stack-mismatch-stateful-overload: open_popup /
  open_popup_on_item_click with a string id outside the popup
  container body fails because the trigger and BeginPopup hash
  against different ID-stack depths.
- registry-path-brackets-vs-slash-indexed-vs-container: indexed
  widget paths use IDENT[N], container paths use IDENT/N; mixing
  them (test/record wait targets with /0) silently no-ops.
- sort-specs-block-arg-helper-for-sortable-data-table: the
  TableSortSpec struct + `sort_specs() $(specs) { ... }` pattern
  that wraps the cpp ImGuiTableSortSpecs pointer drain.
- synced-table-instances-shared-boost-ident-in-a-for-loop: sharing
  the same boost IDENT across iterations of a `for` loop in
  dasImgui binds BeginTable to one table instance — synced layout
  is the intended behavior.
- with-id-for-per-iter-id-namespacing-inside-loops: `with_id(i) { ... }`
  is the daslang shape for ImGui's PushID/PopID per-iter
  namespacing when widget IDENTs are literal strings.
- imgui-angled-headers-row-plus-normal-headers-row-both-intentional:
  cpp imgui_demo calls TableAngledHeadersRow AND TableHeadersRow —
  the angled row has no column context menu, so both rows are
  intentional.

daslang card:

- table-safe-index-safe-only-with-null-coalescing: `table?[key]` is
  safe ONLY when immediately consumed by `??`. Bare `tab?[k]` /
  stored pointer escapes the table's storage and the compiler
  enforces `unsafe()`. Mirrors the rule just landed in CLAUDE.md
  (commit 760b69fac).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-2026-05-19

mouse-data: 8 dasImgui + 1 daslang cards from PR-D / PR-E work
The lint pass rejected C++ reserved words as variable/struct/class/field/
enum/enum-value names because the AOT C++ emitter would otherwise produce
invalid C++ (struct char {}, for (auto& register : ...), ...). Function
names already had this restriction dropped in 2026-05-14 by extending
aotSuffixNameEx to detect keywords and prepend _Func.

This continues that pattern across the remaining sites:

ast_lint.cpp drops 8 isCppKeyword(...) guards:
- enum / enum-value / structure / structure-field names
- global-let / for-iter / let / type-arg variable names
- function-argument / block-argument names
Module-name guard stays (modules emit raw into namespace X { ... }).

daslib/aot_cpp.das extends the mangling:
- aotFieldName adds _f_ prefix for keyword names (already did this for
  DELETE to avoid Windows SDK winnt.h macro collision).
- aotStructName passes "_S" suffix instead of "" - aotSuffixNameEx
  prepends it only when the name is a keyword, so non-keyword structs
  emit unchanged.
- New helpers aotEnumName (_E) and aotEnumValueName (_V) mirror the
  same shape for enums.
- Replaces 5 raw enumType.name / structType.name emission sites with
  the helpers (type-decl emission, enum class decl/value decl, enum
  value access expressions, struct rtti field offsetof site).
- aotSuffixNameEx gains a string overload that forwards to the
  das_string one so callers can pass either.

Tests:
- tests/language/cpp_keywords_as_names.das (new): positive coverage
  for every now-allowed shape - for-iter, let, global var, struct
  fields, struct name, class name, enum name, enum values, typedef
  alias, function arg, function-type arg name, lambda arg. Uses C++
  keywords that aren't daslang reserved (char, register, short,
  extern, mutable, volatile, signed, long, union).
- tests/aot/test_cpp_keyword_names.das extended to mirror every
  shape through AOT codegen alongside the existing function-name
  coverage.
- tests/language/failed_reserved_names.das deleted - supplanted.

Interpreter: 7920 tests, 7914 passed, 6 skipped, 0 failed.
AOT: 7309 tests, 7303 passed, 6 skipped, 0 failed.
Lint + das-fmt --verify pass on all 3 changed .das files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ake-while-skip-while

linq_fold: take_while/skip_while predicate-driven ranges (PR-G)
Two new bit flags on Structure::FieldDeclaration that make class-method
abstractness and parent-inheritance state inspectable from AST macros
and tooling:

- _abstract (0x400): set when the method is declared `def abstract`
  (init=null, body must come from a derived class). Cleared on override.
- inherited (0x800): set on fields cloned from a parent class that are
  not (re)declared in this class. Cleared when the field is declared or
  overridden locally.

parentType keeps its existing narrow semantic (type pending from parent
auto-resolution; transient, cleared during inference). A new explicit
"inherited" bit avoids overloading it.

Both bits are exposed via fld.flags._abstract / fld.flags.inherited in
the FieldDeclarationFlags bitfield binding. AstSerializer bumped to 85.

Tests under tests/language/ verify the three flag combinations
(declared-here-abstract, inherited-from-parent, overridden) at compile
time via a [structure_macro] helper, plus a runtime override-dispatch
sanity check.
…-as-names

allow C++ keywords as daslang identifiers (vars, structs, fields, enums)
…abstract-inherited-flags

ast: add _abstract and inherited bits to FieldDeclaration
Phase 1 of the linq zip extension plan (LINQ_TO_DECS.md). Extends the existing
2-ary and 3-ary `zip` family to arities 4..8 with full overload coverage per
arity:

- lockstep iter / array / zip_to_array (no selector)
- result-selector iter / array / zip_to_array (block<...>)
- private `zipN_impl` / `zipN_impl_const` helpers per arity

Also adds a `static_if` reserve in `select_many_impl` mirroring the existing
zip pattern — closes a pre-existing PERF006 hint on length-bearing sources.

Tests in `tests/linq/test_linq_transform.das` cover all arities and overloads
(72 tests for zip; 1047 total linq tests including AOT mode green). LINT003/002
on pre-existing test functions also fixed per the "fix all lint" rule.

`benchmarks/sql/LINQ_TO_DECS.md` documents the multi-phase plan: this PR is
Phase 1; future phases add `plan_zip` splice in linq_fold (Phase 2),
`from_decs_template(type<Foo>)` macro (Phase 3), and `plan_from_decs_template`
splice for SoA-without-buffer decs iteration (Phase 4).

Mouse card documents an intermittent daslang bug observed during this work —
a 4-ary block call with lambda params `p, q, r, s` returning stale first-param
values. Could not produce reliable minimal repro; workaround documented.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
linq: N-ary zip family extended to arities 4-8
…count

Adds plan_zip planner ahead of plan_loop_or_count in the _fold cascade.
Recognizes 2-ary zip(srcA, srcB) chains and splices them into a single
multi-iterator for-loop with no intermediate tuple buffer.

Z1 — no-terminator: emits invoke($($i(srcA), $i(srcB)) { var buf : array<tuple>;
[reserve when both have length]; for (itA, itB in srcA, srcB) { buf |>
push_clone((itA, itB)); }; return <- buf [|> to_sequence_move] }, srcA, srcB).
Covers zip(arr, arr), zip(iter, iter) |> to_array, and zip(iter, iter) →
iterator. Shortest-source truncation comes free with daslang's multi-iter for.

Z2 — count/long_count terminator: when both sources are length-bearing,
emits the length-min shortcut directly (int / int64). Otherwise emits a
counter loop (acc++, no buffer). No-pred only — predicated count deferred.

Z7 — trims `zip` from is_buffer_required_op marker arm (now dead since
plan_zip catches all zip-led chains before plan_loop_or_count).

Z3 (fused chain ops: where_/select/take/skip between zip and terminator)
and Z4..Z6 (3..8-ary mechanical expansion) deferred to a stacked follow-up.

Verification: lint clean; 154/154 test_linq_fold_ast pass; full linq suite
green under interpret (test_linq* — 480+ tests); 1103/1103 under -use-aot.
…lan-zip

linq_fold: plan_zip splice arm (Phase 2A — bare zip + count/long_count)
@pull pull Bot locked and limited conversation to collaborators May 19, 2026
@pull pull Bot added the ⤵️ pull label May 19, 2026
@pull pull Bot merged commit b9c255e into forksnd:master May 19, 2026
@pull pull Bot had a problem deploying to github-pages May 19, 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