Skip to content

Module#define_method: MRI 4.0.6 arity, visibility, hooks, and bind checks - #525

Merged
tannevaled merged 4 commits into
mainfrom
feat/agent-defmethod
Aug 31, 2026
Merged

tannevaled merged 4 commits into
mainfrom
feat/agent-defmethod

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Module#define_method conformance (MRI 4.0.6)

Advances core/module/define_method_spec.rb from 45 → 85 passing (of 88), with no regressions in the wider suite.

Changes

  1. Lambda arity semantics (callProcMethod): a define_method body — from a block or a non-lambda Proc — now binds arguments with method/lambda arity: ArgumentError on a count mismatch, and no auto-splat of a lone Array. (+22 examples)
  2. Visibility: the created method takes the receiver's current default visibility (public/private/protected), is public when defined from another module, and is always private for :initialize/:initialize_copy.
  3. Name coercion: a non-Symbol/String name is coerced through #to_str; a #to_str returning a non-String raises the MRI TypeError.
  4. method_added fires once after the method is installed.
  5. Body/error paths: a Proc/Method/UnboundMethod argument takes precedence over a passed block; a non-Proc/Method/UnboundMethod body raises the MRI TypeError naming Proc/Method/UnboundMethod; a frozen receiver raises FrozenError.
  6. Cross-class bind check: a transplanted Method/UnboundMethod owned by a Class (or singleton class) may only be re-homed onto that class or a subclass, else TypeError ("bind argument must be a subclass of …" / "can't bind singleton method to a different class"); Module-owned and the Object/BasicObject universal roots stay permissive.

Files

  • internal/vm/object_model.go — callProcMethod lambda binding.
  • internal/vm/builtins.go — define_method native rewrite.
  • internal/vm/modulefunc.go — defineMethodName, defineMethodVis, checkTransplantBindable, fireMethodAdded helpers.
  • internal/vm/define_method_conformance_test.go — new coverage.
  • Two reflection tests corrected to the MRI cross-class bind rule.

Remaining 3 (out of scope for the define_method path)

  • #to_str non-String message: rbgo emits MRI 4.0.6's exact "can't convert Object to String (…)"; the corpus regex expects the older "…into String".
  • { |a,| } two-arg arity and redo: parser/compiler representation, outside modulefunc.go/object_model.go.

Verification

go test ./internal/vm/ and go test ./... green; GOOS=js GOARCH=wasm build OK; gofmt -l clean; MERGED coverage of all changed functions and the overall profile 100.0%.

🤖 Generated with Claude Code

tannevaled and others added 4 commits August 31, 2026 13:22
A method created by define_method (from a block or a non-lambda Proc)
enforces method/lambda argument arity: it raises ArgumentError on a
count mismatch and never auto-splats a lone Array argument, matching
MRI 4.0.6. Mark the anchored body Proc as a lambda so bindBlockArgs
routes through the strict path and exec's arity check fires.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bring Module#define_method to MRI 4.0.6 for the remaining spec cluster:

- Visibility: a created method takes the receiver's current default
  visibility (private/public/protected), is public when defined from
  another module, and is always private for :initialize/:initialize_copy.
- Name coercion: a non-Symbol/String name is converted through #to_str;
  a #to_str returning a non-String raises TypeError with MRI's message.
- The method_added hook fires once after the method is installed.
- A non-Proc/Method/UnboundMethod body raises the MRI TypeError naming
  Proc/Method/UnboundMethod; a Proc/Method arg takes precedence over a
  block; a frozen receiver raises FrozenError.
- A transplanted Method/UnboundMethod owned by a Class (or singleton
  class) may only be re-homed onto that class or a subclass, else
  TypeError ("bind argument must be a subclass of ..." / "can't bind
  singleton method to a different class"); Module-owned and the Object/
  BasicObject universal roots stay permissive.

Adds define_method_conformance_test.go and corrects a tier0 reflection
case that asserted the old, non-MRI lenient cross-class transplant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
TestMethodOriginalName and the tier0 reflection case transplanted a
Method/UnboundMethod onto an unrelated class, which MRI rejects. Make
the target a subclass of the source so the tests still exercise
original_name preservation and Method transplant under the MRI-correct
bind-compatibility rule now enforced by define_method.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add Go tests exercising the permissive module- and Object-owner branches
of the transplant bind check, the singleton-owner rejection, and the
always-private :initialize_copy case, bringing the changed functions to
100% MERGED coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit 37deefa into main Aug 31, 2026
7 checks passed
@tannevaled
tannevaled deleted the feat/agent-defmethod branch August 31, 2026 12:51
tannevaled added a commit that referenced this pull request Aug 31, 2026
* Raise ruby/spec floor to 17470 after wave-3 conformance work

Locks in the gains from #521 (regexp \xHH), #523 (String#encode
fallback/xml/IBM437/default_internal), #524 (Marshal Data + Regexp
encoding), #525 (Module#define_method arity/visibility/hooks) and #522
(sprintf %c/named-default/hex-float). Measured 17470 passing on the
pinned corpus, up from 17310.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* ci: re-trigger after transient GOPROXY stream errors

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant