Skip to content

fix(jq): link each dependency module once and forward to it through stubs (#2955, #3058) - #3150

Merged
newhoggy merged 5 commits into
mainfrom
issue-2955-triage-module-memory-compound
Sep 18, 2026
Merged

newhoggy merged 5 commits into
mainfrom
issue-2955-triage-module-memory-compound

Conversation

@newhoggy

Copy link
Copy Markdown
Contributor

Closes #2955. Closes #3058.

What

Binding a module's include/import dependencies by copying their bodies into every def that reached them compounded down a module chain (F^L copies of the bottom level). A dependency module is now emitted once, outermost in process_program, as an ordinary run whose begin marker carries a hidden alias and whose defs are named <NUL>link:<id>::<name>; each consuming def's body is wrapped in one forwarding stub per dependency (name, arity) it calls directly. Inside the run a bare sibling call is retried under the alias by the code #2989 added for imported modules, so the evaluator is untouched. The one resolver change is that scan_scope steps over an open floor for a link-name lookup. #2962's rename machinery and the transitive closure are gone. ADR-0023 gains the amendment with the soundness argument.

This is the triage plan on #2955, simplified after an adversarial re-derivation against the current code (no new marker kinds, no alias defs, no name-hiding rule) and with the hoisted run filtered to the defs reached by name from the main filter, which a review of the closure found must retry an imported module's bare sibling calls under its alias (pinned).

Measured (release, Apple M-series, /usr/bin/time -l, the issue's generator; output identical to jq on every row)

chain before after jq 1.7.1
6 x 40 x 1 12 MB 9 MB 2 MB
8 x 6 x 3 142 MB 34 MB 2 MB
12 x 4 x 2 162 MB 36 MB 2 MB
14 x 4 x 2 681 MB, 0.38 s 112 MB, 0.03 s 2 MB
1000-def utility module, one def used through two modules 9 MB 9 MB 2 MB

Processed-program node count at 6 / 8 / 10 levels: 1253 / 5093 / 20453 before, 169 / 241 / 313 after (link_size_guard_2955, which fails against the old loader). Recursion ceiling through a linked dependency: 19494 before and after.

M4 Pro, idle, interleaved, 25 reps, medians: [range(1e5) | f] with f calling one dependency through a stub +1.6%, against +3.3% drift on the same defs inline (neutral within noise). A 20-module x 50-def chain with one def used: 20 ms → 9 ms. The shape that pays: the same chain with all 50 top-level defs used links all 950 dependency defs into the top-level chain: 32 ms → 136 ms, because every chain def is installed over the whole program below it at bind time — the evaluator's pre-existing O(M x N) (a single 3000-def include costs 1 GB / 0.5 s today). Recorded in limitations.md and filed with #3148.

What remains above jq's 2 MB is the evaluator's per-call bound-body retention (#3148, filed from this: the same 56 defs in one file cost 58 MB, fib(20) 234 MB, fib(21) overflows the stack). #3149 records a $param-generator divergence found while pinning rows; it predates this change and is not touched.

Behaviour changes, all captured from /usr/bin/jq 1.7.1

  • A compile error inside a dependency reached from two defs is reported once (jq: a compile error in a module dependency reached from two defs is reported once per copy #3058). Residual: a module that is both a top-level include and a dependency, reached both ways, still reports twice (documented).
  • Cross-module compile errors now come out in jq's order: a dependency's before its includer's, last-declared dependency first, deepest first (was includer-first).
  • import "inner3" as i; def h: i::k; inside a module, where k calls its sibling bare, now answers [42] (was g/0 is not defined).

All 30 rows of test_dependencies_are_bound_in_their_own_scope_2962 and the #2865/#2951/#2989/#2774/#2857/#2991/#2740/#2971/#2395/#2682 module families are unchanged.

Verification

cargo fmt --check, both ci.yml clippy sets, cargo doc --all-features, cargo test --features cli, cargo test, cargo test --no-default-features — all green on the rebased tree.

@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown

Coverage

Total: 93.96% ⚪ 0.01 pp vs main

Comparing d45fec1..0bcfcb4 (merge-base → PR head)

File Before After Δ
src/bin/succinctly/jq_runner.rs 94.72% 94.8% 🟢 0.07 pp
🔇 0 ignored region(s), 175 tolerated region(s)

ignore removes the lines from both reports; tolerate keeps them in the reported percentage but scores them against the baseline, so a cross-run flip cannot move a delta. Regions are read from each revision's own source.

File Kind Lines Rev Reason
src/bin/succinctly/jq_runner.rs tolerate 1750-1762 both unreachable: try_parse_meta_op only fires under ParserMode::Yq (src/jq/parser.rs), and rewrite_namespaced_calls is only reached via ModuleProcessor::process_program, which jq_runner's own jq-mode run is the sole caller of -- so a MetaAssign node can never reach this function (#798)
src/bin/succinctly/jq_runner.rs tolerate 2375 both unreachable by construction: every error this wrapper receives today is a decode or nesting-depth failure the evaluator raised itself, never error(v); kept so a future one is rendered rather than dropped (#2999)
src/bin/succinctly/jq_runner.rs tolerate 2376 both see the arm above (#2999)
src/bin/succinctly/jq_runner.rs tolerate 2939-2942 both unreachable in a single-process run by construction: run_id_for (the sole source of an origin id) always inserts a run_origins entry for the id it hands back -- from a real load's canonical path, or its own literal-path fallback on a resolve failure -- and a def body only ever gets stamped with an origin after its module loaded successfully, so at always names a file that existed and was readable moments earlier. Reaching this arm needs that same file to vanish (or become unreadable) in the narrow window between that load and this re-read, entirely outside this process's control (#2964)
src/bin/succinctly/jq_runner.rs tolerate 3199 both unreachable: widening the shadow-candidate set never rejects a program the first parse accepted -- a newly covered name only wraps an already-successful dedicated parse, and a failing one would have propagated its error in the first parse too, so the retry budget is charged at the identical sites in both (#2395)
src/bin/succinctly/jq_runner.rs tolerate 9538 both unreachable in a passing suite by design -- the fixed b\
src/bin/succinctly/main.rs tolerate 1430 both the CLI suites drive yq through the syq multi-call arm above; this arm is the same call reached only when spelled succinctly yq (#2999)
src/bin/succinctly/yq_runner.rs tolerate 1652 both unreachable: bytes already parsed successfully by every caller (#1350)
src/bin/succinctly/yq_runner.rs tolerate 3318 both unreachable: path is always the raw output of the path(TARGET) builtin evaluated a few lines up in resolve_one_meta_assign -- path/1 is a jq/yq language invariant that always answers an array of path components (see Expr::Builtin(Builtin::PathNoArg) => Ok(Some(OwnedValue::Array(..))) in eval_generic.rs), never any other shape (#798)
src/bin/succinctly/yq_runner.rs tolerate 3420 both unreachable: resolve_meta_assign_writes runs expr through this before any evaluation begins (see its own doc comment), and Expr::Shared is never constructed by the parser -- only at eval time, by function-call argument substitution (substitute_func_param in eval.rs) -- so a pre-evaluation AST can never contain one here (#798)
src/bin/succinctly/yq_runner.rs tolerate 4052 both unreachable: every arm of the match result { .. } above that assigns docs (L3492-3622) constructs Ok(..) -- none ever produces Err, so this if let's implicit else can't be taken; symmetric to L1625's ? (#798)
src/jq/document.rs tolerate 1125-1132 both unreachable: both implementors (StandardJson, YamlValue) override this to decode once; the default exists as the contract a future implementor inherits, and is deliberately the two-call sequence it replaces (#965)
src/jq/eval.rs tolerate 1142 both unreachable: def is always a collect_alias_groups anchor path, which step_to_expr never fails on (#1351)
src/jq/eval.rs tolerate 1163 both unreachable: redirect_paths with Redirect::SINGLE always contributes exactly one output per input, so a 1-element paths always pops Some (#1351)
src/jq/eval.rs tolerate 1172 both unreachable: a concrete setpath/delpaths path's components are always Field/Index -- step_to_expr never produces another shape (#1351)
src/jq/eval.rs tolerate 1176 both unreachable: the map above never yields None, since it only ever matches Field/Index (#1351)
src/jq/eval.rs tolerate 4388 both unreachable: is_escape() is exactly `Error
src/jq/eval.rs tolerate 4389 both unreachable: see the if let above -- push_owned_values never answers None for an is_escape() result (#2180)
src/jq/eval.rs tolerate 5555 both unreachable today: to_owned's only failures are is_decode_failure()-tagged, and suppresses() answers false for those whatever optional is -- the same defensive-but-dead arm eval_generic's own Builtin::Path materialization documents under #2280 (#2908)
src/jq/eval.rs tolerate 6301 both unreachable: every Demand::Stop the sink answers is preceded by outcome = Some(..), handled just above (#2872)
src/jq/eval.rs tolerate 7446 both unreachable: optional is never true here. eval_each is entered with a forced true at exactly one site (Expr::Optional over an IndexExpr/SliceExpr), and both of those evaluate their target (eval_index_expr) and their key (eval_each(key, .., false)) with a hardcoded false, so only the final index/slice step ever sees it -- nothing carries it down to an Expr::Object (#2180)
src/jq/eval.rs tolerate 10732 both unreachable: every entry point supplies path context, so path never evaluates without one; pre-existing zero-hit line; #2999 changed only how its array payload is constructed
src/jq/eval.rs tolerate 10862 both unreachable: only ever constructed by builtin_sort_keys's own eval_update_no_vivify call, whose enclosing eval_update_impl already runs to_owned on the whole document up front (#2855) -- a decode failure anywhere raises there, before this filter ever sees a value to re-decode; confirmed live, sort_keys(.a)/sort_keys(..) on a document with a decode-failure subtree both raise from the outer to_owned
src/jq/eval.rs tolerate 21925 both unreachable: escape_with_prefix! sets terminal before Demand::Stop; already returned above (#2138)
src/jq/eval.rs tolerate 22294 both unreachable: escape! sets terminal before Demand::Stop; already returned above (#2546)
src/jq/eval.rs tolerate 30257 both reachable only on a genuine allocation failure: Vec::try_reserve(1) on a vector that has spare capacity, or can grow, cannot fail -- this is the whole purpose of the branch, converting an OOM into a catchable error rather than an abort (ADR-0018's 'would take the host process down' exception). The pre-#2267 form of the same guard, out.try_reserve(branches.len()) in resolve_index_expr/resolve_slice_expr, was 0-hit for the identical reason (#2267)
src/jq/eval.rs tolerate 30258 both see the line above -- the Demand::Stop half of the same allocation-failure-only branch (#2267)
src/jq/eval.rs tolerate 30264 both reachable only when the try_reserve above failed, i.e. only on a genuine allocation failure (#2267)
src/jq/eval.rs tolerate 32544 both unreachable: is_primitive admits only Identity/Field/Index/Slice, and of those only a Slice's computed bounds can halt -- all four have their own arm in resolve_node_sink/resolve_node_eager, so none reaches this function. Pre-existing; #2694 only wrapped the return in Some (#2694)
src/jq/eval.rs tolerate 32578 both unreachable, as this arm's own comment above says: indexing or slicing a value yields zero or one result, so is_primitive never produces more than one -- kept as a named error rather than a panic. Pre-existing; #2694 only wrapped the enclosing return in Some (#2694)
src/jq/eval.rs tolerate 34999 both unreachable: every Demand::Stop the sink answers is preceded by outcome = Some(..), handled just above (#2872)
src/jq/eval.rs tolerate 36031 both unreachable: every Demand::Stop the sink answers is preceded by outcome = Some(..), handled just above (#2872)
src/jq/eval.rs tolerate 36055 both unreachable: every arm of the loop's last iteration returns -- a walk refusal, an exhausted walk, and a step outcome that never retries on the last alternative (#2979, #2872)
src/jq/eval.rs tolerate 36602 both unreachable: every Demand::Stop the sink answers is preceded by outcome = Some(..), handled just above (#2872)
src/jq/eval.rs tolerate 36622 both unreachable: every path through the loop's last iteration returns -- a walk refusal, an exhausted walk, and a step outcome that never retries on the last alternative (#2979, #2872)
src/jq/eval.rs tolerate 39313 both unreachable in a passing suite by design -- a panic-message format argument for the #682 single-valued-tail pin, evaluated only if that assert's own condition is false (#2190)
src/jq/eval.rs tolerate 39390 both unreachable: the only caller reaches this after classify_static_component answered Field for this same value, which it does only for an object (#2190)
src/jq/eval.rs tolerate 39434 both unreachable: both callers establish the container first -- navigate_static_component_ref via classify_static_component's Index arm, and walk_path's Expr::Iterate arm by matching on the container itself (#2190)
src/jq/eval.rs tolerate 41828 both unreachable: every Demand::Stop the sink answers is preceded by outcome = Some(..), handled just above (#2872)
src/jq/eval.rs tolerate 42005 both pre-existing zero-hit line; #2999 changed only how its array payload is constructed
src/jq/eval.rs tolerate 43290 both unreachable: stop_with_escape's only write is slot.set(Some(control)) with the control it was handed, which is always the Control::Error built one line above (#2180)
src/jq/eval.rs tolerate 43637 both unreachable: on_update records a step_outcome before every Demand::Stop it answers, and the fallback match runs only when it recorded none (#2872)
src/jq/eval.rs tolerate 43660 both unreachable: every Demand::Stop the sink answers is preceded by outcome = Some(..), handled just above (#2872)
src/jq/eval.rs tolerate 44183 both unreachable by construction: the per-fork match only ever hands stop_with_downstream a non-Exhausted flow, so terminal can never hold Exhausted. foreach_forks' identical arm is 0-hit for the same reason and is only unflagged because it predates this diff (#2899)
src/jq/eval.rs tolerate 46939 both pre-existing zero-hit line; #2999 changed only how its array payload is constructed
src/jq/eval.rs tolerate 48912 both pre-existing zero-hit line; #2999 changed only which null constant it names
src/jq/eval.rs tolerate 51546 both defensive, unreachable via ordinary JSON parsing (#3068)
src/jq/eval.rs tolerate 51549 both defensive, unreachable via ordinary JSON parsing (#3068)
src/jq/eval.rs tolerate 51590 both optional is never true through either caller of this function -- confirmed live (eprintln! probe): `[1,2,3]
src/jq/eval.rs tolerate 51599 both every array literal written in filter source, and every array-element JSON parses, decodes to NumberLiteral (#1035), not a bare Int -- Int is for internally-synthesized values spliced post-parse; probed a computed element ([1970,(0+0),..]) and it still decoded as NumberLiteral here, so this arm has no known real producer (#3068)
src/jq/eval.rs tolerate 51614 both optional is never true through this call path -- see the array-length check above (#3068)
src/jq/eval.rs tolerate 51629 both optional is never true through either caller of this function, same as the array-length check above (#3068)
src/jq/eval.rs tolerate 53394 both unreachable from combinations, whose own empty-input return runs first; pre-existing zero-hit line; #2999 changed only how its array payload is constructed
src/jq/eval.rs tolerate 54489 both confirmed live (eprintln! probe): optional is always false in builtin_abs regardless of whether the filter writes abs?, since that suppression happens entirely outside builtin dispatch here -- the same 'optional is never true here' shape eval.rs already documents elsewhere (#2180) (#3041)
src/jq/eval.rs tolerate 56467 both unreachable: the sink is a plain collector that always answers Demand::Continue (#2872)
src/jq/eval.rs tolerate 56577 both unreachable: optional: false makes index_one_owned answer Ok(Some)/Err only (#2872)
src/jq/eval.rs tolerate 56723 both unreachable: pattern_has_computed_key is false for Pattern::Var, so the loop walker above always takes it (#2872)
src/jq/eval.rs tolerate 56770 both unreachable: every caller gates on pattern_has_computed_key being false (#2872)
src/jq/eval.rs tolerate 57828 both unreachable in practice: a def with more than 32 parameters; the fallback exists so ScopeMask's one-bit-per-parameter u64 is a performance ceiling rather than a correctness limit (#2633)
src/jq/eval.rs tolerate 57854 both unreachable: bind_def_call only calls this for a non-empty params, and install_def_calls only builds a DefCall whose args.len() equals params.len(), so the zip is never empty here (#2560)
src/jq/eval.rs tolerate 57874 both unreachable in practice: needs more than 32 duplicated-name parameters; see the non-duplicate path's own note (#2633)
src/jq/eval.rs tolerate 57895 both unreachable: name was just read from params, so the zip over (params, args) has a matching pair unless args is shorter than params, which install_def_calls' own arity guard rules out (#2560)
src/jq/eval.rs tolerate 57915 both unreachable: params is non-empty here (bind_def_call's own guard) and its first entry is never skipped, so at least one substitution always ran (#2560)
src/jq/eval.rs tolerate 57976 both unreachable: same arity invariant as bind_def_call_params' own copy of this loop -- name came from params, so the zip has a matching pair unless args is shorter, which install_def_calls rules out (#2560)
src/jq/eval.rs tolerate 59294 both substitute_var_impl's FuncDef arm always returns FuncDef (#2283)
src/jq/eval.rs tolerate 59316 both substitute_var_impl's FuncDef arm always returns FuncDef (#2283)
src/jq/eval.rs tolerate 59334 both substitute_var_impl's FuncDef arm always returns FuncDef (#2283)
src/jq/eval.rs tolerate 59404 both substitute_var_impl's FuncDef arm always returns FuncDef (#2283)
src/jq/eval.rs tolerate 59418 both substitute_func_param_impl's FuncDef arm always returns FuncDef (#2555)
src/jq/eval.rs tolerate 66579 both unreachable in a passing suite by design -- this is the failure message for the assertion the test exists to make (#2190)
src/jq/eval.rs tolerate 78640 both unreachable in a passing suite by design -- this test's own diagnostic (#3071)
src/jq/eval.rs tolerate 79406-79411 both the closure is asserted never called below (on_update_calls stays 0) -- Err(_) with optional=true short-circuits fold_step_each before this sink runs (#3122)
src/jq/eval.rs tolerate 84083 both unreachable in a passing suite by design -- this test's own diagnostic (#3071)
src/jq/eval.rs tolerate 89925 both unreachable in a passing suite by design -- this test's own diagnostic (#3071)
src/jq/eval.rs tolerate 96481 both unreachable in a passing suite by design -- every filter this helper is called with parses to an AsPattern (#2649)
src/jq/eval.rs tolerate 96525 both unreachable in a passing suite by design -- a literal-key pattern's walk either completes or refuses with an error (#2872)
src/jq/eval.rs tolerate 96534 both unreachable in a passing suite by design -- every call site passes the origin of a binding this same test already proved carries a marker (#2649)
src/jq/eval.rs tolerate 96862 both unreachable in a passing suite by design -- the assertion above is the test (#2872)
src/jq/eval.rs tolerate 97509 both unreachable in a passing suite by design -- every row here is a shape jq accepts, confirmed live (#2649)
src/jq/eval.rs tolerate 97577 both unreachable in a passing suite by design -- every row here is a shape jq refuses, confirmed live (#2649)
src/jq/eval.rs tolerate 97962 both unreachable in a passing suite by design -- this is the panic message for the #2072 pin itself, only formatted if the let-else pattern fails to match (#2072)
src/jq/eval.rs tolerate 97973 both unreachable in a passing suite by design -- this is the panic message for the #2072 pin itself, only formatted if the let-else pattern fails to match (#2072)
src/jq/eval.rs tolerate 97994 both unreachable in a passing suite by design -- this is the panic message for the #2072 pin itself, only formatted if the match doesn't hit the expected arm above (#2072)
src/jq/eval.rs tolerate 98010 both unreachable in a passing suite by design -- this is the panic message for the #2072 pin itself, only formatted if the match doesn't hit the expected arm above (#2072)
src/jq/eval.rs tolerate 98095 both unreachable in a passing suite by design -- every expression this closure receives is built by marker above (#3037)
src/jq/eval.rs tolerate 98173 both unreachable in a passing suite by design -- rewrite_markers rebuilds the same node kind it was given (#3037)
src/jq/eval.rs tolerate 98200 both unreachable in a passing suite by design -- every expression this closure receives is built by marker above (#3122)
src/jq/eval.rs tolerate 98218 both unreachable in a passing suite by design -- rewrite_markers rebuilds the same node kind it was given (#3122)
src/jq/eval.rs tolerate 98286 both unreachable in a passing suite by design -- fires only if demote_for_reentry's own let-else assertion condition is false (#3122)
src/jq/eval.rs tolerate 98290 both unreachable in a passing suite by design -- fires only if demote_for_reentry's own let-else assertion condition is false (#3122)
src/jq/eval.rs tolerate 98330 both unreachable in a passing suite by design -- fires only if reroot's own let-else assertion condition is false (#3122)
src/jq/eval.rs tolerate 98334 both unreachable in a passing suite by design -- fires only if reroot's own let-else assertion condition is false (#3122)
src/jq/eval.rs tolerate 99812 both unreachable in a passing suite by design -- this is the panic message for the assertion above, only formatted if the match doesn't hit the Error arm (#3119)
src/jq/eval.rs tolerate 102204 both unreachable in a passing suite by design -- this is the failure message for the assertion this test exists to make (#2937)
src/jq/eval.rs tolerate 102358 both every pinned filter below yields an owned value; kept so a cursor answer still renders rather than panics (#2999)
src/jq/eval.rs tolerate 102359 both unreachable in a passing suite by design -- the failure message for the assertions this helper serves (#2999)
src/jq/eval_generic.rs tolerate 3729 both unreachable: the sole remaining caller (retain_truthy_generic's Many arm) runs to_owned on an item before keeping it, so re-converting a kept item here cannot fail; the ManyCursor caller that made this reachable went with the truthiness walk (#2692, re-establishing #2661's premise)
src/jq/eval_generic.rs tolerate 7092 both unreachable in a passing suite by design -- this is the panic message for the #2368 pin itself, only formatted if the assert's own condition is false (#2368)
src/jq/eval_generic.rs tolerate 8950 both the CLI evaluates every program that uses input/inputs on the eager route (jq_runner's can_use_lazy_path excludes them), so this guard never fires today -- #2968's identical guards on the arms above are equally unfired; kept for the day the lazy path admits such a program (#1309)
src/jq/eval_generic.rs tolerate 8967 both the CLI evaluates every program that uses input/inputs on the eager route (jq_runner's can_use_lazy_path excludes them), so this guard never fires today -- #2968's identical guards on the arms above are equally unfired; kept for the day the lazy path admits such a program (#1309)
src/jq/eval_generic.rs tolerate 10156 both unreachable: every Demand::Stop the sink answers is preceded by outcome = Some(..), handled just above (#2872)
src/jq/eval_generic.rs tolerate 11650 both unreachable: every producer that reaches this empty-exprs tail (each_lazy_keys_iterate_sink's sorted/!sorted arms, each_lazy_index_range_iterate_sink, each_lazy_seq_iterate_sink) yields OneCursorValue/OneCursor/Owned, never a cursorless GenericItem::One -- so cursor is always Some here; kept for exhaustiveness/symmetry with the Some arm (#2103)
src/jq/eval_generic.rs tolerate 14768 both unreachable: escape_generic!/ensure_owned! set terminal before Demand::Stop; already returned above (#2138)
src/jq/eval_generic.rs tolerate 15122 both unreachable: escape! sets terminal before Demand::Stop; already returned above (#2546)
src/jq/eval_generic.rs tolerate 15714 both unreachable by construction: key_elements_generic builds every key of a run from the same Option<&Expr> (#2999)
src/jq/eval_generic.rs tolerate 15724 both unreachable by construction: key_elements_generic builds every key of a run from the same Option<&Expr> (#2999)
src/jq/eval_generic.rs tolerate 16237 both pre-existing zero-hit line; #2999 changed only how its array payload is constructed
src/jq/eval_generic.rs tolerate 17919 both unreachable: len_checked and SliceBounds::resolve already bound every index in range to [0, len), so get_cursor cannot miss (#2168)
src/jq/eval_generic.rs tolerate 20702 both unreachable by construction: path_context_resolvable admits an any(cond) read only through admits.prefetch, so the rewriter always has one -- the AnyCond arm above carries the identical assertion (#3079)
src/jq/eval_generic.rs tolerate 20706 both unreachable by construction, see above
src/jq/eval_generic.rs tolerate 20707 both unreachable by construction, see above
src/jq/eval_generic.rs tolerate 21511 both pre-existing zero-hit line; #2999 changed only how its array payload is constructed
src/jq/eval_generic.rs tolerate 21532 both pre-existing zero-hit line; #2999 changed only how its array payload is constructed
src/jq/eval_generic.rs tolerate 21875 both unreachable: malformed_object_member above already proved every key stringifies (the None half), and to_owned_cursor on an untagged key decoded_key_str decoded cannot fail (the Err half) (#2785)
src/jq/eval_generic.rs tolerate 21956 both unreachable: map(f) over an array emits exactly one array; kept so a future map shape produces no output rather than a panic (#2785)
src/jq/eval_generic.rs tolerate 22243 both unreachable by design -- eval_single's #2368 debug_assert forbids optional=true on Builtin::Reverse, so length never answers None here (#2730)
src/jq/eval_generic.rs tolerate 22847 both the CLI evaluates every program that uses input/inputs on the eager route (jq_runner's can_use_lazy_path excludes them), so this guard never fires today -- #2968's identical guards on the arms above are equally unfired; kept for the day the lazy path admits such a program (#1309)
src/jq/eval_generic.rs tolerate 22853 both see above: the input-queue deferral never fires from the CLI
src/jq/eval_generic.rs tolerate 24297 both unreachable by construction: every shape either guard admits now has an arm above (#2771), and expr_dispatch_catchall_guards_default_conservatively_2549 pins both guards' _ => false defaults directly
src/jq/eval_generic.rs tolerate 24845 both unreachable in a passing suite by design -- owned_identity_rule maps a bare Expr::Var to Bound too (for the static gate, which sees a body before its as substitution runs), but every runtime dispatch that reaches this rule (owned_identity_after_stage/owned_identity_placed_by, from owned_identity_leaving_cursor's Bound arm) only ever sees a stage after eval_owned_identity_as's unconditional substitute_bound_var_from call, which always turns $x into Expr::TrackedVar before recursing -- confirmed by running the full suite with this arm replaced by a hard panic!(), which never fired (#2072)
src/jq/eval_generic.rs tolerate 24863 both unreachable: this Slice rule's own two execution paths both exclude Expr::Slice before ever reaching owned_identity_placed_by -- eval_owned_identity_stages's catch-all only runs a stage owned_identity_nav_supported refused, and owned_identity_leaving_cursor (identity_from_first, eval_generic.rs:10724) only runs a stage path_context_is_navigational refused -- and both predicates admit Expr::Slice (owned_identity_nav_supported/path_context_is_navigational_at each list Expr::Slice { .. } => true), so a bare slice is always resolved by owned_identity_step's own arm first. Kept in owned_identity_rule's match for exhaustiveness/symmetry with the rule table's other entries, the same reason #2072's Bound arm above is kept unreachable-by-construction (#2966's review of #2834)
src/jq/eval_generic.rs tolerate 26362 both optional is never true through this pipe: ? is eval_owned_identity_try, which catches the escape instead, and after #693 only the IndexExpr/SliceExpr special case ever sets it -- kept as any_all_f's scalar_fallback mirror
src/jq/eval_generic.rs tolerate 26363 both optional is never true here, see above
src/jq/eval_generic.rs tolerate 26766 both unreachable in a passing suite by design -- the failure message for the assertion the tests below make (#2999)
src/jq/eval_generic.rs tolerate 26905 both unreachable in a passing suite by design -- the fixture's map(.+1) is always a LazySeq; this arm is the test's own diagnostic (#2666)
src/jq/eval_generic.rs tolerate 26933 both unreachable in a passing suite by design -- the fixture's only escape is Control::Error; this arm is the test's own diagnostic (#2666)
src/jq/parser.rs tolerate 226 both unreachable: parse_join_expr only calls join_expr with two to four arguments (#3046)
src/jq/parser.rs tolerate 248 both unreachable: join_expr builds only this shape, and only join_expr names JOIN_IDX_VAR, which no program can spell (#3046)
src/jq/parser.rs tolerate 250 both unreachable: join_expr builds only this shape, and only join_expr names JOIN_IDX_VAR, which no program can spell (#3046)
src/jq/parser.rs tolerate 252 both unreachable: join_expr builds only this shape, and only join_expr names JOIN_IDX_VAR, which no program can spell (#3046)
src/jq/parser.rs tolerate 259 both unreachable: join_expr builds only this shape, and only join_expr names JOIN_IDX_VAR, which no program can spell (#3046)
src/jq/parser.rs tolerate 263 both unreachable: join_expr builds only this shape, and only join_expr names JOIN_IDX_VAR, which no program can spell (#3046)
src/jq/parser.rs tolerate 279 both unreachable: join_expr builds only this shape, and only join_expr names JOIN_IDX_VAR, which no program can spell (#3046)
src/jq/parser.rs tolerate 8949 both unreachable in a passing suite by design -- this is the panic message for the #3044 pin itself, only formatted if the let-else pattern fails to match (#3044)
src/jq/resolve.rs tolerate 2300 both unreachable in practice today: this arm needs builtin_fallback==None (the name was never a shadow candidate) yet is_jq_builtin==true (a real jq builtin at this arity) -- every implemented builtin's own dedicated parse already lowers that shape to Expr::Builtin before resolve.rs ever runs, and #3042/#3046 closed the once-real 'unimplemented builtin' gap this existed for (see JQ_BUILTIN_ROSTER's own doc comment)
src/jq/resolve.rs tolerate 2302 both unreachable with the current roster: every JQ_BUILTIN_ROSTER entry of arity >= 1 already has a dedicated parser form (a matches_keyword special case or a Libm1/Libm2/Libm3::ALL entry -- confirmed by cross-referencing the full roster against both), so it is parsed straight to Expr::Builtin and never reaches here as a bare FuncCall. This arm exists for a roster name with no dedicated parse yet and a nonzero arity -- there is none today, so the loop body is reached with an empty args on every pinned-suite run (355 hits on the arm's own condition, 0 in the loop) and would only start executing if such a name were added (#2964)
src/jq/share_stats.rs tolerate 97 both process-global env var; exercised by the CLI audit run, not by an in-process test (#2999)
src/jq/value.rs tolerate 358 both unreachable by construction: the slow path is entered only after significant_digit_count found 18+ digits over the same mantissa bytes this loop walks (#2936)
src/jq/value.rs tolerate 382 both unreachable by construction: 18+ significant digits were counted, so at least one nonzero digit was kept (#2936)
src/jq/value.rs tolerate 2771 both unreachable: format_float_with_fraction of a finite double is always RFC 8259 number text, which parse_i64_or_f64 reads (#2936)
src/jq/value.rs tolerate 4541 both unreachable in a passing suite by design -- every caller built the value as an array (#2999)
src/jq/value.rs tolerate 4784 both unreachable in a passing suite by design -- the value was built as this container a few lines above (#2999)
src/jq/value.rs tolerate 5603 both unreachable in a passing suite by design -- the failure message for a malformed oracle table (#2936)
src/json/light.rs tolerate 7068 both unreachable in a passing suite by design -- the failure message for the assertion this #2877 test exists to make
src/json/light.rs tolerate 7150 both unreachable in a passing suite by design -- the failure message for the assertion this #2877 test exists to make
src/json/light.rs tolerate 7238 both unreachable in a passing suite by design -- the failure message for the assertion this #2877 test exists to make
src/json/light.rs tolerate 7263 both unreachable in a passing suite by design -- this is a panic-message format argument for the #2072 pin itself, only evaluated if the assert's own condition is false (#2072)
src/json/light.rs tolerate 7274 both unreachable in a passing suite by design -- this is a panic-message format argument for the #2072 pin itself, only evaluated if the assert's own condition is false (#2072)
src/json/light.rs tolerate 7355 both unreachable in a passing suite by design -- this is a panic-message format argument for the #2072 pin itself, only evaluated if the assert's own condition is false (#2072)
src/util/simd/x86.rs tolerate 208-258 both CPU-gated: the avx512f early-return only executes on Zen 4+ / Skylake-X runners, and its absence changes which AMD/Intel branch below executes too (#2449)
src/yaml/index.rs tolerate 1268 both unreachable: YamlIndex::build always wraps the parsed document(s) in a virtual root Sequence, at TY index 0 (#798)
src/yaml/index.rs tolerate 1272 both unreachable: every fixture field_key_head_foot is called with in this test module is a top-level mapping (#798)
src/yaml/index.rs tolerate 1283 both unreachable: a mapping key is always emitted as YamlValue::String -- it is never type-inferred like a value (#222), so this if-let's pattern can never fail to match (#798)
src/yaml/index.rs tolerate 1285 both unreachable: every call to field_key_head_foot in this test module passes a key that the fixture's mapping actually has (#798)
src/yaml/index.rs tolerate 1295 both unreachable: YamlIndex::build always wraps the parsed document(s) in a virtual root Sequence, at TY index 0 (#798)
src/yaml/index.rs tolerate 1299 both unreachable: every fixture seq_item_head_foot is called with in this test module is a top-level sequence (#798)
src/yaml/index.rs tolerate 1322 both unreachable: YamlIndex::build always wraps the parsed document(s) in a virtual root Sequence, at TY index 0 (#798)
src/yaml/index.rs tolerate 1341 both unreachable: YamlIndex::build always wraps the parsed document(s) in a virtual root Sequence, at TY index 0 (#798)
src/yaml/index.rs tolerate 1364 both unreachable: YamlIndex::build always wraps the parsed document(s) in a virtual root Sequence, at TY index 0 (#798)
src/yaml/index.rs tolerate 1373 both unreachable: every fixture field_key_head_foot_in_doc is called with in this test module is a top-level mapping (#798)
src/yaml/index.rs tolerate 1384 both unreachable: a mapping key is always emitted as YamlValue::String -- it is never type-inferred like a value (#222), so this if-let's pattern can never fail to match (#798)
src/yaml/index.rs tolerate 1386 both unreachable: every call to field_key_head_foot_in_doc in this test module passes a key that the fixture's document actually has (#798)
src/yaml/index.rs tolerate 1397 both unreachable: YamlIndex::build always wraps the parsed document(s) in a virtual root Sequence, at TY index 0 (#798)
src/yaml/index.rs tolerate 1401 both unreachable: every fixture nested_key_head_foot is called with in this test module is a top-level mapping (#798)
src/yaml/index.rs tolerate 1405 both unreachable: a mapping key is always emitted as YamlValue::String -- it is never type-inferred like a value (#222), so this let-else's pattern can never fail to match (#798)
src/yaml/index.rs tolerate 1411 both unreachable: every fixture nested_key_head_foot is called with has a nested mapping under outer (#798)
src/yaml/index.rs tolerate 1422 both unreachable: a mapping key is always emitted as YamlValue::String -- it is never type-inferred like a value (#222), so this if-let's pattern can never fail to match (#798)
src/yaml/index.rs tolerate 1425 both unreachable: every call to nested_key_head_foot in this test module passes an outer.inner pair that the fixture actually has (#798)
src/yaml/light.rs tolerate 3364 both unreachable: an alias target is never None for a built index (#1374)
src/yaml/light.rs tolerate 15398 both unreachable in a passing suite by design -- this is a panic-message format argument for the #2072 pin itself, only evaluated if the assert's own condition is false (#2072)
src/yaml/light.rs tolerate 15409 both unreachable in a passing suite by design -- this is a panic-message format argument for the #2072 pin itself, only evaluated if the assert's own condition is false (#2072)
src/yaml/light.rs tolerate 15566 both unreachable in a passing suite by design -- this is a panic-message format argument for the #2072 pin itself, only evaluated if the assert's own condition is false (#2072)
src/yaml/parser.rs tolerate 1583 both unreachable: every block-sequence open registers a frame at its own depth before any item of it can be parsed (#1079)
src/yaml/parser.rs tolerate 1613 both unreachable: this function's sole caller (record_standalone_comment) only invokes it from inside a match on pending_head_lines.last(), so pending_head_lines is already known non-empty here (#798)
src/yaml/parser.rs tolerate 7999 both unreachable: every byte here already passed the [0-9.eE+-] charset check above, a strict subset of ASCII, so str::from_utf8 can never fail (#2778)

Patch coverage

Patch: 98.54% (338/343 new lines covered)

File Patch Uncovered new lines
src/bin/succinctly/jq_runner.rs 98.25% (280/285) 1395, 1404, 1435, 2857, 8510
src/jq/resolve.rs 100% (58/58)
Uncovered new lines (5)
  • src/bin/succinctly/jq_runner.rs:1395
  • src/bin/succinctly/jq_runner.rs:1404
  • src/bin/succinctly/jq_runner.rs:1435
  • src/bin/succinctly/jq_runner.rs:2857
  • src/bin/succinctly/jq_runner.rs:8510

Indirect coverage changes

🔴 1 lines lost coverage, 🟢 0 lines gained coverage on unchanged code.

Indirect changes
  • src/bin/succinctly/jq_runner.rs:2691 🔴 covered → uncovered

📦 Full per-file coverage summary · run summary

@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown

Coverage

Total: 94.05% ⚪ 0.01 pp vs main

Comparing d45fec1..0bcfcb4 (merge-base → PR head)

File Before After Δ
src/bin/succinctly/jq_runner.rs 94.72% 94.8% 🟢 0.07 pp
🔇 0 ignored region(s), 174 tolerated region(s)

ignore removes the lines from both reports; tolerate keeps them in the reported percentage but scores them against the baseline, so a cross-run flip cannot move a delta. Regions are read from each revision's own source.

File Kind Lines Rev Reason
src/bin/succinctly/jq_runner.rs tolerate 1750-1762 both unreachable: try_parse_meta_op only fires under ParserMode::Yq (src/jq/parser.rs), and rewrite_namespaced_calls is only reached via ModuleProcessor::process_program, which jq_runner's own jq-mode run is the sole caller of -- so a MetaAssign node can never reach this function (#798)
src/bin/succinctly/jq_runner.rs tolerate 2375 both unreachable by construction: every error this wrapper receives today is a decode or nesting-depth failure the evaluator raised itself, never error(v); kept so a future one is rendered rather than dropped (#2999)
src/bin/succinctly/jq_runner.rs tolerate 2376 both see the arm above (#2999)
src/bin/succinctly/jq_runner.rs tolerate 2939-2942 both unreachable in a single-process run by construction: run_id_for (the sole source of an origin id) always inserts a run_origins entry for the id it hands back -- from a real load's canonical path, or its own literal-path fallback on a resolve failure -- and a def body only ever gets stamped with an origin after its module loaded successfully, so at always names a file that existed and was readable moments earlier. Reaching this arm needs that same file to vanish (or become unreadable) in the narrow window between that load and this re-read, entirely outside this process's control (#2964)
src/bin/succinctly/jq_runner.rs tolerate 3199 both unreachable: widening the shadow-candidate set never rejects a program the first parse accepted -- a newly covered name only wraps an already-successful dedicated parse, and a failing one would have propagated its error in the first parse too, so the retry budget is charged at the identical sites in both (#2395)
src/bin/succinctly/jq_runner.rs tolerate 9538 both unreachable in a passing suite by design -- the fixed b\
src/bin/succinctly/main.rs tolerate 1430 both the CLI suites drive yq through the syq multi-call arm above; this arm is the same call reached only when spelled succinctly yq (#2999)
src/bin/succinctly/yq_runner.rs tolerate 1652 both unreachable: bytes already parsed successfully by every caller (#1350)
src/bin/succinctly/yq_runner.rs tolerate 3318 both unreachable: path is always the raw output of the path(TARGET) builtin evaluated a few lines up in resolve_one_meta_assign -- path/1 is a jq/yq language invariant that always answers an array of path components (see Expr::Builtin(Builtin::PathNoArg) => Ok(Some(OwnedValue::Array(..))) in eval_generic.rs), never any other shape (#798)
src/bin/succinctly/yq_runner.rs tolerate 3420 both unreachable: resolve_meta_assign_writes runs expr through this before any evaluation begins (see its own doc comment), and Expr::Shared is never constructed by the parser -- only at eval time, by function-call argument substitution (substitute_func_param in eval.rs) -- so a pre-evaluation AST can never contain one here (#798)
src/bin/succinctly/yq_runner.rs tolerate 4052 both unreachable: every arm of the match result { .. } above that assigns docs (L3492-3622) constructs Ok(..) -- none ever produces Err, so this if let's implicit else can't be taken; symmetric to L1625's ? (#798)
src/jq/document.rs tolerate 1125-1132 both unreachable: both implementors (StandardJson, YamlValue) override this to decode once; the default exists as the contract a future implementor inherits, and is deliberately the two-call sequence it replaces (#965)
src/jq/eval.rs tolerate 1142 both unreachable: def is always a collect_alias_groups anchor path, which step_to_expr never fails on (#1351)
src/jq/eval.rs tolerate 1163 both unreachable: redirect_paths with Redirect::SINGLE always contributes exactly one output per input, so a 1-element paths always pops Some (#1351)
src/jq/eval.rs tolerate 1172 both unreachable: a concrete setpath/delpaths path's components are always Field/Index -- step_to_expr never produces another shape (#1351)
src/jq/eval.rs tolerate 1176 both unreachable: the map above never yields None, since it only ever matches Field/Index (#1351)
src/jq/eval.rs tolerate 4388 both unreachable: is_escape() is exactly `Error
src/jq/eval.rs tolerate 4389 both unreachable: see the if let above -- push_owned_values never answers None for an is_escape() result (#2180)
src/jq/eval.rs tolerate 5555 both unreachable today: to_owned's only failures are is_decode_failure()-tagged, and suppresses() answers false for those whatever optional is -- the same defensive-but-dead arm eval_generic's own Builtin::Path materialization documents under #2280 (#2908)
src/jq/eval.rs tolerate 6301 both unreachable: every Demand::Stop the sink answers is preceded by outcome = Some(..), handled just above (#2872)
src/jq/eval.rs tolerate 7446 both unreachable: optional is never true here. eval_each is entered with a forced true at exactly one site (Expr::Optional over an IndexExpr/SliceExpr), and both of those evaluate their target (eval_index_expr) and their key (eval_each(key, .., false)) with a hardcoded false, so only the final index/slice step ever sees it -- nothing carries it down to an Expr::Object (#2180)
src/jq/eval.rs tolerate 10732 both unreachable: every entry point supplies path context, so path never evaluates without one; pre-existing zero-hit line; #2999 changed only how its array payload is constructed
src/jq/eval.rs tolerate 10862 both unreachable: only ever constructed by builtin_sort_keys's own eval_update_no_vivify call, whose enclosing eval_update_impl already runs to_owned on the whole document up front (#2855) -- a decode failure anywhere raises there, before this filter ever sees a value to re-decode; confirmed live, sort_keys(.a)/sort_keys(..) on a document with a decode-failure subtree both raise from the outer to_owned
src/jq/eval.rs tolerate 21925 both unreachable: escape_with_prefix! sets terminal before Demand::Stop; already returned above (#2138)
src/jq/eval.rs tolerate 22294 both unreachable: escape! sets terminal before Demand::Stop; already returned above (#2546)
src/jq/eval.rs tolerate 30257 both reachable only on a genuine allocation failure: Vec::try_reserve(1) on a vector that has spare capacity, or can grow, cannot fail -- this is the whole purpose of the branch, converting an OOM into a catchable error rather than an abort (ADR-0018's 'would take the host process down' exception). The pre-#2267 form of the same guard, out.try_reserve(branches.len()) in resolve_index_expr/resolve_slice_expr, was 0-hit for the identical reason (#2267)
src/jq/eval.rs tolerate 30258 both see the line above -- the Demand::Stop half of the same allocation-failure-only branch (#2267)
src/jq/eval.rs tolerate 30264 both reachable only when the try_reserve above failed, i.e. only on a genuine allocation failure (#2267)
src/jq/eval.rs tolerate 32544 both unreachable: is_primitive admits only Identity/Field/Index/Slice, and of those only a Slice's computed bounds can halt -- all four have their own arm in resolve_node_sink/resolve_node_eager, so none reaches this function. Pre-existing; #2694 only wrapped the return in Some (#2694)
src/jq/eval.rs tolerate 32578 both unreachable, as this arm's own comment above says: indexing or slicing a value yields zero or one result, so is_primitive never produces more than one -- kept as a named error rather than a panic. Pre-existing; #2694 only wrapped the enclosing return in Some (#2694)
src/jq/eval.rs tolerate 34999 both unreachable: every Demand::Stop the sink answers is preceded by outcome = Some(..), handled just above (#2872)
src/jq/eval.rs tolerate 36031 both unreachable: every Demand::Stop the sink answers is preceded by outcome = Some(..), handled just above (#2872)
src/jq/eval.rs tolerate 36055 both unreachable: every arm of the loop's last iteration returns -- a walk refusal, an exhausted walk, and a step outcome that never retries on the last alternative (#2979, #2872)
src/jq/eval.rs tolerate 36602 both unreachable: every Demand::Stop the sink answers is preceded by outcome = Some(..), handled just above (#2872)
src/jq/eval.rs tolerate 36622 both unreachable: every path through the loop's last iteration returns -- a walk refusal, an exhausted walk, and a step outcome that never retries on the last alternative (#2979, #2872)
src/jq/eval.rs tolerate 39313 both unreachable in a passing suite by design -- a panic-message format argument for the #682 single-valued-tail pin, evaluated only if that assert's own condition is false (#2190)
src/jq/eval.rs tolerate 39390 both unreachable: the only caller reaches this after classify_static_component answered Field for this same value, which it does only for an object (#2190)
src/jq/eval.rs tolerate 39434 both unreachable: both callers establish the container first -- navigate_static_component_ref via classify_static_component's Index arm, and walk_path's Expr::Iterate arm by matching on the container itself (#2190)
src/jq/eval.rs tolerate 41828 both unreachable: every Demand::Stop the sink answers is preceded by outcome = Some(..), handled just above (#2872)
src/jq/eval.rs tolerate 42005 both pre-existing zero-hit line; #2999 changed only how its array payload is constructed
src/jq/eval.rs tolerate 43290 both unreachable: stop_with_escape's only write is slot.set(Some(control)) with the control it was handed, which is always the Control::Error built one line above (#2180)
src/jq/eval.rs tolerate 43637 both unreachable: on_update records a step_outcome before every Demand::Stop it answers, and the fallback match runs only when it recorded none (#2872)
src/jq/eval.rs tolerate 43660 both unreachable: every Demand::Stop the sink answers is preceded by outcome = Some(..), handled just above (#2872)
src/jq/eval.rs tolerate 44183 both unreachable by construction: the per-fork match only ever hands stop_with_downstream a non-Exhausted flow, so terminal can never hold Exhausted. foreach_forks' identical arm is 0-hit for the same reason and is only unflagged because it predates this diff (#2899)
src/jq/eval.rs tolerate 46939 both pre-existing zero-hit line; #2999 changed only how its array payload is constructed
src/jq/eval.rs tolerate 48912 both pre-existing zero-hit line; #2999 changed only which null constant it names
src/jq/eval.rs tolerate 51546 both defensive, unreachable via ordinary JSON parsing (#3068)
src/jq/eval.rs tolerate 51549 both defensive, unreachable via ordinary JSON parsing (#3068)
src/jq/eval.rs tolerate 51590 both optional is never true through either caller of this function -- confirmed live (eprintln! probe): `[1,2,3]
src/jq/eval.rs tolerate 51599 both every array literal written in filter source, and every array-element JSON parses, decodes to NumberLiteral (#1035), not a bare Int -- Int is for internally-synthesized values spliced post-parse; probed a computed element ([1970,(0+0),..]) and it still decoded as NumberLiteral here, so this arm has no known real producer (#3068)
src/jq/eval.rs tolerate 51614 both optional is never true through this call path -- see the array-length check above (#3068)
src/jq/eval.rs tolerate 51629 both optional is never true through either caller of this function, same as the array-length check above (#3068)
src/jq/eval.rs tolerate 53394 both unreachable from combinations, whose own empty-input return runs first; pre-existing zero-hit line; #2999 changed only how its array payload is constructed
src/jq/eval.rs tolerate 54489 both confirmed live (eprintln! probe): optional is always false in builtin_abs regardless of whether the filter writes abs?, since that suppression happens entirely outside builtin dispatch here -- the same 'optional is never true here' shape eval.rs already documents elsewhere (#2180) (#3041)
src/jq/eval.rs tolerate 56467 both unreachable: the sink is a plain collector that always answers Demand::Continue (#2872)
src/jq/eval.rs tolerate 56577 both unreachable: optional: false makes index_one_owned answer Ok(Some)/Err only (#2872)
src/jq/eval.rs tolerate 56723 both unreachable: pattern_has_computed_key is false for Pattern::Var, so the loop walker above always takes it (#2872)
src/jq/eval.rs tolerate 56770 both unreachable: every caller gates on pattern_has_computed_key being false (#2872)
src/jq/eval.rs tolerate 57828 both unreachable in practice: a def with more than 32 parameters; the fallback exists so ScopeMask's one-bit-per-parameter u64 is a performance ceiling rather than a correctness limit (#2633)
src/jq/eval.rs tolerate 57854 both unreachable: bind_def_call only calls this for a non-empty params, and install_def_calls only builds a DefCall whose args.len() equals params.len(), so the zip is never empty here (#2560)
src/jq/eval.rs tolerate 57874 both unreachable in practice: needs more than 32 duplicated-name parameters; see the non-duplicate path's own note (#2633)
src/jq/eval.rs tolerate 57895 both unreachable: name was just read from params, so the zip over (params, args) has a matching pair unless args is shorter than params, which install_def_calls' own arity guard rules out (#2560)
src/jq/eval.rs tolerate 57915 both unreachable: params is non-empty here (bind_def_call's own guard) and its first entry is never skipped, so at least one substitution always ran (#2560)
src/jq/eval.rs tolerate 57976 both unreachable: same arity invariant as bind_def_call_params' own copy of this loop -- name came from params, so the zip has a matching pair unless args is shorter, which install_def_calls rules out (#2560)
src/jq/eval.rs tolerate 59294 both substitute_var_impl's FuncDef arm always returns FuncDef (#2283)
src/jq/eval.rs tolerate 59316 both substitute_var_impl's FuncDef arm always returns FuncDef (#2283)
src/jq/eval.rs tolerate 59334 both substitute_var_impl's FuncDef arm always returns FuncDef (#2283)
src/jq/eval.rs tolerate 59404 both substitute_var_impl's FuncDef arm always returns FuncDef (#2283)
src/jq/eval.rs tolerate 59418 both substitute_func_param_impl's FuncDef arm always returns FuncDef (#2555)
src/jq/eval.rs tolerate 66579 both unreachable in a passing suite by design -- this is the failure message for the assertion the test exists to make (#2190)
src/jq/eval.rs tolerate 78640 both unreachable in a passing suite by design -- this test's own diagnostic (#3071)
src/jq/eval.rs tolerate 79406-79411 both the closure is asserted never called below (on_update_calls stays 0) -- Err(_) with optional=true short-circuits fold_step_each before this sink runs (#3122)
src/jq/eval.rs tolerate 84083 both unreachable in a passing suite by design -- this test's own diagnostic (#3071)
src/jq/eval.rs tolerate 89925 both unreachable in a passing suite by design -- this test's own diagnostic (#3071)
src/jq/eval.rs tolerate 96481 both unreachable in a passing suite by design -- every filter this helper is called with parses to an AsPattern (#2649)
src/jq/eval.rs tolerate 96525 both unreachable in a passing suite by design -- a literal-key pattern's walk either completes or refuses with an error (#2872)
src/jq/eval.rs tolerate 96534 both unreachable in a passing suite by design -- every call site passes the origin of a binding this same test already proved carries a marker (#2649)
src/jq/eval.rs tolerate 96862 both unreachable in a passing suite by design -- the assertion above is the test (#2872)
src/jq/eval.rs tolerate 97509 both unreachable in a passing suite by design -- every row here is a shape jq accepts, confirmed live (#2649)
src/jq/eval.rs tolerate 97577 both unreachable in a passing suite by design -- every row here is a shape jq refuses, confirmed live (#2649)
src/jq/eval.rs tolerate 97962 both unreachable in a passing suite by design -- this is the panic message for the #2072 pin itself, only formatted if the let-else pattern fails to match (#2072)
src/jq/eval.rs tolerate 97973 both unreachable in a passing suite by design -- this is the panic message for the #2072 pin itself, only formatted if the let-else pattern fails to match (#2072)
src/jq/eval.rs tolerate 97994 both unreachable in a passing suite by design -- this is the panic message for the #2072 pin itself, only formatted if the match doesn't hit the expected arm above (#2072)
src/jq/eval.rs tolerate 98010 both unreachable in a passing suite by design -- this is the panic message for the #2072 pin itself, only formatted if the match doesn't hit the expected arm above (#2072)
src/jq/eval.rs tolerate 98095 both unreachable in a passing suite by design -- every expression this closure receives is built by marker above (#3037)
src/jq/eval.rs tolerate 98173 both unreachable in a passing suite by design -- rewrite_markers rebuilds the same node kind it was given (#3037)
src/jq/eval.rs tolerate 98200 both unreachable in a passing suite by design -- every expression this closure receives is built by marker above (#3122)
src/jq/eval.rs tolerate 98218 both unreachable in a passing suite by design -- rewrite_markers rebuilds the same node kind it was given (#3122)
src/jq/eval.rs tolerate 98286 both unreachable in a passing suite by design -- fires only if demote_for_reentry's own let-else assertion condition is false (#3122)
src/jq/eval.rs tolerate 98290 both unreachable in a passing suite by design -- fires only if demote_for_reentry's own let-else assertion condition is false (#3122)
src/jq/eval.rs tolerate 98330 both unreachable in a passing suite by design -- fires only if reroot's own let-else assertion condition is false (#3122)
src/jq/eval.rs tolerate 98334 both unreachable in a passing suite by design -- fires only if reroot's own let-else assertion condition is false (#3122)
src/jq/eval.rs tolerate 99812 both unreachable in a passing suite by design -- this is the panic message for the assertion above, only formatted if the match doesn't hit the Error arm (#3119)
src/jq/eval.rs tolerate 102204 both unreachable in a passing suite by design -- this is the failure message for the assertion this test exists to make (#2937)
src/jq/eval.rs tolerate 102358 both every pinned filter below yields an owned value; kept so a cursor answer still renders rather than panics (#2999)
src/jq/eval.rs tolerate 102359 both unreachable in a passing suite by design -- the failure message for the assertions this helper serves (#2999)
src/jq/eval_generic.rs tolerate 3729 both unreachable: the sole remaining caller (retain_truthy_generic's Many arm) runs to_owned on an item before keeping it, so re-converting a kept item here cannot fail; the ManyCursor caller that made this reachable went with the truthiness walk (#2692, re-establishing #2661's premise)
src/jq/eval_generic.rs tolerate 7092 both unreachable in a passing suite by design -- this is the panic message for the #2368 pin itself, only formatted if the assert's own condition is false (#2368)
src/jq/eval_generic.rs tolerate 8950 both the CLI evaluates every program that uses input/inputs on the eager route (jq_runner's can_use_lazy_path excludes them), so this guard never fires today -- #2968's identical guards on the arms above are equally unfired; kept for the day the lazy path admits such a program (#1309)
src/jq/eval_generic.rs tolerate 8967 both the CLI evaluates every program that uses input/inputs on the eager route (jq_runner's can_use_lazy_path excludes them), so this guard never fires today -- #2968's identical guards on the arms above are equally unfired; kept for the day the lazy path admits such a program (#1309)
src/jq/eval_generic.rs tolerate 10156 both unreachable: every Demand::Stop the sink answers is preceded by outcome = Some(..), handled just above (#2872)
src/jq/eval_generic.rs tolerate 11650 both unreachable: every producer that reaches this empty-exprs tail (each_lazy_keys_iterate_sink's sorted/!sorted arms, each_lazy_index_range_iterate_sink, each_lazy_seq_iterate_sink) yields OneCursorValue/OneCursor/Owned, never a cursorless GenericItem::One -- so cursor is always Some here; kept for exhaustiveness/symmetry with the Some arm (#2103)
src/jq/eval_generic.rs tolerate 14768 both unreachable: escape_generic!/ensure_owned! set terminal before Demand::Stop; already returned above (#2138)
src/jq/eval_generic.rs tolerate 15122 both unreachable: escape! sets terminal before Demand::Stop; already returned above (#2546)
src/jq/eval_generic.rs tolerate 15714 both unreachable by construction: key_elements_generic builds every key of a run from the same Option<&Expr> (#2999)
src/jq/eval_generic.rs tolerate 15724 both unreachable by construction: key_elements_generic builds every key of a run from the same Option<&Expr> (#2999)
src/jq/eval_generic.rs tolerate 16237 both pre-existing zero-hit line; #2999 changed only how its array payload is constructed
src/jq/eval_generic.rs tolerate 17919 both unreachable: len_checked and SliceBounds::resolve already bound every index in range to [0, len), so get_cursor cannot miss (#2168)
src/jq/eval_generic.rs tolerate 20702 both unreachable by construction: path_context_resolvable admits an any(cond) read only through admits.prefetch, so the rewriter always has one -- the AnyCond arm above carries the identical assertion (#3079)
src/jq/eval_generic.rs tolerate 20706 both unreachable by construction, see above
src/jq/eval_generic.rs tolerate 20707 both unreachable by construction, see above
src/jq/eval_generic.rs tolerate 21511 both pre-existing zero-hit line; #2999 changed only how its array payload is constructed
src/jq/eval_generic.rs tolerate 21532 both pre-existing zero-hit line; #2999 changed only how its array payload is constructed
src/jq/eval_generic.rs tolerate 21875 both unreachable: malformed_object_member above already proved every key stringifies (the None half), and to_owned_cursor on an untagged key decoded_key_str decoded cannot fail (the Err half) (#2785)
src/jq/eval_generic.rs tolerate 21956 both unreachable: map(f) over an array emits exactly one array; kept so a future map shape produces no output rather than a panic (#2785)
src/jq/eval_generic.rs tolerate 22243 both unreachable by design -- eval_single's #2368 debug_assert forbids optional=true on Builtin::Reverse, so length never answers None here (#2730)
src/jq/eval_generic.rs tolerate 22847 both the CLI evaluates every program that uses input/inputs on the eager route (jq_runner's can_use_lazy_path excludes them), so this guard never fires today -- #2968's identical guards on the arms above are equally unfired; kept for the day the lazy path admits such a program (#1309)
src/jq/eval_generic.rs tolerate 22853 both see above: the input-queue deferral never fires from the CLI
src/jq/eval_generic.rs tolerate 24297 both unreachable by construction: every shape either guard admits now has an arm above (#2771), and expr_dispatch_catchall_guards_default_conservatively_2549 pins both guards' _ => false defaults directly
src/jq/eval_generic.rs tolerate 24845 both unreachable in a passing suite by design -- owned_identity_rule maps a bare Expr::Var to Bound too (for the static gate, which sees a body before its as substitution runs), but every runtime dispatch that reaches this rule (owned_identity_after_stage/owned_identity_placed_by, from owned_identity_leaving_cursor's Bound arm) only ever sees a stage after eval_owned_identity_as's unconditional substitute_bound_var_from call, which always turns $x into Expr::TrackedVar before recursing -- confirmed by running the full suite with this arm replaced by a hard panic!(), which never fired (#2072)
src/jq/eval_generic.rs tolerate 24863 both unreachable: this Slice rule's own two execution paths both exclude Expr::Slice before ever reaching owned_identity_placed_by -- eval_owned_identity_stages's catch-all only runs a stage owned_identity_nav_supported refused, and owned_identity_leaving_cursor (identity_from_first, eval_generic.rs:10724) only runs a stage path_context_is_navigational refused -- and both predicates admit Expr::Slice (owned_identity_nav_supported/path_context_is_navigational_at each list Expr::Slice { .. } => true), so a bare slice is always resolved by owned_identity_step's own arm first. Kept in owned_identity_rule's match for exhaustiveness/symmetry with the rule table's other entries, the same reason #2072's Bound arm above is kept unreachable-by-construction (#2966's review of #2834)
src/jq/eval_generic.rs tolerate 26362 both optional is never true through this pipe: ? is eval_owned_identity_try, which catches the escape instead, and after #693 only the IndexExpr/SliceExpr special case ever sets it -- kept as any_all_f's scalar_fallback mirror
src/jq/eval_generic.rs tolerate 26363 both optional is never true here, see above
src/jq/eval_generic.rs tolerate 26766 both unreachable in a passing suite by design -- the failure message for the assertion the tests below make (#2999)
src/jq/eval_generic.rs tolerate 26905 both unreachable in a passing suite by design -- the fixture's map(.+1) is always a LazySeq; this arm is the test's own diagnostic (#2666)
src/jq/eval_generic.rs tolerate 26933 both unreachable in a passing suite by design -- the fixture's only escape is Control::Error; this arm is the test's own diagnostic (#2666)
src/jq/parser.rs tolerate 226 both unreachable: parse_join_expr only calls join_expr with two to four arguments (#3046)
src/jq/parser.rs tolerate 248 both unreachable: join_expr builds only this shape, and only join_expr names JOIN_IDX_VAR, which no program can spell (#3046)
src/jq/parser.rs tolerate 250 both unreachable: join_expr builds only this shape, and only join_expr names JOIN_IDX_VAR, which no program can spell (#3046)
src/jq/parser.rs tolerate 252 both unreachable: join_expr builds only this shape, and only join_expr names JOIN_IDX_VAR, which no program can spell (#3046)
src/jq/parser.rs tolerate 259 both unreachable: join_expr builds only this shape, and only join_expr names JOIN_IDX_VAR, which no program can spell (#3046)
src/jq/parser.rs tolerate 263 both unreachable: join_expr builds only this shape, and only join_expr names JOIN_IDX_VAR, which no program can spell (#3046)
src/jq/parser.rs tolerate 279 both unreachable: join_expr builds only this shape, and only join_expr names JOIN_IDX_VAR, which no program can spell (#3046)
src/jq/parser.rs tolerate 8949 both unreachable in a passing suite by design -- this is the panic message for the #3044 pin itself, only formatted if the let-else pattern fails to match (#3044)
src/jq/resolve.rs tolerate 2300 both unreachable in practice today: this arm needs builtin_fallback==None (the name was never a shadow candidate) yet is_jq_builtin==true (a real jq builtin at this arity) -- every implemented builtin's own dedicated parse already lowers that shape to Expr::Builtin before resolve.rs ever runs, and #3042/#3046 closed the once-real 'unimplemented builtin' gap this existed for (see JQ_BUILTIN_ROSTER's own doc comment)
src/jq/resolve.rs tolerate 2302 both unreachable with the current roster: every JQ_BUILTIN_ROSTER entry of arity >= 1 already has a dedicated parser form (a matches_keyword special case or a Libm1/Libm2/Libm3::ALL entry -- confirmed by cross-referencing the full roster against both), so it is parsed straight to Expr::Builtin and never reaches here as a bare FuncCall. This arm exists for a roster name with no dedicated parse yet and a nonzero arity -- there is none today, so the loop body is reached with an empty args on every pinned-suite run (355 hits on the arm's own condition, 0 in the loop) and would only start executing if such a name were added (#2964)
src/jq/share_stats.rs tolerate 97 both process-global env var; exercised by the CLI audit run, not by an in-process test (#2999)
src/jq/value.rs tolerate 358 both unreachable by construction: the slow path is entered only after significant_digit_count found 18+ digits over the same mantissa bytes this loop walks (#2936)
src/jq/value.rs tolerate 382 both unreachable by construction: 18+ significant digits were counted, so at least one nonzero digit was kept (#2936)
src/jq/value.rs tolerate 2771 both unreachable: format_float_with_fraction of a finite double is always RFC 8259 number text, which parse_i64_or_f64 reads (#2936)
src/jq/value.rs tolerate 4541 both unreachable in a passing suite by design -- every caller built the value as an array (#2999)
src/jq/value.rs tolerate 4784 both unreachable in a passing suite by design -- the value was built as this container a few lines above (#2999)
src/jq/value.rs tolerate 5603 both unreachable in a passing suite by design -- the failure message for a malformed oracle table (#2936)
src/json/light.rs tolerate 7068 both unreachable in a passing suite by design -- the failure message for the assertion this #2877 test exists to make
src/json/light.rs tolerate 7150 both unreachable in a passing suite by design -- the failure message for the assertion this #2877 test exists to make
src/json/light.rs tolerate 7238 both unreachable in a passing suite by design -- the failure message for the assertion this #2877 test exists to make
src/json/light.rs tolerate 7263 both unreachable in a passing suite by design -- this is a panic-message format argument for the #2072 pin itself, only evaluated if the assert's own condition is false (#2072)
src/json/light.rs tolerate 7274 both unreachable in a passing suite by design -- this is a panic-message format argument for the #2072 pin itself, only evaluated if the assert's own condition is false (#2072)
src/json/light.rs tolerate 7355 both unreachable in a passing suite by design -- this is a panic-message format argument for the #2072 pin itself, only evaluated if the assert's own condition is false (#2072)
src/yaml/index.rs tolerate 1268 both unreachable: YamlIndex::build always wraps the parsed document(s) in a virtual root Sequence, at TY index 0 (#798)
src/yaml/index.rs tolerate 1272 both unreachable: every fixture field_key_head_foot is called with in this test module is a top-level mapping (#798)
src/yaml/index.rs tolerate 1283 both unreachable: a mapping key is always emitted as YamlValue::String -- it is never type-inferred like a value (#222), so this if-let's pattern can never fail to match (#798)
src/yaml/index.rs tolerate 1285 both unreachable: every call to field_key_head_foot in this test module passes a key that the fixture's mapping actually has (#798)
src/yaml/index.rs tolerate 1295 both unreachable: YamlIndex::build always wraps the parsed document(s) in a virtual root Sequence, at TY index 0 (#798)
src/yaml/index.rs tolerate 1299 both unreachable: every fixture seq_item_head_foot is called with in this test module is a top-level sequence (#798)
src/yaml/index.rs tolerate 1322 both unreachable: YamlIndex::build always wraps the parsed document(s) in a virtual root Sequence, at TY index 0 (#798)
src/yaml/index.rs tolerate 1341 both unreachable: YamlIndex::build always wraps the parsed document(s) in a virtual root Sequence, at TY index 0 (#798)
src/yaml/index.rs tolerate 1364 both unreachable: YamlIndex::build always wraps the parsed document(s) in a virtual root Sequence, at TY index 0 (#798)
src/yaml/index.rs tolerate 1373 both unreachable: every fixture field_key_head_foot_in_doc is called with in this test module is a top-level mapping (#798)
src/yaml/index.rs tolerate 1384 both unreachable: a mapping key is always emitted as YamlValue::String -- it is never type-inferred like a value (#222), so this if-let's pattern can never fail to match (#798)
src/yaml/index.rs tolerate 1386 both unreachable: every call to field_key_head_foot_in_doc in this test module passes a key that the fixture's document actually has (#798)
src/yaml/index.rs tolerate 1397 both unreachable: YamlIndex::build always wraps the parsed document(s) in a virtual root Sequence, at TY index 0 (#798)
src/yaml/index.rs tolerate 1401 both unreachable: every fixture nested_key_head_foot is called with in this test module is a top-level mapping (#798)
src/yaml/index.rs tolerate 1405 both unreachable: a mapping key is always emitted as YamlValue::String -- it is never type-inferred like a value (#222), so this let-else's pattern can never fail to match (#798)
src/yaml/index.rs tolerate 1411 both unreachable: every fixture nested_key_head_foot is called with has a nested mapping under outer (#798)
src/yaml/index.rs tolerate 1422 both unreachable: a mapping key is always emitted as YamlValue::String -- it is never type-inferred like a value (#222), so this if-let's pattern can never fail to match (#798)
src/yaml/index.rs tolerate 1425 both unreachable: every call to nested_key_head_foot in this test module passes an outer.inner pair that the fixture actually has (#798)
src/yaml/light.rs tolerate 3364 both unreachable: an alias target is never None for a built index (#1374)
src/yaml/light.rs tolerate 15398 both unreachable in a passing suite by design -- this is a panic-message format argument for the #2072 pin itself, only evaluated if the assert's own condition is false (#2072)
src/yaml/light.rs tolerate 15409 both unreachable in a passing suite by design -- this is a panic-message format argument for the #2072 pin itself, only evaluated if the assert's own condition is false (#2072)
src/yaml/light.rs tolerate 15566 both unreachable in a passing suite by design -- this is a panic-message format argument for the #2072 pin itself, only evaluated if the assert's own condition is false (#2072)
src/yaml/parser.rs tolerate 1583 both unreachable: every block-sequence open registers a frame at its own depth before any item of it can be parsed (#1079)
src/yaml/parser.rs tolerate 1613 both unreachable: this function's sole caller (record_standalone_comment) only invokes it from inside a match on pending_head_lines.last(), so pending_head_lines is already known non-empty here (#798)
src/yaml/parser.rs tolerate 7999 both unreachable: every byte here already passed the [0-9.eE+-] charset check above, a strict subset of ASCII, so str::from_utf8 can never fail (#2778)

Patch coverage

Patch: 98.54% (338/343 new lines covered)

File Patch Uncovered new lines
src/bin/succinctly/jq_runner.rs 98.25% (280/285) 1395, 1404, 1435, 2857, 8510
src/jq/resolve.rs 100% (58/58)
Uncovered new lines (5)
  • src/bin/succinctly/jq_runner.rs:1395
  • src/bin/succinctly/jq_runner.rs:1404
  • src/bin/succinctly/jq_runner.rs:1435
  • src/bin/succinctly/jq_runner.rs:2857
  • src/bin/succinctly/jq_runner.rs:8510

Indirect coverage changes

🔴 1 lines lost coverage, 🟢 0 lines gained coverage on unchanged code.

Indirect changes
  • src/bin/succinctly/jq_runner.rs:2691 🔴 covered → uncovered

📦 Full per-file coverage summary · run summary

@newhoggy

Copy link
Copy Markdown
Contributor Author

Rebased onto 5f65aef02 and added one commit from review: dep_stubs_for compared an import ... as ns dependency's bare name against the consuming def's own (name, arity) and parameters, so import "dep" as ns; def g: ns::g; (and a parameter or a recursive def named like the dependency) dropped the stub and failed with ns::g/0 is not defined where jq 1.7.1 answers 99. Pinned as R34-R36 in the #2962 matrix, all re-captured live. Same commit: hoist_order reuses the run ids process_program already interned, the per-module closure indexes siblings by name, and both unresolved-call reporters go through ModuleRun::display_name. All ci.yml gates re-run green locally on the rebased tree.

…tubs (#2955, #3058)

Binding a module's dependencies by copying their bodies into every def that
reached them compounded down a chain: each level's bodies already carried the
level below, so a chain whose defs each call F defs of the level below held F^L
copies of the bottom one. The issue's 14-level fan-out-2 chain peaked at 681 MB
against jq's 2 MB.

A module some other module depends on is now emitted once, outermost in
`process_program`, as an ordinary run whose begin marker carries a hidden alias
(`ModuleRun::link_alias`) and whose defs are named `ModuleRun::link_name`
(`<NUL>link:<id>::<name>`). Inside the run a bare sibling call misses those
names, floors, and is retried under the alias by the code #2989 added for
imported modules, so the evaluator binds it unchanged. Each consuming def's body
is wrapped in one forwarding stub per dependency (name, arity) it calls directly
(`dep_stubs_for`), with the two exclusions the copying loader had; the
transitive closure and #2962's rename (`rename_dep_calls`, `renamed_dep`) are
gone, since a dependency's free names resolve in its own run. The one resolver
change: `scan_scope` steps over an open begin marker when the name looked up is
a link name, so a stub inside any run reaches the run outside.

Link runs are wrapped in dependency post-order with each module's directives
visited last-declared first (`hoist_order`), which is the order jq 1.7.1
reports cross-module compile errors in; and a dependency body now exists once,
so an error in it is reported once (#3058). Only defs reached by name from the
main filter -- through the top-level runs, retrying an imported module's bare
sibling calls under its alias, then the linked modules dependents-first -- are
linked, which keeps a wide chain the filter uses one def of at one def per
level.

Measured (release, this machine, output identical to jq): 8x6x3 142 MB -> 34 MB,
12x4x2 162 MB -> 36 MB, 14x4x2 681 MB -> 112 MB and 0.38 s -> 0.03 s; the
recursion ceiling through a linked dependency is unchanged at 19494. The
processed program is now linear in chain depth (169 / 241 / 313 nodes at 6 / 8
/ 10 levels, against 1253 / 5093 / 20453).
…d a chain size guard (#2955)

- `link_size_guard_2955` (jq_runner.rs): counts the processed program's nodes
  for the issue's fan-out-2 chain at 6 / 8 / 10 levels and asserts a constant
  per-level delta; it failed against the copying loader (1253 / 5093 / 20453)
  and cannot pass by accident. A sibling test pins that `unqualified_def_names`
  never returns a link spelling.
- `test_fan_out_module_chain_stays_linear_2955`: the issue's 8x6x3 and 12x4x2
  shapes end to end, with jq's answers.
- `test_linked_dependency_keeps_jq_scoping_2955`: ten rows captured from jq
  1.7.1 -- a nested import's bare sibling call, an imported module's sibling
  reached bare that has its own dependency (the referenced closure must retry
  the bare call under the alias; found by review), one dependency under two
  includers, `$param` and closure params and `path()` through a stub, a
  parameter named like its def, two same-name defs, siblings plus a deeper
  dependency, recursion inside and through a dependency.
- `test_dependency_errors_report_in_jq_order_2955`: a dependency's errors
  before its includer's, last-declared first, deepest first.
- `test_dependency_error_is_reported_once_3058` replaces the pin of the
  once-per-copy count with jq's single report, for a variable, a call, and
  the include-plus-dependency shape.
- `test_depth_guard_names_a_linked_def_as_written_2955`.
- The `_2962` matrix's doc comment no longer describes a rename; its 30 rows
  are unchanged.
#3058 residual

ADR-0023 gains an "Amendment (#2955, implemented)" section -- one link run per
dependency module with a hidden alias, forwarding stubs, the floor-crossing
lookup, jq's error order from placement, the referenced closure, and the
soundness argument re-derived from decision 5 -- and marks the #2962 rename
superseded and the "symbolic binding" alternative taken. `Expr::Shared`
splicing is recorded as rejected.

limitations.md closes "Deeply chained modules compound in memory" with the
before/after table, closes #3058 with its include-plus-dependency residual,
rewrites the #2962 section's mechanism, fixes its row count to 30, and records
what remains: the evaluator's own per-call bound-body retention (the same 56
defs in one file cost 58 MB, `fib(20)` 234 MB, `fib(21)` overflows the stack)
and the wide-chain startup cost of a long top-level chain (a 20x50 chain with
every top-level def used: 31 ms -> 137 ms on an M4 Pro; one def used: 21 ms ->
9 ms). jq-language.md's module paragraph describes the new binding.
…dress #2955 review findings

- `dep_stubs_for` compared a dependency's *bare* name against the consuming
  def's own (name, arity) and parameter names even for an `import ... as ns`
  group, whose stub is spelt `ns::name` and cannot clash with either. The stub
  was dropped and a working call became a spurious compile error. Rows R34-R36
  in the #2962 matrix pin the three shapes (own name, a parameter, the def's
  own recursive call), captured from jq 1.7.1.
- `hoist_order` takes the top-level directives' already-interned run ids from
  `process_program` instead of re-resolving every path through the
  filesystem.
- The per-module referenced closure indexes siblings by name once instead of
  scanning the module per call.
- `report_unresolved_call` and the bare-form fallback map a def name through
  `ModuleRun::display_name`, so a link spelling can never reach stderr.
- Doc comments on `link_keys` (why it is keyed by literal path) and on the two
  twin fixed points.
…ads by canonical path

hoist_order relied on deps_of's insertion order, but module_dep_defs records a
module's includes before its imports regardless of interleaving, reversing
jq's compile-error order when a dependency module mixes both directives.
Sorting deps_of entries by their own decl_index, the same way top_ids already
is, restores it (#2955 review).

Also key loaded_modules by the canonical file (ModuleLoader::run_key) instead
of the literal spelling a caller wrote, so two spellings of one module share
one load instead of parsing and binding it twice -- this also makes link_keys
redundant with run_origins, so it's retired. Finally, factor the two
reachability fixed-point loops in link_dependency_runs onto one shared
grow_to_fixed_point helper, so a future fix to the iteration itself doesn't
need to be applied twice.
@newhoggy
newhoggy force-pushed the issue-2955-triage-module-memory-compound branch from 56b3803 to 0bcfcb4 Compare September 18, 2026 12:03
@newhoggy
newhoggy added this pull request to the merge queue Sep 18, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to invalid changes in the merge commit Sep 18, 2026
@newhoggy
newhoggy added this pull request to the merge queue Sep 18, 2026
Merged via the queue into main with commit c0f0bbe Sep 18, 2026
64 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant