Skip to content

Interior anchor after a skippable first item is ignored when the sequence also has a trailing anchor #439

Description

@zharinov

Problem

A sequence can have an optional first item, an anchor, and a trailing anchor: {(a)? . (b) .}. The anchor rules are not enforced once the sequence has that trailing ..

Q='Q = (program {(lexical_declaration)? @a . (debugger_statement) @b .})'

cargo run -p plotnik -- run -q "$Q" -s 'debugger;'             -l javascript
# Want: match {a: null, b: debugger}  (a skipped, b is the first and only child)
# Got:  no match — exit 1

cargo run -p plotnik -- run -q "$Q" -s 'foo; debugger;'        -l javascript
# Want: no match  (a skipped, so b must be first; it isn't)
# Got:  {a: null, b: debugger} — exit 0

cargo run -p plotnik -- run -q "$Q" -s 'let x; foo; debugger;' -l javascript
# Want: no match
# Got:  {a: null, b: debugger} — exit 0

Reproduces on master. This is a different code path from #415, so #415's fix does not touch it.

Cause

A sequence with a trailing anchor goes through compile_named_node_with_trailing_anchor (crates/plotnik-compiler/src/compile/expressions.rs). It works differently from the normal sequence path:

  • It compiles the body with Nav::StayExact and drives matching with an outer loop: go to the first child, then step to each next sibling and retry.
  • The body's first nav is StayExact, not a Down, so the skip-exit logic from Interior anchor is silently dropped after a skippable first item #415 never kicks in. When the optional is skipped, the follower steps forward instead of staying put.
  • The outer loop also tries the body at every child position. So even with the right nav, it would still find b at a later position, which breaks the "b must be the first child" rule.

Fix

The trailing-anchor path needs the skip-exit split (match path: b is a sibling; skip path: b degrades to the first-child anchor) and must not search forward past the first child on the skip path.

This sits in #419's territory (it lists the trailing-anchor retry as a search mechanism to unify), but the leading-anchor part is specific, so track it here.

Acceptance

  • The repros above behave per the Want lines.
  • Tests cover, for a trailing-anchored sequence with an optional first item: item present + adjacency holds; item present + adjacency broken; item absent + b is first-and-last; item absent + b not first.
  • Strict (.!) variants too.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions