Skip to content

Commit 64d23ab

Browse files
borisbatclaude
andcommitted
linq_fold: PR E R1 — gate Array pass on no-bridge (Copilot #1)
Pre-existing latent bug surfaced by the PR E refactor. peel_each only unwraps `each(<array>)`, so `extract_decs_bridge(top)` still succeeds on a from_decs eager-bridge source after the Decs pass. If the Decs pass cascaded (e.g. emit_group_by's non-primitive join-key bail at linq_fold.das:4475), the Array pass would re-match the same decs-only row (decs_source predicate stays true) and emit_group_by would take its Array branch — silently dropping the upstream_join capture and mis-splicing the chain. Same shape existed in master (plan_decs_group_by stub cascaded -> plan_group_by stub re-matched group_by_decs row with Array adapter). Never exercised by tests because no in-tree fixture combines from_decs + non-primitive-key join + group_by, but the path was real. Fix: in try_splice_patterns, return null after the Decs pass when bridge != null. Cascaded decs chains now fall through to tier-2 fold_linq_default — the safety net that materializes the bridge buffer and re-runs the chain — instead of risking a wrong-adapter emission. doc/source/reference/linq_fold_patterns.rst "How dispatch works": restated pass 2 as "runs only when bridge == null", with the WHY inline (peel_each leaves the eager bridge intact, so without the gate decs_source predicates would still match in the Array pass). Verification: 1689/1689 tests/linq + 9809/9815 full suite (same baseline). No test changed result, confirming nothing was relying on the bug-path emission. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 2ff10e2 commit 64d23ab

2 files changed

Lines changed: 13 additions & 8 deletions

File tree

daslib/linq_fold.das

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6487,6 +6487,8 @@ def private try_splice_patterns(var expr : Expression?) : Expression? {
64876487
}
64886488
}
64896489
}
6490+
// Cascaded decs chains fall through to fold_linq_default rather than risk a decs-only row matching here (peel_each doesn't strip the eager-bridge ExprInvoke, so decs_source stays true) and emitting via Array adapter — see linq_fold_patterns.rst "How dispatch works".
6491+
if (bridge != null) return null
64906492
top = peel_each(top)
64916493
let srcName = qn("source", at)
64926494
for (p in splice_patterns) {

doc/source/reference/linq_fold_patterns.rst

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,28 @@ described below, and hands the result to ``try_splice_patterns`` in
2929
``splice_patterns`` table (17 rows, one per arm listed in this
3030
document) twice:
3131

32-
1. **Decs adapter pass.** Skipped unless
32+
1. **Decs adapter pass.** Runs only when
3333
``extract_decs_bridge(top) != null`` (i.e. the source is
3434
``from_decs_template(...)``). Each row's ``requires`` predicates
3535
gate the match; rows with an ``array_source`` predicate fail
3636
here and fall through. First emit that returns non-null wins.
37-
2. **Array adapter pass.** Runs with ``peel_each(top)``. Rows with
38-
``decs_source`` / ``decs_join_invariants`` predicates fail this
39-
pass; the remaining rows match against the (now-peeled) top.
37+
2. **Array adapter pass.** Runs only when ``extract_decs_bridge(top)
38+
== null``, i.e. the source is **not** a decs eager bridge. Top is
39+
first ``peel_each``-unwrapped. Decs chains never reach this pass:
40+
if the Decs pass above cascades on every row, control falls
41+
through to ``fold_linq_default`` instead. (This is deliberate —
42+
``peel_each`` does not strip the eager-bridge ``ExprInvoke``, so
43+
without this gate the ``decs_source`` predicate would still
44+
succeed on a decs source in the Array pass and decs-only rows
45+
could match and emit via ``SourceAdapter::Array``, silently
46+
dropping adapter-specific captures like ``upstream_join``.)
4047

4148
If neither pass emits, the Theme 6 perf-warn fires for
4249
``from_decs_template`` chains (telling the user the bridge will
4350
materialize and tier-2 will run on the buffer), then control falls
4451
through to ``fold_linq_default`` (the iterator-materializing tier-2
4552
path) and finally to a raw passthrough.
4653

47-
The "decs-first" rule is preserved at the dispatcher level rather
48-
than per-row, so a chain starting with ``from_decs_template(...)``
49-
always tries every decs-eligible arm before any array arm.
50-
5154
.. note::
5255

5356
Labels of the form ``plan_<X>`` (e.g. ``plan_distinct``,

0 commit comments

Comments
 (0)