Skip to content

fix(getter/ruby): complex and rational literals double-count their inner numeral #1359

Description

@dekobon

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions