Skip to content

VM captures root node when a captured ref returns via non-greedy optional skip #383

@zharinov

Description

@zharinov

Summary

  • A captured ref can fabricate a node when the callee returns without matching anything.
  • Repro via CLI: A = (identifier)??; Q = (A) @x on source foo returns the root program node as x.
  • Suspected root cause: exec_return() unconditionally writes matched_node = Some(self.cursor.node()) even when the callee matched nothing.

Repro

Run:

cargo run -p plotnik -- exec -q 'A = (identifier)??
Q = (A) @x' -s 'foo' -l javascript --entry Q

Observed output:

{
  "x": {
    "kind": "program",
    "text": "foo",
    "span": [0, 3]
  }
}

Trace also shows the callee taking the epsilon skip path and then the caller executing an epsilon [Node Set(M0)] capture:

cargo run -p plotnik -- trace -q 'A = (identifier)??
Q = (A) @x' -s 'foo' -l javascript --entry Q -vv

And the lowered bytecode contains:

Q:
  08   !   (A)                              06 : 09
  09   ε   [Node Set(M0)]                   11

Why this looks wrong

A = (identifier)?? is a non-greedy optional, so A is allowed to return without matching an identifier.
In that case, (A) @x should not fabricate a program node that A never matched.

Regardless of whether the intended behavior is null / absent / no match, returning the root node is incorrect.

Suspected root cause

In crates/plotnik-vm/src/engine/vm.rs, exec_return() does:

  • set matched_node = Some(self.cursor.node())
  • then restore tree depth

That seems correct when the callee actually matched something, but incorrect when the callee returned through a zero-match path (like a skipped optional).

A fix likely needs to distinguish:

  • return after a real match
  • return after a zero-width / skipped path

and avoid manufacturing matched_node in the latter case.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    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