Skip to content

fix(jq): keep a computed float bare across the reindex bridge - #2933

Merged
newhoggy merged 3 commits into
mainfrom
issue-2902-computed-float-container-tostring
Sep 14, 2026
Merged

fix(jq): keep a computed float bare across the reindex bridge#2933
newhoggy merged 3 commits into
mainfrom
issue-2902-computed-float-container-tostring

Conversation

@newhoggy

@newhoggy newhoggy commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Closes #2902. Closes #1134. Closes #1144.

Summary

Test plan

  • 64-row probe matrix in both modes against /usr/bin/jq 1.7.1 and yq v4.53.3 — every row matches (the only remaining diff, [0.5+0.5] | tostring flow-vs-block, is main's pre-existing container-tostring style and unrelated)
  • !!float 2 document floats unchanged through a container (and [.a] | .[0] == 2 is now true, as in yq)
  • Unit tests: token unparseable as f64/i64, bit-exact round trip incl. -0.0/subnormal/f64::MAX, decoder rejects 1e0/sentinels/malformed; from_number_bytes and JsonNumber decode; serialize-and-reparse keeps Float/NumberLiteral/Int apart in both modes
  • New computed_float_through_container_2902 (yq) and test_computed_float_through_container_keeps_computed_spelling_2902 (jq) CLI modules, every expectation captured from the pinned binaries; yq: computed-float formatting diverges between @uri/@html/@urid/@base64d (numeric_display_string) and tostring/join (to_json_yq) #1124 known-gap test flipped to "1"; yq-mode skip removed from eval_owned_pure_agrees_with_the_reindex_bridge
  • cargo test --features cli,simd,regex,serde --workspace, --no-default-features, cargo fmt --check, cargo clippy --all-targets --all-features -D warnings, RUSTDOCFLAGS=-D warnings cargo doc --no-deps --all-features all green locally

Review round (second commit)

/code-review found three routes that had been relying on the bridge's re-spelling, all fixed and pinned:

  • @yaml/@props spelled a bare Float with plain Display under a comment claiming the bridge never yields one — [1e17*1] | .[0] | @yaml gave 100000000000000000 (yq 1e+17); both arms now take yq's computed-float rule.
  • A path-context pipe over an owned container (reduce 1 as $x ([1e10*2]; .[0] | [tostring, key])) hands the bridge's document to the generic materializer, whose literal-first chain fell through to from_document_float and re-baked "20000000000.0". New DocumentValue::bridge_computed_float hook, consulted before number_literal() in to_owned_at_depth/to_owned_cursor_at_depth/tonumber.
  • yq's --input-format json DOM route (--slurp/--eval-all) printed a: 100 only because the jq-mode bridge collapsed Float(100.0) to an integer literal. Real yq's JSON decoder types a whole float as !!int (.c | type!!int for 1.0, 9223372036854775808.09223372036854775807, 1e19 stays !!float), so yq: JSON-sourced literal spelling leaks decimal-point preservation, unlike real yq #978's canonicalizer (from_number_literal_plain) now does the same — which also fixes the pre-existing --eval-all divergence on that route. --argjson (a succinctly extension, no oracle) moves with it: 1.01, 1.e5100000; recorded in the yq limitations doc.

Also from review: reindex_bridge_is_identity admits every bare Float; yq_float_fidelity_fixup (now a dead round trip per float-bearing [..]/,) removed; tests build tokens with the encoder rather than hard-coding spellings; the pre-existing container tostring/tojson rendering and tojson trailing-newline divergences observed during verification are now recorded in docs/compliance/yq/limitations.md.

@github-actions

github-actions Bot commented Sep 13, 2026

Copy link
Copy Markdown

Coverage

Total: 93.49% ⚪ 0.01 pp vs main (not attributable to this diff)

Comparing 3c25b36..b2d2be6 (merge-base → PR head)

No per-file coverage changes vs main.

🔇 0 ignored region(s), 91 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 572-584 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 1539 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 7411 both unreachable in a passing suite by design -- the fixed b\
src/bin/succinctly/yq_runner.rs tolerate 1638 both unreachable: bytes already parsed successfully by every caller (#1350)
src/bin/succinctly/yq_runner.rs tolerate 3299 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 3399 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 4028 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 1069-1076 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 1128 both unreachable: def is always a collect_alias_groups anchor path, which step_to_expr never fails on (#1351)
src/jq/eval.rs tolerate 1149 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 1158 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 1162 both unreachable: the map above never yields None, since it only ever matches Field/Index (#1351)
src/jq/eval.rs tolerate 4223 both unreachable: is_escape() is exactly `Error
src/jq/eval.rs tolerate 4224 both unreachable: see the if let above -- push_owned_values never answers None for an is_escape() result (#2180)
src/jq/eval.rs tolerate 5372 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 7253 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 10512 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 21040 both unreachable: escape_with_prefix! sets terminal before Demand::Stop; already returned above (#2138)
src/jq/eval.rs tolerate 21409 both unreachable: escape! sets terminal before Demand::Stop; already returned above (#2546)
src/jq/eval.rs tolerate 29711 both unreachable today: postfix ? attaches to a single path element until #367 reopens it, so no path this resolver builds has depth() != 1 here (#2649)
src/jq/eval.rs tolerate 30071 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 30105 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 31747 both unreachable: PatternStep::component only ever builds Expr::Field/Expr::Index, and navigation_element answers Some for both (#2649)
src/jq/eval.rs tolerate 35340 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 35391 both unreachable: classify_static_component answers Field only for OwnedValue::Object, and it was handed this very value (#2190)
src/jq/eval.rs tolerate 35396 both unreachable: classify_static_component answers Index only for OwnedValue::Array, and it was handed this very value (#2190)
src/jq/eval.rs tolerate 35473 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 35517 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 39032 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 39879 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 52201 both unreachable in practice: a def with more than 64 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 52213 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 52233 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 52254 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 52271 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 52332 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 53650 both substitute_var_impl's FuncDef arm always returns FuncDef (#2283)
src/jq/eval.rs tolerate 53672 both substitute_var_impl's FuncDef arm always returns FuncDef (#2283)
src/jq/eval.rs tolerate 53690 both substitute_var_impl's FuncDef arm always returns FuncDef (#2283)
src/jq/eval.rs tolerate 53760 both substitute_var_impl's FuncDef arm always returns FuncDef (#2283)
src/jq/eval.rs tolerate 53774 both substitute_func_param_impl's FuncDef arm always returns FuncDef (#2555)
src/jq/eval.rs tolerate 60774 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 89135 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 89167 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 90047 both unreachable in a passing suite by design -- every row here is a shape jq accepts, confirmed live (#2649)
src/jq/eval.rs tolerate 90115 both unreachable in a passing suite by design -- every row here is a shape jq refuses, confirmed live (#2649)
src/jq/eval.rs tolerate 90479 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 90490 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 90511 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 90527 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_generic.rs tolerate 3553 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 6907 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 10648 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 13755 both unreachable: escape_generic!/ensure_owned! set terminal before Demand::Stop; already returned above (#2138)
src/jq/eval_generic.rs tolerate 14109 both unreachable: escape! sets terminal before Demand::Stop; already returned above (#2546)
src/jq/eval_generic.rs tolerate 16562 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 20220 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 20301 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 20569 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 22428 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 22928 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 24709 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 24737 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/json/light.rs tolerate 6311 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 6322 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 6403 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.62% (214/217 new lines covered)

File Patch Uncovered new lines
src/jq/document.rs 100% (3/3)
src/jq/eval.rs 100% (2/2)
src/jq/eval_generic.rs 96.15% (25/26) 290
src/jq/value.rs 100% (67/67)
src/json/light.rs 94.59% (35/37) 6282, 6285
src/json/validate.rs 100% (82/82)
Uncovered new lines (3)
  • src/jq/eval_generic.rs:290
  • src/json/light.rs:6282
  • src/json/light.rs:6285

Indirect coverage changes

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

Indirect changes
  • src/bin/succinctly/yq_runner.rs:3285 🟢 uncovered → covered
  • src/jq/eval.rs:22823 🔴 covered → uncovered
  • src/jq/eval_generic.rs:11376 🔴 covered → uncovered

📦 Full per-file coverage summary · run summary

@github-actions

github-actions Bot commented Sep 13, 2026

Copy link
Copy Markdown

Coverage

Total: 93.39% ⚪ 0.01 pp vs main (not attributable to this diff)

Comparing 3c25b36..b2d2be6 (merge-base → PR head)

No per-file coverage changes vs main.

🔇 0 ignored region(s), 92 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 572-584 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 1539 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 7411 both unreachable in a passing suite by design -- the fixed b\
src/bin/succinctly/yq_runner.rs tolerate 1638 both unreachable: bytes already parsed successfully by every caller (#1350)
src/bin/succinctly/yq_runner.rs tolerate 3299 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 3399 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 4028 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 1069-1076 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 1128 both unreachable: def is always a collect_alias_groups anchor path, which step_to_expr never fails on (#1351)
src/jq/eval.rs tolerate 1149 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 1158 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 1162 both unreachable: the map above never yields None, since it only ever matches Field/Index (#1351)
src/jq/eval.rs tolerate 4223 both unreachable: is_escape() is exactly `Error
src/jq/eval.rs tolerate 4224 both unreachable: see the if let above -- push_owned_values never answers None for an is_escape() result (#2180)
src/jq/eval.rs tolerate 5372 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 7253 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 10512 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 21040 both unreachable: escape_with_prefix! sets terminal before Demand::Stop; already returned above (#2138)
src/jq/eval.rs tolerate 21409 both unreachable: escape! sets terminal before Demand::Stop; already returned above (#2546)
src/jq/eval.rs tolerate 29711 both unreachable today: postfix ? attaches to a single path element until #367 reopens it, so no path this resolver builds has depth() != 1 here (#2649)
src/jq/eval.rs tolerate 30071 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 30105 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 31747 both unreachable: PatternStep::component only ever builds Expr::Field/Expr::Index, and navigation_element answers Some for both (#2649)
src/jq/eval.rs tolerate 35340 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 35391 both unreachable: classify_static_component answers Field only for OwnedValue::Object, and it was handed this very value (#2190)
src/jq/eval.rs tolerate 35396 both unreachable: classify_static_component answers Index only for OwnedValue::Array, and it was handed this very value (#2190)
src/jq/eval.rs tolerate 35473 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 35517 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 39032 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 39879 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 52201 both unreachable in practice: a def with more than 64 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 52213 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 52233 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 52254 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 52271 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 52332 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 53650 both substitute_var_impl's FuncDef arm always returns FuncDef (#2283)
src/jq/eval.rs tolerate 53672 both substitute_var_impl's FuncDef arm always returns FuncDef (#2283)
src/jq/eval.rs tolerate 53690 both substitute_var_impl's FuncDef arm always returns FuncDef (#2283)
src/jq/eval.rs tolerate 53760 both substitute_var_impl's FuncDef arm always returns FuncDef (#2283)
src/jq/eval.rs tolerate 53774 both substitute_func_param_impl's FuncDef arm always returns FuncDef (#2555)
src/jq/eval.rs tolerate 60774 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 89135 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 89167 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 90047 both unreachable in a passing suite by design -- every row here is a shape jq accepts, confirmed live (#2649)
src/jq/eval.rs tolerate 90115 both unreachable in a passing suite by design -- every row here is a shape jq refuses, confirmed live (#2649)
src/jq/eval.rs tolerate 90479 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 90490 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 90511 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 90527 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_generic.rs tolerate 3553 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 6907 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 10648 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 13755 both unreachable: escape_generic!/ensure_owned! set terminal before Demand::Stop; already returned above (#2138)
src/jq/eval_generic.rs tolerate 14109 both unreachable: escape! sets terminal before Demand::Stop; already returned above (#2546)
src/jq/eval_generic.rs tolerate 16562 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 20220 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 20301 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 20569 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 22428 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 22928 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 24709 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 24737 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/json/light.rs tolerate 6311 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 6322 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 6403 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.62% (214/217 new lines covered)

File Patch Uncovered new lines
src/jq/document.rs 100% (3/3)
src/jq/eval.rs 100% (2/2)
src/jq/eval_generic.rs 96.15% (25/26) 290
src/jq/value.rs 100% (67/67)
src/json/light.rs 94.59% (35/37) 6282, 6285
src/json/validate.rs 100% (82/82)
Uncovered new lines (3)
  • src/jq/eval_generic.rs:290
  • src/json/light.rs:6282
  • src/json/light.rs:6285

Indirect coverage changes

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

Indirect changes
  • src/bin/succinctly/yq_runner.rs:3285 🟢 uncovered → covered
  • src/jq/eval.rs:22823 🔴 covered → uncovered
  • src/jq/eval_generic.rs:11376 🔴 covered → uncovered

📦 Full per-file coverage summary · run summary

`to_json_for_reindex` spelled a bare `Float` with a display formatter
(`format_float_yq` / `jq_bare_float_display`), and the reparse could not tell
that text from a document literal, so a computed float came back as a
`NumberLiteral` and every literal-preserving rule echoed the bridge's
spelling: `[0.5+0.5] | .[0] | tostring` was `1.0` in yq mode (yq `1`), and
`[2*1e16] | .[0] | tostring` was `"2E+16"` in jq mode (jq `"2e+16"`).

The bridge now writes a finite bare `Float` as a token with a doubled
exponent marker (`1e0e0`), the same design as the NaN/Infinity sentinels:
unparseable as an ordinary number so no literal can collide, digit-leading
so the semi-index scans it as a number. `from_number_bytes` and
`JsonNumber::as_f64` decode it back to a bare `Float`; `number_literal()`
and `as_i64` already refuse it. `to_json_yq` switches to yq's threshold-aware
`format_float_yq`, which the old literal accident had been masking, and
`reindex_bridge_is_identity` admits a finite `Float` now that the round trip
is an identity on it.

Also closes #1134 (every intervening stage before `tostring`) and #1144
(`join`/`@csv`/interpolation on a constructed array), and removes the
recorded `[(0.5+0.5)] | .[0] == 1` divergence.
Three regressions the token exposed, each on a route that had been relying
on the bridge re-spelling a computed float:

- `@yaml`/`@props` spelled a bare `Float` with plain `Display` under a
  comment claiming the bridge never yields one (`[1e17*1] | .[0] | @yaml`
  gave `100000000000000000`); both arms now take yq's computed-float rule.
- A path-context pipe over an owned container (`reduce 1 as $x ([1e10*2];
  .[0] | [tostring, key])`) hands the bridge's document to the *generic*
  materializer, whose literal-first chain fell through to
  `from_document_float` and re-baked the value as `"20000000000.0"`. A
  `DocumentValue::bridge_computed_float` hook, consulted before
  `number_literal()`, keeps it bare there and in `tonumber`.
- yq's `--input-format json` DOM route printed a whole float as `a: 100`
  only because the jq-mode bridge collapsed `Float(100.0)` to an integer
  literal; real yq's JSON decoder types it `!!int`, so #978's canonicalizer
  (`from_number_literal_plain`) now does the same, with Go's saturating
  `int64` round-trip as the boundary. Fixes the pre-existing `--eval-all`
  divergence on the same route and moves `--argjson`'s rendering with it.

Also: `reindex_bridge_is_identity` admits every bare `Float` (the sentinels
make NaN/infinite ones identities too); `yq_float_fidelity_fixup` removed,
now a dead round trip; tests build tokens with the encoder instead of
hard-coding spellings; the container `tostring`/`tojson` rendering and the
`tojson` trailing-newline divergences the verification surfaced are recorded
in the yq limitations doc.
Stale docs: eval_path_context_pipe_owned/eval_path_context_pipe_detached's
doc comments, two of their own tests, and the jq limitations record still
claimed a bare Float takes the cheap detached route reindex_bridge_is_identity
now admits as an identity -- only NaN and an over-cap NumberLiteral still do.
pure_value_matrix's comment repeated the same stale claim.

Reuse: extracted sign_len(), collapsing a sign-stripping micro-pattern that
had been hand-rolled four independent times in validate.rs (is_valid_number,
strip_redundant_leading_zeros, has_leading_dot, parse_computed_float_token).
Trimmed to_json_for_reindex's doc comment, which repeated computed_float_token's
own bug-history narrative verbatim, to a pointer instead.

Efficiency: from_number_bytes now tries is_valid_number before
parse_computed_float_token, not after -- semantically identical (the token's
doubled exponent marker makes is_valid_number reject every token
unconditionally) but skips the token check's contains(&b'e') scan entirely
for the common case, including a genuine literal that happens to end in the
token's own suffix (5e0, 120e0).

Hardening: computed_float_token's finiteness precondition is now a real
assert!, not a debug_assert! -- every current caller already guarantees it,
but a future one that didn't would otherwise splice unparseable text into
reindexed JSON and corrupt the document silently in release builds.

Simplification: test_reindex_round_trip_keeps_float_and_literal_apart_2902
no longer hand-rolls a JsonIndex/StandardJson::Array walk; a per-value loop
through to_json_for_reindex + from_number_bytes covers the identical property
with far less scaffolding, matching the two sibling tests beside it.

Coverage: added a negative computed float through a container for both
modes (only the sign-stripping unit tests exercised the negative case
before; nothing walked the full arithmetic -> array -> reindex -> cursor
pipeline end to end for one).
@newhoggy
newhoggy force-pushed the issue-2902-computed-float-container-tostring branch from 397ceea to b2d2be6 Compare September 14, 2026 02:49
@newhoggy
newhoggy added this pull request to the merge queue Sep 14, 2026
Merged via the queue into main with commit 950b8a5 Sep 14, 2026
30 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