Severity: Low (message wording on a write that fails either way; found by /code-review on PR #2926)
Summary
jq's INDEX re-reads the target's kind at write time, so a slice component whose bound
it never parsed reports a kind-dependent error. succinctly's through_slice refuses such a
component before looking at root, so it always reports the bound error.
Only reachable when a slot stops being null between path resolution and the write — i.e.
an earlier path in the same fan-out replaced it.
Repro
Verified live against /usr/bin/jq 1.7.1 on main @ PR #2926's branch:
$ echo '{"a":null}' | jq -c '(.a, .a["x":]) = 5'
jq: error (at <stdin>:1): Cannot index number with object
$ echo '{"a":null}' | succinctly jq -c '(.a, .a["x":]) = 5'
jq: error (at <stdin>:1): Array/string slice indices must be integers
Same for |= 5. Both tools error and both exit 5; only the sentence differs.
Not a regression
Before #2926 the resolver raised Array/string slice indices must be integers for this
same program, at resolution rather than at the write — so the observable behaviour is
unchanged. #2926 moved where the refusal happens without changing what it says.
Root cause
through_slice (src/jq/eval.rs) checks SliceEditFlags::non_integer_bound ahead of its
match root, because that flag is a property of the component rather than of the target.
The component can only be produced over a null target (#2853), but by the time the
write runs the slot may hold something else.
Why the obvious narrowing is wrong
Gating the arm on matches!(root, OwnedValue::Null) and letting everything else fall
through would be worse than a wrong sentence. A SliceBoundKey::Raw bound carries
start: None, end: None, which every arm below reads as the whole slice — so an array
target would be silently spliced rather than refused. Any fix has to answer the
kind-dependent error explicitly, not delegate to the existing arms.
Suggested fix direction
Give the arm a kind-aware refusal, mirroring SliceTargetKind's existing ordering (#2546):
array/string target → Array/string slice indices must be integers; anything else →
Cannot index <kind> with object. Capture the full oracle matrix first (number, string,
array, object, boolean, and the ?/|=/del spellings of each) — the del() row
especially, since delpaths never parses the descriptor and may well no-op regardless of
kind.
Why this is separate from #2853
#2853 is about a path that resolves where succinctly used to raise. This is about a
target whose kind changed underneath an already-resolved path, which is a different
mechanism, has its own oracle matrix, and was not a divergence #2853 introduced.
Refs #2853, #2546, #2926.
Severity: Low (message wording on a write that fails either way; found by
/code-reviewon PR #2926)Summary
jq's
INDEXre-reads the target's kind at write time, so a slice component whose boundit never parsed reports a kind-dependent error. succinctly's
through_slicerefuses such acomponent before looking at
root, so it always reports the bound error.Only reachable when a slot stops being
nullbetween path resolution and the write — i.e.an earlier path in the same fan-out replaced it.
Repro
Verified live against
/usr/bin/jq1.7.1 onmain@ PR #2926's branch:Same for
|= 5. Both tools error and both exit 5; only the sentence differs.Not a regression
Before #2926 the resolver raised
Array/string slice indices must be integersfor thissame program, at resolution rather than at the write — so the observable behaviour is
unchanged. #2926 moved where the refusal happens without changing what it says.
Root cause
through_slice(src/jq/eval.rs) checksSliceEditFlags::non_integer_boundahead of itsmatch root, because that flag is a property of the component rather than of the target.The component can only be produced over a
nulltarget (#2853), but by the time thewrite runs the slot may hold something else.
Why the obvious narrowing is wrong
Gating the arm on
matches!(root, OwnedValue::Null)and letting everything else fallthrough would be worse than a wrong sentence. A
SliceBoundKey::Rawbound carriesstart: None, end: None, which every arm below reads as the whole slice — so an arraytarget would be silently spliced rather than refused. Any fix has to answer the
kind-dependent error explicitly, not delegate to the existing arms.
Suggested fix direction
Give the arm a kind-aware refusal, mirroring
SliceTargetKind's existing ordering (#2546):array/string target →
Array/string slice indices must be integers; anything else →Cannot index <kind> with object. Capture the full oracle matrix first (number, string,array, object, boolean, and the
?/|=/delspellings of each) — thedel()rowespecially, since
delpathsnever parses the descriptor and may well no-op regardless ofkind.
Why this is separate from #2853
#2853 is about a path that resolves where succinctly used to raise. This is about a
target whose kind changed underneath an already-resolved path, which is a different
mechanism, has its own oracle matrix, and was not a divergence #2853 introduced.
Refs #2853, #2546, #2926.