Skip to content

Tagged/captured alternation before a soft anchor stays conservative and under-matches #472

@zharinov

Description

@zharinov

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions