Skip to content

fix(ast): anonymous rest param def f(*) no longer compiles to arity 0#335

Merged
linyiru merged 1 commit into
masterfrom
feat/anon-splat-params
Jun 2, 2026
Merged

fix(ast): anonymous rest param def f(*) no longer compiles to arity 0#335
linyiru merged 1 commit into
masterfrom
feat/anon-splat-params

Conversation

@linyiru
Copy link
Copy Markdown
Owner

@linyiru linyiru commented Jun 2, 2026

Summary

  • def f(*) (Ruby 2.0+ anonymous forwarding form) used to compile with arity 0 — translator dropped the rest slot when Prism reports RestParameterNode { name: None }.
  • One-line fix in ast.rs:2111 mirrors the existing anonymous block-param pattern: fall back to sentinel * so the binder still allocates a sink slot. CRuby reports the same anonymous rest as :* in Method#parameters, so introspection stays byte-identical.
  • Discovered by TRY_RUNS pass-11 probe — sinatra-4's Mustermann.new(path, **opts) at sinatra/base.rb:1818 hit this via the probe's def self.new(*) Mustermann stub. (Layer Tier 2 scans — Bundler, Tilt, stdlib slice (n=10 dataset) #13.)

Scope check

  • Named forms def f(*x) already worked — unaffected.
  • Anonymous ** and & already worked — added as regression pins in the fixture.
  • Pre-existing post-rest-required introspection ordering quirk (def h(a, *, b)) is not addressed here — it's a separate bug that was previously hidden behind this one. Tracked as a separate follow-up.

Test plan

  • cargo test --test diff_cruby anonymous_rest_param — new fixture covers 6 shapes (zero/many positional, required + rest, anonymous ** / & regression pins, Method#parameters / #arity introspection, def self.new(*) singleton form).
  • cargo test --test diff_cruby — 307/307 green (304 → 307 after recent additions; this PR adds 1).
  • cargo test — full suite green.
  • Manual pass-11 probe — confirms the Mustermann.new(...) call site no longer trips this gap; the next layer (Spike L3-A: rb_raise (longjmp across Rust stack) #14) is now waiting behind it.

`def f(*)` is the Ruby 2.0+ anonymous forwarding form — the
method should accept arbitrary positional args and silently drop
them. Prism reports this as `RestParameterNode { name: None }`.

Before this fix, the translator at ast.rs:2111 did
`rp.name().map(|n| cid_to_string(n))`, so an unnamed rest
yielded `rest = None` and the rest slot was dropped entirely.
Result: `def f(*); end` compiled with arity 0 and rejected any
positional arg with `ArgumentError: wrong number of arguments
(given N, expected 0)`. Named forms `def f(*x)` were unaffected.

Fix mirrors the existing anonymous-block-param pattern at
~ast.rs:2109: fall back to a reserved sentinel `*` (invalid as
a user identifier) so the binder still allocates a sink slot.
CRuby surfaces the same anonymous rest as `:*` in
`Method#parameters` (`[[:rest, :*]]`), so the sentinel passes
through introspection unchanged.

Discovered by TRY_RUNS pass-11 probe — sinatra-4's
`Mustermann.new(path, **opts)` at sinatra/base.rb:1818 hit this
via the probe's `module Mustermann; def self.new(*); ...; end`
stub. (Layer #13.)

Tests: `tests/diff/anonymous_rest_param.rb` covers 6 shapes —
zero/many positional, required + rest, anonymous `**` and `&`
(already worked, kept as regression pins), Method#parameters /
#arity introspection, and the `def self.new(*)` singleton form
that triggered the probe.
Copilot AI review requested due to automatic review settings June 2, 2026 03:58
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 2, 2026

gapscan PR diff

Both binaries produced identical histograms across the 10 canonical scan targets. (If the classifier changed for node classes that none of these targets exercise, this view won't catch it — the data-file diff would.)

See docs/gap-reports/ for the dataset and methodology.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes rubyrs’ AST translation for anonymous rest parameters so def f(*) no longer drops the rest slot (which previously caused methods to compile as arity 0 and reject positional arguments). This aligns runtime call acceptance and Method#parameters / Method#arity introspection with CRuby.

Changes:

  • Translate RestParameterNode { name: None } to a reserved sentinel name "*" so the compiler/binder still allocates a rest slot.
  • Add a new CRuby differential fixture covering anonymous * behavior, arity/parameters introspection, and singleton def self.new(*).
  • Register the new fixture in the diff_cruby test runner.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
crates/rubyrs/src/ast.rs Preserves anonymous rest params by mapping missing rest names to "*" (mirrors existing anonymous block-param handling).
crates/rubyrs/tests/diff/anonymous_rest_param.rb New differential fixture exercising anonymous * acceptance, introspection parity, plus regression pins for ** and &.
crates/rubyrs/tests/diff_cruby.rs Adds the new fixture to the diff test suite.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@linyiru linyiru merged commit 7ba17d0 into master Jun 2, 2026
7 of 11 checks passed
linyiru added a commit that referenced this pull request Jun 2, 2026
Three passes condensed: layers #11/#12/#13/#14 closed across
PRs #209/#324/#335/#337 between 2026-05-28 and 2026-06-01.

Highlights:
  - Pass-10: PR #324 closed `Module#module_function` (Cat H).
  - Pass-11: PR #335 closed anonymous `def f(*)` → arity 0
    (Cat I real bug, uncovered by Mustermann stub).
  - Pass-12: PR #337 closed `Class#inherited` callback never
    fired (Cat H, surfaced by `class App < Sinatra::Base`).
  - sinatra/base.rb now loads fully (REACHED-END) for the
    first time since the probe started.

Next wall (open): #15 — `Kernel#caller`, surfaced by
sinatra/base.rb:1913 `cleaned_caller` during base.rb's own
load (Sinatra::Application < Base triggers the just-fixed
inherited hook, which calls caller_files).

Pre-existing follow-up uncovered: `def h(a, *, b)`
Method#parameters renders `:opt` where CRuby renders `:rest`
and arity -2 vs CRuby's -3. Separate post-rest required-param
rendering bug, hidden by #13 until that fix landed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants