Summary
Found reviewing the #1353 fix. Ruby's subshell delimiters are counted as
operators, the same fabrication #1312 removed for regex delimiters and
#1256 for Elixir — subshell was not in either issue's sweep.
tree-sitter-ruby aliases both ends of a `…` literal to the same
backtick token:
// grammar.js
subshell: $ => seq(
alias($._subshell_start, '`'),
optional($._literal_contents),
alias($._string_end, '`'),
),
RubyCode::get_op_type (src/getter/ruby.rs) lists R::BQUOTE in the
operator arm for the def `(cmd) method-name marker, so every
subshell literal contributes two ` operator occurrences that are
nowhere in the source as operations.
Reproduction
$ bca dump --no-config -p s.rb # excerpt
╰─ {subshell:315} : `echo hi`
├─ {`:103} : `
├─ {string_content:138} : echo hi
╰─ {`:103} : `
$ bca ops --no-config -p s.rb
operators: =, `
operands: `echo hi`, u
n1 2 / N1 3 for a line containing one operator. (Only the opening
delimiter shows in the deduplicated vocabulary, but N1 counts both —
get_operator_id_as_str folds the pair, exactly as it did for the regex
delimiters in #1312.)
Fix shape
The parent guard already sitting eleven lines above in the same match,
applied to the second delimiter family:
R::BQUOTE if ancestors.parent_has_kind(node, R::Subshell as u16) => HalsteadType::Unknown,
Parent, not ancestor — a backtick nested deeper (a def ` inside a
subshell's #{…} interpolation) must still count, which is the
distinction ruby_regex_guard_is_parent_scoped_not_ancestor_scoped
already pins for R::SLASH.
The subshell node itself is the operand (it reaches
string_operand_type in the string-like arm), so suppressing the
delimiters loses nothing.
Tests
Mirror the three regex tests directly:
- the counts for
u = `echo hi` — n1 1 / N1 1, n2 2 / N2 2;
def `(cmd) still bills a backtick operator, so the guard is
parent-scoped and the method-name marker survives;
- a backtick inside a subshell interpolation still counts.
Sweep
#1314 enumerated eight interpolating languages for the opener question
and did not look at delimiter aliasing outside regex. Any grammar that
aliases a paired delimiter to a token the operator arm also lists is in
scope — bca ops on a one-literal file is the cheap probe, and a
fabricated operator shows up immediately as an n1 the source cannot
justify.
Resolution
Status: Fixed (pending merge)
Branch: fix/issue-1360
Commit: 9108f0ad — fix(getter/ruby): stop billing subshell backticks
(the hash may be rewritten when the branch is re-signed at merge)
Root cause: tree-sitter-ruby aliases both delimiters of every subshell
spelling — `…` and all six %x forms — to the same BQUOTE token,
which RubyCode::get_op_type lists in its operator arm for the def `
method-name marker, so each literal fabricated two ` operators.
Fix: suppress a BQUOTE whose parent is a subshell
(grammar-dispatch §5). Gated rather than deleted (§6) because the
method-name marker wraps its BQUOTE in a named operator node, and
parent- rather than ancestor-scoped so a backtick method inside a
subshell's interpolation still counts. Five regression tests, each
verified against four production mutants. Sweep found no sibling language
with this defect.
Two premises in the body above were corrected in the resolution comment:
the def ` marker is an operator-wrapped BQUOTE rather than a bare
one, and the backtick heredoc (<<~`CMD`) is not a subshell sibling
— it emits a single heredoc_beginning leaf with no BQUOTE.
Summary
Found reviewing the #1353 fix. Ruby's subshell delimiters are counted as
operators, the same fabrication #1312 removed for regex delimiters and
#1256 for Elixir —
subshellwas not in either issue's sweep.tree-sitter-ruby aliases both ends of a
`…`literal to the samebacktick token:
RubyCode::get_op_type(src/getter/ruby.rs) listsR::BQUOTEin theoperator arm for the
def `(cmd)method-name marker, so everysubshell literal contributes two
`operator occurrences that arenowhere in the source as operations.
Reproduction
n1 2 / N1 3 for a line containing one operator. (Only the opening
delimiter shows in the deduplicated vocabulary, but
N1counts both —get_operator_id_as_strfolds the pair, exactly as it did for the regexdelimiters in #1312.)
Fix shape
The parent guard already sitting eleven lines above in the same match,
applied to the second delimiter family:
Parent, not ancestor — a backtick nested deeper (a
def `inside asubshell's
#{…}interpolation) must still count, which is thedistinction
ruby_regex_guard_is_parent_scoped_not_ancestor_scopedalready pins for
R::SLASH.The
subshellnode itself is the operand (it reachesstring_operand_typein the string-like arm), so suppressing thedelimiters loses nothing.
Tests
Mirror the three regex tests directly:
u = `echo hi`— n1 1 / N1 1, n2 2 / N2 2;def `(cmd)still bills a backtick operator, so the guard isparent-scoped and the method-name marker survives;
Sweep
#1314 enumerated eight interpolating languages for the opener question
and did not look at delimiter aliasing outside regex. Any grammar that
aliases a paired delimiter to a token the operator arm also lists is in
scope —
bca opson a one-literal file is the cheap probe, and afabricated operator shows up immediately as an
n1the source cannotjustify.
Resolution
Status: Fixed (pending merge)
Branch:
fix/issue-1360Commit:
9108f0ad— fix(getter/ruby): stop billing subshell backticks(the hash may be rewritten when the branch is re-signed at merge)
Root cause: tree-sitter-ruby aliases both delimiters of every subshell
spelling —
`…`and all six%xforms — to the sameBQUOTEtoken,which
RubyCode::get_op_typelists in its operator arm for thedef `method-name marker, so each literal fabricated two
`operators.Fix: suppress a
BQUOTEwhose parent is asubshell(grammar-dispatch §5). Gated rather than deleted (§6) because the
method-name marker wraps its
BQUOTEin a namedoperatornode, andparent- rather than ancestor-scoped so a backtick method inside a
subshell's interpolation still counts. Five regression tests, each
verified against four production mutants. Sweep found no sibling language
with this defect.
Two premises in the body above were corrected in the resolution comment:
the
def `marker is anoperator-wrappedBQUOTErather than a bareone, and the backtick heredoc (
<<~`CMD`) is not a subshell sibling— it emits a single
heredoc_beginningleaf with noBQUOTE.