Problem
When you capture an alternation by name or tag (@t), and that alternation has at least one named branch and at least one anonymous branch, and it sits before a soft . anchor into a named follower, the follower stays on the conservative extras-only navigation (NextSkipExtras) instead of the both-sides-named one (NextSkip).
NextSkipExtras cannot skip an anonymous sibling (like an operator) between the matched named branch and the follower. So a match that the untagged form catches is silently dropped.
# Want: {"t":{"kind":"number","text":"5",...}} (same as the untagged form)
# Got: no match
cargo run -p plotnik -- run -l javascript -s '5 * x' -q 'Q = (program (expression_statement (binary_expression {[A: (number) B: "+"] @t . (identifier)})))'
The untagged twin {[(number) "+"] @t . (identifier)} matches {"t":5} on the same source. This contradicts the spec guarantee that "adding an anonymous branch no longer makes the named branches stricter" (docs/lang-reference.md:59).
Cause
compile/sequences.rs:414-431 clone_named_follower_skip_entry only builds the NextSkip twin when the instruction at exit is a single Match carrying NextSkipExtras on a named node.
For a tag/SetAfter capture, compile/expressions.rs:398-415 emits a trailing Set epsilon and hands its label as exit. So the follower Match is one hop further. The twin builder sees the Set epsilon, returns None, and every branch stays on the conservative extras-only follower.
The untagged Node-mechanism capture rides effects on the branch Match itself, so its exit is the follower and the twin is built.
docs/tree-navigation.md:147-151 labels this "correct but not yet optimal," but it produces user-visible dropped matches, so it is a correctness gap, not a missed optimization.
Fix
Make the twin builder look through a leading scope Set/EndObj/EndArr epsilon to find the underlying follower Match before deciding. (Or defer the twin/skip classification until after epsilon elimination merges the Set onto the follower.)
Keep the intervening Set on both the twin and conservative paths so effects fire exactly once per match path.
Acceptance
- The repro returns
{"t":{"kind":"number","text":"5",...}}.
- conformance
tagged_alternation_before_soft_anchor_stays_conservative flips to match its untagged twin.
Related
The same nav-classification path also misses the NextSkip upgrade for struct/array scope captures (EndObj/EndArr trailing epsilon) — the documented carve-out in tree-navigation.md.
Problem
When you capture an alternation by name or tag (
@t), and that alternation has at least one named branch and at least one anonymous branch, and it sits before a soft.anchor into a named follower, the follower stays on the conservative extras-only navigation (NextSkipExtras) instead of the both-sides-named one (NextSkip).NextSkipExtrascannot skip an anonymous sibling (like an operator) between the matched named branch and the follower. So a match that the untagged form catches is silently dropped.The untagged twin
{[(number) "+"] @t . (identifier)}matches{"t":5}on the same source. This contradicts the spec guarantee that "adding an anonymous branch no longer makes the named branches stricter" (docs/lang-reference.md:59).Cause
compile/sequences.rs:414-431 clone_named_follower_skip_entryonly builds theNextSkiptwin when the instruction atexitis a singleMatchcarryingNextSkipExtrason a named node.For a tag/
SetAftercapture,compile/expressions.rs:398-415emits a trailingSetepsilon and hands its label asexit. So the followerMatchis one hop further. The twin builder sees theSetepsilon, returnsNone, and every branch stays on the conservative extras-only follower.The untagged Node-mechanism capture rides effects on the branch
Matchitself, so itsexitis the follower and the twin is built.docs/tree-navigation.md:147-151labels this "correct but not yet optimal," but it produces user-visible dropped matches, so it is a correctness gap, not a missed optimization.Fix
Make the twin builder look through a leading scope
Set/EndObj/EndArrepsilon to find the underlying followerMatchbefore deciding. (Or defer the twin/skip classification until after epsilon elimination merges theSetonto the follower.)Keep the intervening
Seton both the twin and conservative paths so effects fire exactly once per match path.Acceptance
{"t":{"kind":"number","text":"5",...}}.tagged_alternation_before_soft_anchor_stays_conservativeflips to match its untagged twin.Related
The same nav-classification path also misses the
NextSkipupgrade for struct/array scope captures (EndObj/EndArrtrailing epsilon) — the documented carve-out intree-navigation.md.