Don't apply enclosing struct captures when a branch fails - #461
Conversation
strct.Parse flushed the entire deferred-capture queue when its expression failed, applying captures belonging to enclosing structs. A lookahead disjunction that later discards the branch has then already written them (e.g. the Bad field in the issue's repro ends up true). Each struct now snapshots its start offset into the queue and applies only its own captures, on both the success and failure paths, leaving enclosing captures for their own structs to apply. Fixes alecthomas#216
Pastalikek65
left a comment
There was a problem hiding this comment.
Reviewed and tested locally against master: both new tests pass (TestStructCapturesInBadBranch — Bad is now correctly false instead of leaking true, and B: 4 — and TestNestedStructCaptureNoPanic), and the full go test ./... suite plus go vet are clean.
The approach is sound: scoping Apply to the struct\x27s own queue offset (ApplyFrom) is the minimal way to stop discarded lookahead branches from flushing enclosing structs\x27 deferred captures, and the queue truncation (p.apply[:start]) keeps the "apply exactly once" invariant. The offset-snapshot also guards the nested-struct edge case correctly.
Two small notes (non-blocking):
ctx.Apply()callers were removed entirely — worth a quick grep in follow-ups that nothing else relied on the full-queue flush semantics.- The
out == nil(optional struct) path still leaves deferred entries in the queue; safe today because the top-level struct ultimately flushes from offset 0, but a comment noting that invariant would help future maintainers.
Otherwise this looks correct and ready to merge. Fixes #216 as far as I can verify.
Scope TestIssue216 to the repo's naming and local-type idiom, and remove TestNestedStructCaptureNoPanic: it passes against the unfixed parser, so it never guarded alecthomas#216, and six existing tests already cover ApplyFrom at a non-zero offset (TestPosInjection goes deeper). Trim the duplicated rationale from ApplyFrom's doc comment, and note on the no-match path that the queue is back at applyStart.
|
Thanks for the review. On the first note: grepped, nothing else relied on the full flush. The only other place the queue moves is On the second: added a line there, but narrower than "nothing was deferred". A Separately, I dropped |
Fixes #216.
strct.Parseflushed the entire deferred-capture queue when its expression failed (ctx.Apply()), applying captures that belong to enclosing structs. When a lookahead disjunction later discards that branch, those captures have already been written, e.g. theBadfield in the repro ends uptrue.Each struct now snapshots its start offset into the queue and applies only its own captures, on both the success and failure paths, leaving enclosing structs' captures for those structs to apply. Removed the now-unused
Apply().One behaviour change worth flagging: because an inner struct no longer flushes its enclosing structs' pending captures, the global order of
setFieldcalls across nested structs shifts. Field order within a struct is unchanged, so this is only observable through aCapture()/UnmarshalText()implementation with side effects.Test:
TestIssue216, the reporter's minimal repro. It fails on the unfixed parser withBad: trueand passes with the fix.Used AI assistance on this; I reviewed and tested it.