Summary
Found reviewing the #1353 fix; the same wrapper/leaf double count one arm
below the one that issue names (.claude/rules/grammar-dispatch.md §5).
RubyCode::get_op_type (src/getter/ruby.rs) lists Integer, Float,
Complex and Rational side by side in the plain operand arm. But
tree-sitter-ruby nests them:
// grammar.js
complex: $ => seq(choice($.integer, $.float, $.rational), /i/),
rational: $ => seq(choice($.integer, $.float), 'r'),
so the wrapper and the numeral it wraps are both billed.
Reproduction
a = 1r
b = 2i
c = 3ri
d = 4
$ bca dump --no-config -p n.rb # excerpt
╰─ {rational:308} : 1r
╰─ {integer:108} : 1
╰─ {complex:307} : 2i
╰─ {integer:108} : 2
╰─ {complex:307} : 3ri
╰─ {rational:308} : 3
╰─ {integer:108} : 3
╰─ {integer:108} : 4
$ bca ops --no-config -p n.rb
operands: 1, 1r, 2, 2i, 3, 3ri, 4, a, b, c, d
Four literals and four names is eight operands; bca metrics reports
n2 11 / N2 12. 3ri is billed three times, once per level of the nest.
Fix shape
Not a deletion: both wrappers are always non-childless
(seq(choice(...), …) makes the inner numeral required), so the leaf is
never a literal's sole carrier and dropping Complex / Rational from
the operand arm is safe and loses nothing — the analogue of #1351's Bash
command_name rather than #1353's gated %w[].
The design fork is which node keeps the classification. Keeping the leaf
matches how #1263 / #1293 / #1352 settled qualified names — bill the
component, not the whole span — but it silently drops the r / i
suffix from the operand text, so 1 and 1r would share one vocabulary
entry. Keeping the wrapper preserves the distinction. Decide with
bca dump before writing the arm, and check whether a bare 1r at
top level (no enclosing complex) still yields a rational; if the
grammar ever emits a suffix-only spelling with no inner numeral, the
answer flips to a gate.
Tests
Per-line N2 assertions for 1r, 2i, 3ri and a plain 4, plus
assert_ops_operands pinning the operand text so a returning wrapper
shows up by name. Test-via-revert per .claude/rules/testing.md;
3ri is the row that discriminates a one-level fix from a complete one.
Sweep
Only Ruby was checked. Any language whose grammar wraps a numeric
literal in a suffix/prefix node is a candidate — worth a pass over
src/getter/*.rs for Complex / Imaginary / Rational / Long /
Unsigned style wrappers before closing this.
Resolution
Status: Fixed (pending merge) — branch fix/issue-1359, commit
15ac604d (the hash may be rewritten when the branch is re-signed at
merge).
RubyCode::get_op_type billed every level of a suffixed numeric
literal's nest because Integer, Float, Complex and Rational sat
side by side in one plain operand arm; a numeral whose parent is a
complex or a rational is now suppressed, leaving the wrapper — whose
span carries the r / i suffix — as the single operand.
One premise in the body above needed correcting: the grammar excerpt is
from upstream HEAD, not the pinned =0.23.1. See the comment below.
Summary
Found reviewing the #1353 fix; the same wrapper/leaf double count one arm
below the one that issue names (
.claude/rules/grammar-dispatch.md§5).RubyCode::get_op_type(src/getter/ruby.rs) listsInteger,Float,ComplexandRationalside by side in the plain operand arm. Buttree-sitter-ruby nests them:
so the wrapper and the numeral it wraps are both billed.
Reproduction
Four literals and four names is eight operands;
bca metricsreportsn2 11 / N2 12.
3riis billed three times, once per level of the nest.Fix shape
Not a deletion: both wrappers are always non-childless
(
seq(choice(...), …)makes the inner numeral required), so the leaf isnever a literal's sole carrier and dropping
Complex/Rationalfromthe operand arm is safe and loses nothing — the analogue of #1351's Bash
command_namerather than #1353's gated%w[].The design fork is which node keeps the classification. Keeping the leaf
matches how #1263 / #1293 / #1352 settled qualified names — bill the
component, not the whole span — but it silently drops the
r/isuffix from the operand text, so
1and1rwould share one vocabularyentry. Keeping the wrapper preserves the distinction. Decide with
bca dumpbefore writing the arm, and check whether a bare1rattop level (no enclosing
complex) still yields arational; if thegrammar ever emits a suffix-only spelling with no inner numeral, the
answer flips to a gate.
Tests
Per-line
N2assertions for1r,2i,3riand a plain4, plusassert_ops_operandspinning the operand text so a returning wrappershows up by name. Test-via-revert per
.claude/rules/testing.md;3riis the row that discriminates a one-level fix from a complete one.Sweep
Only Ruby was checked. Any language whose grammar wraps a numeric
literal in a suffix/prefix node is a candidate — worth a pass over
src/getter/*.rsforComplex/Imaginary/Rational/Long/Unsignedstyle wrappers before closing this.Resolution
Status: Fixed (pending merge) — branch
fix/issue-1359, commit15ac604d(the hash may be rewritten when the branch is re-signed atmerge).
RubyCode::get_op_typebilled every level of a suffixed numericliteral's nest because
Integer,Float,ComplexandRationalsatside by side in one plain operand arm; a numeral whose parent is a
complexor arationalis now suppressed, leaving the wrapper — whosespan carries the
r/isuffix — as the single operand.One premise in the body above needed correcting: the grammar excerpt is
from upstream HEAD, not the pinned
=0.23.1. See the comment below.