From 38c17c2f74c6084887be1a3ec999e14d444d84a0 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Sun, 27 Sep 2026 16:34:13 +0200 Subject: [PATCH 1/5] Give a native method's arity a channel to arrive through (#714) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Method#arity answered -1 for 98% of native methods and #parameters [[:rest]] for all of them, because the native registration table recorded no argument count: there was no channel through which a declared arity could arrive, so the value was a constant dressed as a measurement. Add that channel — Method.argc, MRI's rb_define_method argc — and derive all three accessors from it: #arity the declared count, or -1 (MRI's cfunc branch of method_def_min_max_arity followed by method_def_arity) #parameters rb_unnamed_parameters(arity): 0 -> [], 1 -> [[:req]], -1 -> [[:rest]], -2 -> [[:req], [:rest]] #inspect MRI builds this fragment by WALKING #parameters (method_inspect), so "()" / "(_)" / "(*)" follow for free One field, three renderings: they cannot disagree. Proc#parameters gains the same treatment for a Go-backed Proc (rb_proc_parameters falls through to rb_unnamed_parameters too), which fixes :upcase.to_proc.parameters — arity -2, so [[:req], [:rest]] where rbgo said [[:rest]]. Declared across 428 core-class registration sites from a measured oracle (ruby 4.0.5, plus activesupport 8.1.3 and the stdlib requires for the shims rbgo registers at boot). Confusion matrix over the 417 instance methods of String/Array/Hash/Integer/Symbol/Float/Range that both engines have: 149 agreed before, 414 agree now. The 3 that remain are MRI's arity -2 (Array#pack, String#unpack, String#unpack1), which MRI reaches by implementing them in Ruby in , not through any argc. guardArgc rebuilt the Method record it wrapped, which discarded every field on it. Integer#gcd, #lcm, #gcdlcm and #to_r declared a count and answered -1 anyway; nonRetaining, attrKind, vis and origName travelled the same path unseen. It now replaces only the body — and a guard with equal bounds declares the count it enforces. Co-Authored-By: Claude Opus 5 --- internal/vm/abbrev_bind.go | 2 +- internal/vm/actionview.go | 4 +- internal/vm/activesupport.go | 68 +-- internal/vm/array_edges.go | 16 +- internal/vm/builtins.go | 682 +++++++++++++++---------------- internal/vm/complex.go | 6 +- internal/vm/deep_merge.go | 2 +- internal/vm/encoding.go | 10 +- internal/vm/lazy.go | 6 +- internal/vm/method.go | 11 +- internal/vm/method_reflect2.go | 55 ++- internal/vm/numeric_edges.go | 66 +-- internal/vm/object_model.go | 88 +++- internal/vm/prime_bind.go | 4 +- internal/vm/proc.go | 9 + internal/vm/range_edges.go | 4 +- internal/vm/rational.go | 2 +- internal/vm/scanf.go | 2 +- internal/vm/shellwords.go | 6 +- internal/vm/string_encode.go | 4 +- internal/vm/unicode_normalize.go | 6 +- 21 files changed, 612 insertions(+), 441 deletions(-) diff --git a/internal/vm/abbrev_bind.go b/internal/vm/abbrev_bind.go index 85bb5f6c..9d425a10 100644 --- a/internal/vm/abbrev_bind.go +++ b/internal/vm/abbrev_bind.go @@ -62,7 +62,7 @@ func (vm *VM) installAbbrev() { // Array#abbrev(pattern = nil) — the core extension `require "abbrev"` adds; it // calls Abbrev.abbrev(self, pattern). - vm.cArray.define("abbrev", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("abbrev", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { var pattern object.Value if len(args) > 0 { pattern = args[0] diff --git a/internal/vm/actionview.go b/internal/vm/actionview.go index c78c49de..43d61072 100644 --- a/internal/vm/actionview.go +++ b/internal/vm/actionview.go @@ -115,10 +115,10 @@ func (vm *VM) registerSafeBuffer() { return object.Bool(avContains(self.(*SafeBufferVal).buf.String(), avToS(avArg(args, 0)))) }) - vm.cString.define("html_safe", func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("html_safe", 0, func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return vm.newSafeBuffer(actionview.Raw(self.(*object.String).Str())) }) - vm.cString.define("html_safe?", func(_ *VM, _ object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("html_safe?", 0, func(_ *VM, _ object.Value, _ []object.Value, _ *Proc) object.Value { return object.Bool(false) }) } diff --git a/internal/vm/activesupport.go b/internal/vm/activesupport.go index e0751f10..347f6179 100644 --- a/internal/vm/activesupport.go +++ b/internal/vm/activesupport.go @@ -290,124 +290,124 @@ func (vm *VM) registerActiveSupportCoreExt() { } func (vm *VM) registerASString() { - d := func(name string, fn NativeFn) { vm.cString.define(name, fn) } + d := func(name string, argc int, fn NativeFn) { vm.cString.defineArgc(name, argc, fn) } str := func(self object.Value) string { return self.(*object.String).Str() } - d("blank?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + d("blank?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Bool(coreext.StringBlank(str(self))) }) - d("present?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + d("present?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Bool(coreext.StringPresent(str(self))) }) - d("presence", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + d("presence", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { if s, ok := coreext.StringPresence(str(self)); ok { return object.NewString(s) } return object.NilV }) - d("squish", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + d("squish", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.NewString(coreext.Squish(str(self))) }) - d("truncate", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.define("truncate", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return object.NewString(coreext.Truncate(str(self), int(intArg(args[0])), "", "")) }) - d("camelize", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + d("camelize", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { upper := true if len(args) > 0 { upper = camelUpper(args[0]) } return object.NewString(vm.asInflections.Camelize(str(self), upper)) }) - d("underscore", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + d("underscore", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.NewString(vm.asInflections.Underscore(str(self))) }) - d("pluralize", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + d("pluralize", -1, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.NewString(vm.asInflections.Pluralize(str(self))) }) - d("titleize", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + d("titleize", -1, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.NewString(vm.asInflections.Titleize(str(self), false)) }) - d("parameterize", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + d("parameterize", -1, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.NewString(inflector.Parameterize(str(self), "-", false)) }) - d("classify", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + d("classify", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.NewString(vm.asInflections.Classify(str(self))) }) - d("humanize", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + d("humanize", -1, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.NewString(vm.asInflections.Humanize(str(self), true, false)) }) - d("starts_with?", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + d("starts_with?", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return object.Bool(coreext.StartsWith(str(self), strArg(args[0]))) }) - d("ends_with?", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + d("ends_with?", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return object.Bool(coreext.EndsWith(str(self), strArg(args[0]))) }) } func (vm *VM) registerASArray() { - d := func(name string, fn NativeFn) { vm.cArray.define(name, fn) } + d := func(name string, argc int, fn NativeFn) { vm.cArray.defineArgc(name, argc, fn) } elems := func(self object.Value) []object.Value { return self.(*object.Array).Elems } - d("blank?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + d("blank?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Bool(coreext.ArrayBlank(boxSlice(elems(self)))) }) - d("in_groups", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.define("in_groups", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { n := int(intArg(args[0])) if len(args) > 1 && isFalse(args[1]) { return rubyFromGroups(coreext.InGroupsNoFill(boxSlice(elems(self)), n)) } return rubyFromGroups(coreext.InGroups(boxSlice(elems(self)), n, fillArg(args))) }) - d("in_groups_of", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.define("in_groups_of", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { n := int(intArg(args[0])) if len(args) > 1 && isFalse(args[1]) { return rubyFromGroups(coreext.InGroupsOfNoFill(boxSlice(elems(self)), n)) } return rubyFromGroups(coreext.InGroupsOf(boxSlice(elems(self)), n, fillArg(args))) }) - d("to_sentence", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + d("to_sentence", -1, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { strs := make([]any, len(elems(self))) for i, e := range elems(self) { strs[i] = vm.send(e, "to_s", nil, nil).(*object.String).Str() } return object.NewString(coreext.ToSentence(strs)) }) - d("second", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + d("second", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return rubyFrom(coreext.Second(boxSlice(elems(self)))) }) - d("third", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + d("third", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return rubyFrom(coreext.Third(boxSlice(elems(self)))) }) - d("fourth", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + d("fourth", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return rubyFrom(coreext.Fourth(boxSlice(elems(self)))) }) - d("fifth", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + d("fifth", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return rubyFrom(coreext.Fifth(boxSlice(elems(self)))) }) } func (vm *VM) registerASHash() { - d := func(name string, fn NativeFn) { vm.cHash.define(name, fn) } + d := func(name string, argc int, fn NativeFn) { vm.cHash.defineArgc(name, argc, fn) } h := func(self object.Value) *object.Hash { return self.(*object.Hash) } - d("blank?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + d("blank?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Bool(coreext.HashBlank(hashToGo(h(self)))) }) - d("deep_merge", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + d("deep_merge", 1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { other := hashArg(args[0]) gh, gother := hashToGo(h(self)), hashToGo(other) return rubyHashOrdered(coreext.DeepMerge(gh, gother), mergeOrder(h(self), other)) }) - d("deep_dup", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + d("deep_dup", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return rubyHashOrdered(coreext.DeepDup(hashToGo(h(self))), hashGoKeys(h(self))) }) - d("symbolize_keys", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + d("symbolize_keys", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return rubyHashOrdered(coreext.SymbolizeKeys(hashToGo(h(self))), mapKeys(hashGoKeys(h(self)), asSymKey)) }) - d("stringify_keys", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + d("stringify_keys", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return rubyHashOrdered(coreext.StringifyKeys(hashToGo(h(self))), mapKeys(hashGoKeys(h(self)), asStrKey)) }) - d("reverse_merge", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + d("reverse_merge", 1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { other := hashArg(args[0]) gh, gother := hashToGo(h(self)), hashToGo(other) return rubyHashOrdered(coreext.ReverseMerge(gh, gother), reverseMergeOrder(h(self), other)) @@ -415,12 +415,12 @@ func (vm *VM) registerASHash() { } func (vm *VM) registerASInteger() { - d := func(name string, fn NativeFn) { vm.cInteger.define(name, fn) } + d := func(name string, argc int, fn NativeFn) { vm.cInteger.defineArgc(name, argc, fn) } - d("ordinalize", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + d("ordinalize", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.NewString(coreext.Ordinalize(int(self.(object.Integer)))) }) - d("multiple_of?", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + d("multiple_of?", 1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return object.Bool(coreext.MultipleOf(int(self.(object.Integer)), int(intArg(args[0])))) }) } diff --git a/internal/vm/array_edges.go b/internal/vm/array_edges.go index f011d120..4e1b1022 100644 --- a/internal/vm/array_edges.go +++ b/internal/vm/array_edges.go @@ -19,7 +19,7 @@ func (vm *VM) registerArrayEdges() { // bsearch / bsearch_index share the same search; only the returned projection // (element vs index) differs. Without a block both return an Enumerator, as MRI // does. - vm.cArray.define("bsearch", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("bsearch", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { // MRI's bsearch Enumerator reports an unknown (nil) size. return enumForSized(self, "bsearch", func(*VM) object.Value { return object.NilV }) @@ -30,7 +30,7 @@ func (vm *VM) registerArrayEdges() { } return object.NilV }) - vm.cArray.define("bsearch_index", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("bsearch_index", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { // MRI's bsearch_index Enumerator reports an unknown (nil) size. return enumForSized(self, "bsearch_index", func(*VM) object.Value { return object.NilV }) @@ -44,7 +44,7 @@ func (vm *VM) registerArrayEdges() { // difference keeps every element of the receiver that is eql? to no element of // any argument array — duplicates in the receiver are preserved, exactly like a // chained `-`. - vm.cArray.define("difference", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("difference", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { others := vm.toAryArgs(args) var out []object.Value for _, e := range self.(*object.Array).Elems { @@ -56,7 +56,7 @@ func (vm *VM) registerArrayEdges() { }) // union concatenates the receiver and every argument, then removes eql? // duplicates keeping first-seen order — a multi-argument, de-duplicating `|`. - vm.cArray.define("union", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("union", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { others := vm.toAryArgs(args) var out []object.Value add := func(elems []object.Value) { @@ -75,7 +75,7 @@ func (vm *VM) registerArrayEdges() { // intersection keeps each element of the receiver that is eql? to some element // of every argument array, de-duplicated in receiver order. With no arguments // it is simply the receiver de-duplicated (like #uniq under eql?). - vm.cArray.define("intersection", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("intersection", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { others := vm.toAryArgs(args) var out []object.Value for _, e := range self.(*object.Array).Elems { @@ -90,7 +90,7 @@ func (vm *VM) registerArrayEdges() { }) // intersect? reports whether the receiver and the one argument array share any // element (compared with eql?), short-circuiting on the first match. - vm.cArray.define("intersect?", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("intersect?", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { other := vm.toAryArg(args[0]) for _, e := range self.(*object.Array).Elems { if vm.arrayIncludesEql(other.Elems, e) { @@ -102,7 +102,7 @@ func (vm *VM) registerArrayEdges() { // each_index yields every valid index in order and returns the array; without a // block it returns an Enumerator whose #size is the array length. - vm.cArray.define("each_index", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("each_index", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { a := self.(*object.Array) if blk == nil { n := len(a.Elems) @@ -121,7 +121,7 @@ func (vm *VM) registerArrayEdges() { // completes (an unbounded cycle only ends via break); without a block it returns // an Enumerator whose #size is n*length, 0 for a non-positive count, or infinity // for an unbounded cycle. - vm.cArray.define("cycle", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("cycle", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { a := self.(*object.Array) hasN := len(args) > 0 && !object.IsNil(args[0]) if blk == nil { diff --git a/internal/vm/builtins.go b/internal/vm/builtins.go index d6531ae9..0633cd85 100644 --- a/internal/vm/builtins.go +++ b/internal/vm/builtins.go @@ -632,7 +632,7 @@ func (vm *VM) bootstrap() { blk.isLambda = true return blk }) - vm.cSymbol.define("to_proc", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cSymbol.defineArgc("to_proc", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { name := string(self.(object.Symbol)) // :sym.to_proc is { |recv, *rest| recv.sym(*rest) } — arity -2 as in MRI. return &Proc{nativeArity: -2, symName: name, native: func(vm *VM, args []object.Value) object.Value { @@ -2016,7 +2016,7 @@ func (vm *VM) bootstrap() { }) // Symbol. - vm.cSymbol.define("to_sym", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cSymbol.defineArgc("to_sym", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return self }) aliasBuiltin(vm.cSymbol, "intern", "to_sym") @@ -2027,7 +2027,7 @@ func (vm *VM) bootstrap() { symEqual := func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return object.Bool(self == args[0]) } - vm.cSymbol.define("==", symEqual) + vm.cSymbol.defineArgc("==", 1, symEqual) aliasBuiltin(vm.cSymbol, "===", "==") // Symbol#encoding: the encoding of the symbol's string (symbol.c v3_4_0 // sym_encoding is rb_obj_encoding(rb_sym2str(sym))). rb_str_intern re-tags an @@ -2041,7 +2041,7 @@ func (vm *VM) bootstrap() { // a UTF-16LE or from a binary non-ASCII String therefore still reports UTF-8 / // ASCII-8BIT by shape rather than by provenance; carrying the tag would mean // widening object.Symbol itself. - vm.cSymbol.define("encoding", func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cSymbol.defineArgc("encoding", 0, func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { b := []byte(string(self.(object.Symbol))) switch { case asciiOnly(b): @@ -2053,7 +2053,7 @@ func (vm *VM) bootstrap() { } }) symStr := func(self object.Value) string { return string(self.(object.Symbol)) } - vm.cSymbol.define("<=>", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cSymbol.defineArgc("<=>", 1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { o, ok := args[0].(object.Symbol) if !ok { // incomparable with a non-Symbol return object.NilV @@ -2063,27 +2063,27 @@ func (vm *VM) bootstrap() { symLen := func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.IntValue(int64(utf8.RuneCountInString(symStr(self)))) } - vm.cSymbol.define("length", symLen) + vm.cSymbol.defineArgc("length", 0, symLen) aliasBuiltin(vm.cSymbol, "size", "length") - vm.cSymbol.define("empty?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cSymbol.defineArgc("empty?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Bool(symStr(self) == "") }) - vm.cSymbol.define("upcase", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cSymbol.defineArgc("upcase", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return object.Symbol(caseMapUTF8(symStr(self), caseUpcase, parseCaseOptions(caseUpcase, args))) }) - vm.cSymbol.define("downcase", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cSymbol.defineArgc("downcase", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return object.Symbol(caseMapUTF8(symStr(self), caseDowncase, parseCaseOptions(caseDowncase, args))) }) - vm.cSymbol.define("capitalize", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cSymbol.defineArgc("capitalize", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return object.Symbol(caseMapUTF8(symStr(self), caseCapitalize, parseCaseOptions(caseCapitalize, args))) }) - vm.cSymbol.define("swapcase", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cSymbol.defineArgc("swapcase", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return object.Symbol(caseMapUTF8(symStr(self), caseSwapcase, parseCaseOptions(caseSwapcase, args))) }) symSucc := func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Symbol(succString(symStr(self))) } - vm.cSymbol.define("succ", symSucc) + vm.cSymbol.defineArgc("succ", 0, symSucc) aliasBuiltin(vm.cSymbol, "next", "succ") // Symbol#[] / #slice run the whole String#[] protocol against the symbol's // name (they are registered after strIndexFn is defined, below). @@ -2092,10 +2092,10 @@ func (vm *VM) bootstrap() { // i.e. the String implementations over the symbol's name, so they take a // Regexp, run #to_str on anything else, refuse an incompatible encoding and // set $~ — none of which a plain strings.HasPrefix over strArg does. - vm.cSymbol.define("start_with?", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cSymbol.defineArgc("start_with?", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { return vm.send(symbolNameString(self), "start_with?", args, blk) }) - vm.cSymbol.define("end_with?", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cSymbol.defineArgc("end_with?", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { return vm.send(symbolNameString(self), "end_with?", args, blk) }) // symbol.c v3_4_0 rb_sym_to_s is `rb_str_dup(rb_sym2str(sym))` — a fresh @@ -2105,7 +2105,7 @@ func (vm *VM) bootstrap() { symToS := func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return symbolNameString(self) } - vm.cSymbol.define("to_s", symToS) + vm.cSymbol.defineArgc("to_s", 0, symToS) // string.c v3_4_0 Init_Symbol registers each of these PAIRS against ONE C // function (rb_define_method(rb_cSymbol, "next", sym_succ, 0) beside // "succ", "size" beside "length", "slice" beside "[]", "id2name" beside @@ -2117,7 +2117,7 @@ func (vm *VM) bootstrap() { // sym_inspect builds ":" + the name and associates the symbol's encoding // with the result (rb_enc_associate), so an ASCII-only symbol inspects as a // US-ASCII String. - vm.cSymbol.define("inspect", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cSymbol.defineArgc("inspect", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.NewStringBytesEnc([]byte(self.Inspect()), internedSymbolName(symStr(self)).Enc) }) // Symbol has no allocator and its .new is undefined (symbol.c v3_4_0 @@ -2135,24 +2135,24 @@ func (vm *VM) bootstrap() { // over the interned symbol table, so it answers the SAME frozen String each // time — core/symbol/name_spec asserts // :"ruby_3".name.equal?(:"ruby_#{1+2}".name). - vm.cSymbol.define("name", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cSymbol.defineArgc("name", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return internedSymbolName(symStr(self)) }) // Symbol#=~ is sym_match: `return rb_str_match(rb_sym2str(sym), other);`, // which is String#=~ over the name — it answers the match position and sets // $~ / $1. - vm.cSymbol.define("=~", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cSymbol.defineArgc("=~", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.send(symbolNameString(self), "=~", args, nil) }) // Spaceship (<=>) for the built-in ordered types; numerics compare across // Integer/Float, strings lexically, and a mismatched type yields nil. - vm.cInteger.define("<=>", spaceshipNumeric) - vm.cFloat.define("<=>", spaceshipNumeric) - vm.cInteger.define("**", powNumeric) - vm.cInteger.define("pow", powNumeric) - vm.cFloat.define("**", powNumeric) + vm.cInteger.defineArgc("<=>", 1, spaceshipNumeric) + vm.cFloat.defineArgc("<=>", 1, spaceshipNumeric) + vm.cInteger.defineArgc("**", 1, powNumeric) + vm.cInteger.defineArgc("pow", -1, powNumeric) + vm.cFloat.defineArgc("**", 1, powNumeric) vm.cFloat.define("pow", powNumeric) - vm.cString.define("<=>", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("<=>", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.String) if b := stringOrSubclassBytes(args[0]); b != nil { return object.IntValue(int64(strings.Compare(a.Str(), b.Str()))) @@ -2166,7 +2166,7 @@ func (vm *VM) bootstrap() { } return vm.spaceshipInvert(self, args[0]) }) - vm.cString.define("==", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("==", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.String) if b := stringOrSubclassBytes(args[0]); b != nil { // Equal bytes are not enough: rb_str_equal also requires the encodings to @@ -2181,7 +2181,7 @@ func (vm *VM) bootstrap() { } return object.False }) - vm.cString.define("eql?", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("eql?", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { // String#eql? is #== without the #to_str fallback: a non-String is never // eql?, and two Strings are eql? only with equal bytes and comparable // encodings (rb_str_eql). @@ -2210,17 +2210,17 @@ func (vm *VM) bootstrap() { } return object.IntValue(int64(utf8.RuneCountInString(string(s.Bytes())))) } - vm.cString.define("length", strLen) + vm.cString.defineArgc("length", 0, strLen) // String#size is a genuine alias of String#length (shared record), so // "abc".method(:size) == "abc".method(:length), matching MRI. aliasBuiltin(vm.cString, "size", "length") - vm.cString.define("bytesize", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("bytesize", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.IntValue(int64(len(strOf(self)))) }) - vm.cString.define("empty?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("empty?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Bool(len(strOf(self)) == 0) }) - vm.cString.define("append_as_bytes", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("append_as_bytes", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { // Append each argument's bytes to the receiver in place, never changing its // encoding (so the result may become invalidly encoded). A String appends // its raw bytes; an Integer appends its least-significant byte (wrapping @@ -2244,7 +2244,7 @@ func (vm *VM) bootstrap() { s.SetBytes(b) return s }) - vm.cString.define("dump", func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("dump", 0, func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { // Always a plain (never frozen, never subclass) String. The escaped text is // pure ASCII, so it keeps the receiver's encoding when that is ASCII- // compatible, but a non-ASCII-compatible source degrades to US-ASCII and @@ -2257,16 +2257,16 @@ func (vm *VM) bootstrap() { } return object.NewStringBytesEnc([]byte(s.Dump()), enc) }) - vm.cString.define("undump", func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("undump", 0, func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return vm.stringUndump(self.(*object.String)) }) - vm.cString.define("upcase", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("upcase", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.stringCaseMap(self, caseUpcase, args) }) - vm.cString.define("downcase", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("downcase", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.stringCaseMap(self, caseDowncase, args) }) - vm.cString.define("casecmp", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("casecmp", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { o, ok := vm.casecmpOther(self, args[0]) if !ok { // a non-String (no #to_str) or an incompatible encoding compares to nil return object.NilV @@ -2275,7 +2275,7 @@ func (vm *VM) bootstrap() { // are never folded), matching MRI. return object.IntValue(int64(strings.Compare(asciiDowncase(strOf(self)), asciiDowncase(o)))) }) - vm.cString.define("casecmp?", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("casecmp?", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { o, ok := vm.casecmpOther(self, args[0]) if !ok { return object.NilV @@ -2286,34 +2286,34 @@ func (vm *VM) bootstrap() { b := caseMapUTF8(o, caseDowncase, caseFlags{fold: true}) return object.Bool(a == b) }) - vm.cString.define("capitalize", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("capitalize", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.stringCaseMap(self, caseCapitalize, args) }) - vm.cString.define("swapcase", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("swapcase", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.stringCaseMap(self, caseSwapcase, args) }) - vm.cString.define("reverse", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("reverse", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return strEncOf(self, reverseStr(strOf(self))) }) succStr := func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.NewString(succString(strOf(self))) } - vm.cString.define("succ", succStr) + vm.cString.defineArgc("succ", 0, succStr) succBang := func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { s := self.(*object.String) vm.checkFrozen(s) s.SetBytes([]byte(succString(s.Str()))) return s } - vm.cString.define("succ!", succBang) + vm.cString.defineArgc("succ!", 0, succBang) // #next and #next! are true aliases of #succ / #succ! (they share the same // Method object, so instance_method(:next) == instance_method(:succ)). aliasBuiltin(vm.cString, "next", "succ") aliasBuiltin(vm.cString, "next!", "succ!") - vm.cString.define("chr", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("chr", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.NewString(stringChr(strOf(self))) }) - vm.cString.define("setbyte", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("setbyte", 2, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { s := self.(*object.String) vm.checkFrozen(s) b := s.MutableBytes() @@ -2328,14 +2328,14 @@ func (vm *VM) bootstrap() { b[i] = byte(vm.repeatLong(args[1])) return args[1] }) - vm.cString.define("sum", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("sum", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { bits := 16 if len(args) > 0 { bits = int(toInt(args[0])) } return object.IntValue(stringSum(strOf(self), bits)) }) - vm.cString.define("upto", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("upto", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { if blk == nil { // The returned Enumerator reports an unknown (nil) size, as MRI does. return enumForSized(self, "upto", func(_ *VM) object.Value { return object.NilV }, args...) @@ -2355,22 +2355,22 @@ func (vm *VM) bootstrap() { }) return self }) - vm.cString.define("strip", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("strip", -1, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return strEncOf(self, strings.Trim(strOf(self), wsCutset)) }) - vm.cString.define("lstrip", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("lstrip", -1, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return strEncOf(self, strings.TrimLeft(strOf(self), wsCutset)) }) - vm.cString.define("rstrip", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("rstrip", -1, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return strEncOf(self, strings.TrimRight(strOf(self), wsCutset)) }) - vm.cString.define("chomp", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("chomp", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return strEncOf(self, vm.chompSep(strOf(self), self.(*object.String).EncName(), args)) }) - vm.cString.define("chop", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("chop", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return strEncOf(self, chopStr(strOf(self), self.(*object.String).Enc)) }) - vm.cString.define("chars", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("chars", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { enc := self.(*object.String).Enc // each character keeps the receiver's encoding pieces := strCharPieces(strOf(self), enc) if blk != nil { @@ -2386,7 +2386,7 @@ func (vm *VM) bootstrap() { } return object.NewArrayFromSlice(out) }) - vm.cString.define("bytes", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("bytes", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { s := strOf(self) if blk != nil { // the block form yields each byte and returns the receiver (MRI) for i := 0; i < len(s); i++ { @@ -2400,7 +2400,7 @@ func (vm *VM) bootstrap() { } return object.NewArrayFromSlice(out) }) - vm.cString.define("getbyte", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("getbyte", 1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { if len(args) != 1 { raise("ArgumentError", "wrong number of arguments (given %d, expected 1)", len(args)) } @@ -2414,10 +2414,10 @@ func (vm *VM) bootstrap() { } return object.IntValue(int64(s[i])) }) - vm.cString.define("byteslice", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("byteslice", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return byteslice(vm, self.(*object.String), args) }) - vm.cString.define("lines", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("lines", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { segs := vm.stringLineSegs(self, args) if blk != nil { // with a block #lines yields each line and returns self for _, seg := range segs { @@ -2427,7 +2427,7 @@ func (vm *VM) bootstrap() { } return object.NewArrayFromSlice(segs) }) - vm.cString.define("each_line", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("each_line", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { if blk == nil { // MRI reports this enumerator's #size as nil (the line count is unknown). return enumForSized(self, "each_line", func(*VM) object.Value { return object.NilV }, args...) @@ -2437,7 +2437,7 @@ func (vm *VM) bootstrap() { } return self }) - vm.cString.define("each_char", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("each_char", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return enumFor(self, "each_char") } @@ -2447,7 +2447,7 @@ func (vm *VM) bootstrap() { } return self }) - vm.cString.define("grapheme_clusters", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("grapheme_clusters", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { // rb_str_grapheme_clusters passes WANTARRAY(…), which is 0 when a block is // given — so the block form yields each cluster and returns the receiver, // exactly as #each_grapheme_cluster does. @@ -2465,7 +2465,7 @@ func (vm *VM) bootstrap() { } return object.NewArrayFromSlice(out) }) - vm.cString.define("each_grapheme_cluster", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("each_grapheme_cluster", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return enumFor(self, "each_grapheme_cluster") } @@ -2475,16 +2475,16 @@ func (vm *VM) bootstrap() { } return self }) - vm.cString.define("byteindex", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("byteindex", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.strByteindex(self.(*object.String), args) }) - vm.cString.define("byterindex", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("byterindex", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.strByterindex(self.(*object.String), args) }) - vm.cString.define("bytesplice", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("bytesplice", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.strBytesplice(self.(*object.String), args) }) - vm.cString.define("each_byte", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("each_byte", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return enumFor(self, "each_byte") } @@ -2494,7 +2494,7 @@ func (vm *VM) bootstrap() { } return self }) - vm.cString.define("each_codepoint", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("each_codepoint", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { // The enumerator reports the character count for its #size — computed // without decoding, so #size works even on an invalid-encoding string @@ -2513,7 +2513,7 @@ func (vm *VM) bootstrap() { } return self }) - vm.cString.define("codepoints", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("codepoints", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { vm.checkCodepointEncoding(self.(*object.String)) // a broken string raises ArgumentError if blk != nil { // the block form yields each codepoint and returns the receiver (MRI) for _, r := range strOf(self) { @@ -2527,7 +2527,7 @@ func (vm *VM) bootstrap() { } return object.NewArrayFromSlice(out) }) - vm.cString.define("split", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("split", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { args = vm.splitCheckArgs(self.(*object.String), args) res := vm.stringSplit(strOf(self), self.(*object.String).Enc, args) if blk == nil { @@ -2540,10 +2540,10 @@ func (vm *VM) bootstrap() { } return self }) - vm.cString.define("include?", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("include?", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return object.Bool(strings.Contains(strOf(self), vm.strPatternCompat(self, args[0]))) }) - vm.cString.define("start_with?", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("start_with?", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { s := strOf(self) for _, a := range args { // true if any prefix matches; a Regexp must match at offset 0 if re, ok := a.(*Regexp); ok { @@ -2570,7 +2570,7 @@ func (vm *VM) bootstrap() { } return object.False }) - vm.cString.define("end_with?", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("end_with?", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { sb := self.(*object.String).Bytes() enc := self.(*object.String).EncName() for _, a := range args { // true if self ends with any suffix (each coerced via #to_str) @@ -2585,19 +2585,19 @@ func (vm *VM) bootstrap() { } return object.False }) - vm.cString.define("delete_prefix", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("delete_prefix", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { s := self.(*object.String) b := s.Bytes() n := vm.deletedAffixLen(s, args[0], false) return object.NewStringBytesEnc(append([]byte(nil), b[n:]...), s.EncName()) }) - vm.cString.define("delete_suffix", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("delete_suffix", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { s := self.(*object.String) b := s.Bytes() n := vm.deletedAffixLen(s, args[0], true) return object.NewStringBytesEnc(append([]byte(nil), b[:len(b)-n]...), s.EncName()) }) - vm.cString.define("delete_prefix!", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("delete_prefix!", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { s := self.(*object.String) vm.checkFrozen(s) n := vm.deletedAffixLen(s, args[0], false) @@ -2607,7 +2607,7 @@ func (vm *VM) bootstrap() { s.SetBytes(append([]byte(nil), s.Bytes()[n:]...)) return s }) - vm.cString.define("delete_suffix!", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("delete_suffix!", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { s := self.(*object.String) vm.checkFrozen(s) n := vm.deletedAffixLen(s, args[0], true) @@ -2618,7 +2618,7 @@ func (vm *VM) bootstrap() { s.SetBytes(append([]byte(nil), b[:len(b)-n]...)) return s }) - vm.cString.define("index", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("index", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { s := strOf(self) needle, re, isRe := vm.strSearchArg(self, args[0]) nChars := utf8.RuneCountInString(s) @@ -2656,7 +2656,7 @@ func (vm *VM) bootstrap() { } return object.IntValue(int64(utf8.RuneCountInString(s[:byteStart+byteIdx]))) }) - vm.cString.define("rindex", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("rindex", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { s := strOf(self) needle, re, isRe := vm.strSearchArg(self, args[0]) nChars := utf8.RuneCountInString(s) @@ -2679,7 +2679,7 @@ func (vm *VM) bootstrap() { } return strRindexString(s, needle, limit) }) - vm.cString.define("=~", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("=~", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { // string.c v3_4_0 rb_str_match: // case T_STRING: rb_raise(rb_eTypeError, "type mismatch: String given"); // case T_REGEXP: return rb_reg_match(y, x); @@ -2694,7 +2694,7 @@ func (vm *VM) bootstrap() { } return vm.send(args[0], "=~", []object.Value{self}, nil) }) - vm.cString.define("match?", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("match?", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { // string.c v3_4_0 rb_str_match_m_p: get_pat, then rb_reg_match_p(re, str, // argc > 1 ? NUM2LONG(argv[1]) : 0). The position argument is honoured, and // the call goes straight to the engine rather than dispatching #match?. @@ -2704,7 +2704,7 @@ func (vm *VM) bootstrap() { probe := append([]object.Value{self}, args[1:]...) return vm.regexpMatchP(vm.getPat(args[0]).(*Regexp), probe) }) - vm.cString.define("match", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("match", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { // string.c v3_4_0 rb_str_match_m: // re = argv[0]; argv[0] = str; // result = rb_funcallv(get_pat(re), rb_intern("match"), argc, argv); @@ -2722,16 +2722,16 @@ func (vm *VM) bootstrap() { } return result }) - vm.cString.define("scan", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("scan", 1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { return vm.scan(vm.subRegexp(args[0]), strOf(self), self, blk) }) - vm.cString.define("sub", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("sub", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { return vm.stringSub(self.(*object.String), args, blk, false) }) - vm.cString.define("gsub", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("gsub", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { return vm.stringSub(self.(*object.String), args, blk, true) }) - vm.cString.define("to_i", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("to_i", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { vm.mustASCIICompat(self) // rb_str_to_inum's first act base := 10 if len(args) > 0 { @@ -2741,17 +2741,17 @@ func (vm *VM) bootstrap() { } return stringToInt(strOf(self), base) }) - vm.cString.define("to_f", func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("to_f", 0, func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { vm.mustASCIICompat(self) // rb_str_to_dbl's first act return object.Float(parseLeadingFloat(strOf(self))) }) - vm.cString.define("oct", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("oct", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return strOct(strOf(self)) }) - vm.cString.define("hex", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("hex", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return strHex(strOf(self)) }) - vm.cString.define("to_s", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("to_s", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return self }) aliasBuiltin(vm.cString, "to_str", "to_s") // MRI alias of String#to_s @@ -2762,20 +2762,20 @@ func (vm *VM) bootstrap() { } return object.Symbol(strOf(self)) } - vm.cString.define("to_sym", strToSym) + vm.cString.defineArgc("to_sym", 0, strToSym) aliasBuiltin(vm.cString, "intern", "to_sym") // MRI alias of String#to_sym // scrub replaces each ill-formed byte sequence with a replacement (the encoding's // U+FFFD, or an explicit String / block result), returning a valid copy in the // receiver's encoding. scrub! does the same in place, returning self. - vm.cString.define("scrub", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("scrub", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { return vm.stringScrub(self, args, blk, false) }) - vm.cString.define("scrub!", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("scrub!", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { return vm.stringScrub(self, args, blk, true) }) // -@ returns a frozen copy (self when already frozen); +@ returns a mutable copy // (self when already mutable), matching MRI's String#-@ / #+@. - vm.cString.define("-@", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("-@", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { // string.c v3_4_0 str_uminus is // if (!BARE_STRING_P(str) && !rb_obj_frozen_p(str)) str = rb_str_dup(str); // return rb_fstring(str); @@ -2793,7 +2793,7 @@ func (vm *VM) bootstrap() { // deduplicated at all. return internFString(self.(*object.String)) }) - vm.cString.define("+@", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("+@", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { s := self.(*object.String) if s.Frozen { return s.Dup() @@ -2804,42 +2804,42 @@ func (vm *VM) bootstrap() { str, enc := vm.padString(self.(*object.String), args, side) return object.NewStringBytesEnc([]byte(str), enc) } - vm.cString.define("ljust", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("ljust", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return pad(vm, self, args, 'l') }) - vm.cString.define("rjust", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("rjust", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return pad(vm, self, args, 'r') }) - vm.cString.define("center", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("center", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return pad(vm, self, args, 'c') }) trFn := func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return strEncOf(self, trString(strOf(self), vm.strTrArg(args[0]), vm.strTrArg(args[1]), false)) } - vm.cString.define("tr", trFn) + vm.cString.defineArgc("tr", 2, trFn) trSFn := func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return strEncOf(self, trString(strOf(self), vm.strTrArg(args[0]), vm.strTrArg(args[1]), true)) } - vm.cString.define("tr_s", trSFn) - vm.cString.define("tr!", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("tr_s", 2, trSFn) + vm.cString.defineArgc("tr!", 2, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { from, to := vm.strTrArg(args[0]), vm.strTrArg(args[1]) return vm.strBang(self, func(s string) string { return trString(s, from, to, false) }) }) - vm.cString.define("tr_s!", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("tr_s!", 2, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { from, to := vm.strTrArg(args[0]), vm.strTrArg(args[1]) return vm.strBang(self, func(s string) string { return trString(s, from, to, true) }) }) - vm.cString.define("count", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("count", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return object.IntValue(int64(stringCount(strOf(self), vm.coerceSetArgs(args)))) }) - vm.cString.define("delete", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("delete", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return strEncOf(self, stringDelete(strOf(self), vm.coerceSetArgs(args))) }) - vm.cString.define("delete!", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("delete!", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { sets := vm.coerceSetArgs(args) return vm.strBang(self, func(s string) string { return stringDelete(s, sets) }) }) - vm.cString.define("squeeze", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("squeeze", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return strEncOf(self, stringSqueeze(strOf(self), vm.coerceSetArgs(args))) }) strIndexFn := func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { @@ -2857,14 +2857,14 @@ func (vm *VM) bootstrap() { } return res } - vm.cString.define("[]", strIndexFn) + vm.cString.defineArgc("[]", -1, strIndexFn) // Symbol#[] / #slice yield a String by running the full String#[] protocol // (Integer/Range/String/Regexp with capture groups, setting $~) against the // symbol's name, exactly like sym.to_s[...]. symIndexFn := func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { return strIndexFn(vm, object.NewString(symStr(self)), args, blk) } - vm.cSymbol.define("[]", symIndexFn) + vm.cSymbol.defineArgc("[]", -1, symIndexFn) aliasBuiltin(vm.cSymbol, "slice", "[]") // casecmp / casecmp? compare symbol names case-insensitively, but only against // another Symbol — any other argument yields nil, as MRI does (String is never @@ -2879,26 +2879,26 @@ func (vm *VM) bootstrap() { []object.Value{object.NewString(string(other))}, nil) } } - vm.cSymbol.define("casecmp", symCasecmp("casecmp")) - vm.cSymbol.define("casecmp?", symCasecmp("casecmp?")) + vm.cSymbol.defineArgc("casecmp", 1, symCasecmp("casecmp")) + vm.cSymbol.defineArgc("casecmp?", 1, symCasecmp("casecmp?")) // match / match? run the pattern (a Regexp or String) against the symbol name // by delegating to String, so #match still sets $~. - vm.cSymbol.define("match", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cSymbol.defineArgc("match", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { return vm.send(object.NewString(symStr(self)), "match", args, blk) }) - vm.cSymbol.define("match?", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cSymbol.defineArgc("match?", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { return vm.send(object.NewString(symStr(self)), "match?", args, blk) }) // #slice is a true alias of #[] (shares the exact method record). aliasBuiltin(vm.cString, "slice", "[]") - vm.cString.define("ord", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("ord", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { s := strOf(self) if s == "" { raise("ArgumentError", "empty string") } return object.IntValue(int64([]rune(s)[0])) }) - vm.cString.define("partition", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("partition", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { s := strOf(self) enc := self.(*object.String).Enc if re, ok := regexpSep(args[0]); ok { @@ -2919,7 +2919,7 @@ func (vm *VM) bootstrap() { } return object.NewArray(strEncOf(self, s), strEncOf(self, ""), strEncOf(self, "")) }) - vm.cString.define("rpartition", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("rpartition", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { s := strOf(self) enc := self.(*object.String).Enc if re, ok := regexpSep(args[0]); ok { @@ -2989,14 +2989,14 @@ func (vm *VM) bootstrap() { } return s } - vm.cString.define("<<", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("<<", 1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { if len(args) != 1 { // #<< takes exactly one argument (unlike #concat) raise("ArgumentError", "wrong number of arguments (given %d, expected 1)", len(args)) } return strConcatFn(vm, self, args, blk) }) - vm.cString.define("concat", strConcatFn) - vm.cString.define("replace", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("concat", -1, strConcatFn) + vm.cString.defineArgc("replace", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { s := self.(*object.String) vm.checkFrozen(s) repl, src := vm.strCoerceArg(args[0]) // a non-String source converts via #to_str @@ -3004,7 +3004,7 @@ func (vm *VM) bootstrap() { s.Enc = src.Enc // #replace also adopts the other string's encoding (and its validity) return s }) - vm.cString.define("prepend", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("prepend", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { s := self.(*object.String) vm.checkFrozen(s) var head []byte @@ -3015,7 +3015,7 @@ func (vm *VM) bootstrap() { s.SetBytes(append(head, s.Bytes()...)) return s }) - vm.cString.define("insert", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("insert", 2, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { s := self.(*object.String) vm.checkFrozen(s) // The inserted string converts via #to_str before the index is checked (MRI @@ -3040,71 +3040,71 @@ func (vm *VM) bootstrap() { s.Enc = newEnc return s }) - vm.cString.define("clear", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("clear", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { s := self.(*object.String) vm.checkFrozen(s) s.SetBytes(nil) return s }) - vm.cString.define("upcase!", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("upcase!", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.stringCaseMapBang(self, caseUpcase, args) }) - vm.cString.define("downcase!", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("downcase!", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.stringCaseMapBang(self, caseDowncase, args) }) - vm.cString.define("capitalize!", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("capitalize!", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.stringCaseMapBang(self, caseCapitalize, args) }) - vm.cString.define("swapcase!", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("swapcase!", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.stringCaseMapBang(self, caseSwapcase, args) }) - vm.cString.define("reverse!", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("reverse!", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { s := self.(*object.String) vm.checkFrozen(s) s.SetBytes([]byte(reverseStr(s.Str()))) return s }) - vm.cString.define("strip!", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("strip!", -1, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return vm.strBang(self, func(x string) string { return strings.Trim(x, wsCutset) }) }) - vm.cString.define("lstrip!", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("lstrip!", -1, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return vm.strBang(self, func(x string) string { return strings.TrimLeft(x, wsCutset) }) }) - vm.cString.define("rstrip!", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("rstrip!", -1, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return vm.strBang(self, func(x string) string { return strings.TrimRight(x, wsCutset) }) }) - vm.cString.define("chomp!", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("chomp!", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { enc := self.(*object.String).EncName() return vm.strBang(self, func(s string) string { return vm.chompSep(s, enc, args) }) }) - vm.cString.define("chop!", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("chop!", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { enc := self.(*object.String).Enc return vm.strBang(self, func(s string) string { return chopStr(s, enc) }) }) - vm.cString.define("squeeze!", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("squeeze!", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.strBang(self, func(s string) string { return stringSqueeze(s, args) }) }) - vm.cString.define("sub!", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("sub!", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { return vm.strSubBang(self, args, blk, false) }) - vm.cString.define("gsub!", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("gsub!", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { return vm.strSubBang(self, args, blk, true) }) - vm.cString.define("[]=", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("[]=", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.stringIndexAssign(self.(*object.String), args) }) - vm.cString.define("slice!", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("slice!", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.stringSliceBang(self.(*object.String), args) }) // Array. - vm.cArray.define("size", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("size", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.IntValue(int64(len(self.(*object.Array).Elems))) }) // #length is a true alias of #size (shares the exact method record, so // Array.instance_method(:length) == Array.instance_method(:size), as in MRI). aliasBuiltin(vm.cArray, "length", "size") - vm.cArray.define("empty?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("empty?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Bool(len(self.(*object.Array).Elems) == 0) }) // Array#initialize fills the receiver: empty / a copy of an Array argument / @@ -3166,7 +3166,7 @@ func (vm *VM) bootstrap() { } return self } - vm.cArray.define("initialize", arrayInit) + vm.cArray.defineArgc("initialize", -1, arrayInit) vm.setInstanceVisibility(vm.cArray, "initialize", visPrivate) // MRI keeps #initialize private vm.cArray.smethods["new"] = &Method{name: "new", owner: vm.cArray, native: func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { @@ -3220,7 +3220,7 @@ func (vm *VM) bootstrap() { } return r }} - vm.cArray.define("values_at", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("values_at", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array).Elems out := make([]object.Value, 0, len(args)) appendAt := func(idx int) { @@ -3267,7 +3267,7 @@ func (vm *VM) bootstrap() { } return object.NewArrayFromSlice(out) }) - vm.cArray.define("fetch", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("fetch", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { if len(args) < 1 || len(args) > 2 { raise("ArgumentError", "wrong number of arguments (given %d, expected 1..2)", len(args)) } @@ -3294,7 +3294,7 @@ func (vm *VM) bootstrap() { raise("IndexError", "index %d outside of array bounds: %d...%d", orig, -int64(len(a)), int64(len(a))) return object.NilV }) - vm.cArray.define("fetch_values", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("fetch_values", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { a := self.(*object.Array).Elems out := make([]object.Value, 0, len(args)) for _, idxV := range args { @@ -3339,7 +3339,7 @@ func (vm *VM) bootstrap() { } return object.NormInt(new(big.Int).Sqrt(n)) }} - vm.cArray.define("first", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("first", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) if len(args) == 0 { if len(a.Elems) == 0 { @@ -3355,7 +3355,7 @@ func (vm *VM) bootstrap() { copy(out, a.Elems[:n]) return object.NewArrayFromSlice(out) }) - vm.cArray.define("last", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("last", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) if len(args) == 0 { if len(a.Elems) == 0 { @@ -3368,20 +3368,20 @@ func (vm *VM) bootstrap() { copy(out, a.Elems[len(a.Elems)-n:]) return object.NewArrayFromSlice(out) }) - vm.cArray.define("fill", arrayFill) + vm.cArray.defineArgc("fill", -1, arrayFill) // Array#inspect renders each element through its own Ruby-level #inspect (so a // user-defined or mocked #inspect is honoured), following MRI's rb_inspect → // rb_obj_as_string: a non-String #inspect result is passed through #to_s, and // if that is still not a String the object's default # identity is // used (never #to_str; a #to_s exception propagates). A self-referential array // renders as "[...]". #to_s is the classic alias. - vm.cArray.define("inspect", func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("inspect", 0, func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return vm.arrayInspect(self.(*object.Array), map[*object.Array]bool{}) }) // #to_s is a true alias of #inspect: share the same method entry so // Array.instance_method(:to_s) == Array.instance_method(:inspect), as in MRI. vm.cArray.methods["to_s"] = vm.cArray.methods["inspect"] - vm.cArray.define("push", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("push", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) vm.checkArrayFrozen(a) a.Elems = append(a.Elems, args...) @@ -3389,13 +3389,13 @@ func (vm *VM) bootstrap() { }) // #append is a true alias of #push (shared entry for UnboundMethod identity). vm.cArray.methods["append"] = vm.cArray.methods["push"] - vm.cArray.define("<<", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("<<", 1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) vm.checkArrayFrozen(a) a.Elems = append(a.Elems, args[0]) return a }) - vm.cArray.define("pop", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("pop", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) vm.checkArrayFrozen(a) if len(args) > 1 { @@ -3421,7 +3421,7 @@ func (vm *VM) bootstrap() { a.Elems = a.Elems[:len(a.Elems)-1] return v }) - vm.cArray.define("shift", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("shift", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) vm.checkArrayFrozen(a) if len(args) > 1 { @@ -3452,18 +3452,18 @@ func (vm *VM) bootstrap() { a.Elems = append(append([]object.Value{}, args...), a.Elems...) return a } - vm.cArray.define("unshift", unshift) + vm.cArray.defineArgc("unshift", -1, unshift) // #prepend is a true alias of #unshift: share the entry for identity. vm.cArray.methods["prepend"] = vm.cArray.methods["unshift"] // Array#to_ary returns self (the implicit Array-conversion protocol point). - vm.cArray.define("to_ary", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("to_ary", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return self }) // Array#insert(index, *objects): insert the objects before the element at // index (or, for a negative index, after the element index counts back to — // so -1 appends). Inserting past the end pads the gap with nil, as in MRI. // With no objects the array is returned unchanged. - vm.cArray.define("insert", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("insert", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) vm.checkArrayFrozen(a) if len(args) == 0 { @@ -3493,7 +3493,7 @@ func (vm *VM) bootstrap() { a.Elems = append(append(a.Elems[:idx:idx], ins...), tail...) return a }) - vm.cArray.define("delete", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("delete", 1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { // Remove every element == the argument; return it, or (a block's result, // else nil) when nothing matched. a := self.(*object.Array) @@ -3519,7 +3519,7 @@ func (vm *VM) bootstrap() { } return object.NilV }) - vm.cArray.define("delete_if", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("delete_if", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return enumFor(self, "delete_if") } @@ -3530,7 +3530,7 @@ func (vm *VM) bootstrap() { arrayKeepIf(vm, a, blk, false) return a }) - vm.cArray.define("concat", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("concat", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) vm.checkArrayFrozen(a) // Coerce every argument to an Array first (an Array subclass directly, else @@ -3548,7 +3548,7 @@ func (vm *VM) bootstrap() { } return a }) - vm.cArray.define("clear", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("clear", 0, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { if len(args) != 0 { raise("ArgumentError", "wrong number of arguments (given %d, expected 0)", len(args)) } @@ -3557,7 +3557,7 @@ func (vm *VM) bootstrap() { a.Elems = nil return a }) - vm.cArray.define("replace", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("replace", 1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) vm.checkArrayFrozen(a) // #replace coerces its argument to an Array (subclass directly, else #to_ary). @@ -3565,7 +3565,7 @@ func (vm *VM) bootstrap() { a.Elems = append([]object.Value(nil), other.Elems...) return a }) - vm.cArray.define("rotate!", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("rotate!", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) vm.checkArrayFrozen(a) if n := len(a.Elems); n > 0 { @@ -3578,7 +3578,7 @@ func (vm *VM) bootstrap() { } return a }) - vm.cArray.define("reverse_each", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("reverse_each", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return enumFor(self, "reverse_each") } @@ -3596,10 +3596,10 @@ func (vm *VM) bootstrap() { } return object.False } - vm.cArray.define("include?", arrayInclude) + vm.cArray.defineArgc("include?", 1, arrayInclude) // member? is an alias of include? (the Enumerable name), used by Puppet's // settings initialization. - vm.cArray.define("member?", arrayInclude) + vm.cArray.defineArgc("member?", 1, arrayInclude) // #[] and #[]= only read args and copy element *values* into the array (or a // freshly allocated result); they never retain the args slice, so the OpSend // fast path may hand them the live operand-stack region (defineNR). @@ -3623,12 +3623,12 @@ func (vm *VM) bootstrap() { copy(out, a.Elems[start:start+length]) return object.NewArrayFromSlice(out) } - vm.cArray.defineNR("[]", arrayAref) + vm.cArray.defineArgcNR("[]", -1, arrayAref) // #slice is a true alias of #[] (shares the exact method record, so // Array.instance_method(:slice) == Array.instance_method(:[]), as in MRI); // #at takes a single integer index only. aliasBuiltin(vm.cArray, "slice", "[]") - vm.cArray.define("at", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("at", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { if len(args) != 1 { raise("ArgumentError", "wrong number of arguments (given %d, expected 1)", len(args)) } @@ -3639,7 +3639,7 @@ func (vm *VM) bootstrap() { return object.NilV }) // #slice! removes and returns the addressed element or span (#[] semantics). - vm.cArray.define("slice!", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("slice!", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) vm.checkArrayFrozen(a) start, length, isSpan, ok := vm.arrayArefSpan(a, args) @@ -3657,7 +3657,7 @@ func (vm *VM) bootstrap() { return object.NewArrayFromSlice(out) }) // #delete_at removes and returns the element at index, nil when out of range. - vm.cArray.define("delete_at", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("delete_at", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) vm.checkArrayFrozen(a) i, ok := arrayIndex(a, vm.repeatLong(args[0])) @@ -3669,7 +3669,7 @@ func (vm *VM) bootstrap() { return v }) // #keep_if is #select! that always returns self (not nil when unchanged). - vm.cArray.define("keep_if", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("keep_if", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return enumFor(self, "keep_if") } @@ -3679,7 +3679,7 @@ func (vm *VM) bootstrap() { return a }) // #sort_by! sorts in place by the block's key and returns self. - vm.cArray.define("sort_by!", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("sort_by!", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return enumFor(self, "sort_by!") } @@ -3706,13 +3706,13 @@ func (vm *VM) bootstrap() { }) // #shuffle returns a new array with the elements in random order; #shuffle! // shuffles the receiver in place. A random: keyword supplies a custom RNG. - vm.cArray.define("shuffle", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("shuffle", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) out := append([]object.Value(nil), a.Elems...) vm.fisherYates(out, vm.rngKwarg(args)) return object.NewArrayFromSlice(out) }) - vm.cArray.define("shuffle!", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("shuffle!", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) vm.checkArrayFrozen(a) vm.fisherYates(a.Elems, vm.rngKwarg(args)) @@ -3721,7 +3721,7 @@ func (vm *VM) bootstrap() { // #sample returns one random element (nil when empty) or, given a count, an // array of up to count distinct elements. A random: keyword supplies a custom // RNG; the count converts via #to_int and must be non-negative. - vm.cArray.define("sample", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("sample", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) rng := vm.rngKwarg(args) pos := args @@ -3750,7 +3750,7 @@ func (vm *VM) bootstrap() { } return object.NewArrayFromSlice(pool[:count]) }) - vm.cArray.defineNR("[]=", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgcNR("[]=", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) vm.checkArrayFrozen(a) // Range form: a[range] = value (a Range subclass is unwrapped by asRangeValue; @@ -3800,7 +3800,7 @@ func (vm *VM) bootstrap() { } return args[1] }) - vm.cArray.define("each", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("each", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return enumFor(self, "each") } @@ -3831,7 +3831,7 @@ func (vm *VM) bootstrap() { } return a }) - vm.cArray.define("map", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("map", 0, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { if len(args) != 0 { raise("ArgumentError", "wrong number of arguments (given %d, expected 0)", len(args)) } @@ -3853,7 +3853,7 @@ func (vm *VM) bootstrap() { // pre-sized to the input length so it never re-grows, is observably identical // (single-value yields, first-seen order) but skips that per-element overhead. // #filter delegates here through the prelude, so it inherits the fast path. - vm.cArray.define("select", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("select", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return enumFor(self, "select") } @@ -3878,10 +3878,10 @@ func (vm *VM) bootstrap() { // the prelude). The fold mirrors Enumerable#reduce exactly — the (init, sym), // (sym), (init) and bare-block forms, the "no block given" yield error, and the // nil result of an empty fold — so behaviour is byte-identical, only faster. - vm.cArray.define("reduce", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("reduce", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { return arrayReduce(vm, self.(*object.Array), args, blk) }) - vm.cArray.define("reverse", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("reverse", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { a := self.(*object.Array) out := make([]object.Value, len(a.Elems)) for i, e := range a.Elems { @@ -3889,7 +3889,7 @@ func (vm *VM) bootstrap() { } return object.NewArrayFromSlice(out) }) - vm.cArray.define("dig", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("dig", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { if len(args) == 0 { raise("ArgumentError", "wrong number of arguments (given 0, expected 1+)") } @@ -3899,12 +3899,12 @@ func (vm *VM) bootstrap() { } return object.NilV }) - vm.cArray.define("uniq", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("uniq", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { return object.NewArrayFromSlice(vm.arrayUniq(self.(*object.Array).Elems, blk)) }) // Set intersection (&) and union (|): both deduplicate, keeping first-seen // order, matching Ruby. - vm.cArray.define("&", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("&", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) b := vm.toAryArg(args[0]) var out []object.Value @@ -3915,7 +3915,7 @@ func (vm *VM) bootstrap() { } return object.NewArrayFromSlice(out) }) - vm.cArray.define("|", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("|", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) b := vm.toAryArg(args[0]) var out []object.Value @@ -3926,7 +3926,7 @@ func (vm *VM) bootstrap() { } return object.NewArrayFromSlice(out) }) - vm.cArray.define("map!", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("map!", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return enumFor(self, "map!") } @@ -3942,7 +3942,7 @@ func (vm *VM) bootstrap() { // collect! is the classic alias of map! (as collect is of map). Share the // method record so Array.instance_method(:collect!) == (:map!), as in MRI. aliasBuiltin(vm.cArray, "collect!", "map!") - vm.cArray.define("reverse!", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("reverse!", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { a := self.(*object.Array) vm.checkArrayFrozen(a) for i, j := 0, len(a.Elems)-1; i < j; i, j = i+1, j-1 { @@ -3950,7 +3950,7 @@ func (vm *VM) bootstrap() { } return self }) - vm.cArray.define("sort!", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("sort!", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { a := self.(*object.Array) vm.checkArrayFrozen(a) vm.sortSlice(a.Elems, blk) @@ -3964,10 +3964,10 @@ func (vm *VM) bootstrap() { vm.checkArrayFrozen(a) return arrayKeepIf(vm, a, blk, true) } - vm.cArray.define("select!", selectBang) + vm.cArray.defineArgc("select!", 0, selectBang) // #filter! is a true alias of #select! (shared record for identity). aliasBuiltin(vm.cArray, "filter!", "select!") - vm.cArray.define("reject!", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("reject!", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return enumFor(self, "reject!") } @@ -3975,7 +3975,7 @@ func (vm *VM) bootstrap() { vm.checkArrayFrozen(a) return arrayKeepIf(vm, a, blk, false) }) - vm.cArray.define("compact!", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("compact!", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { a := self.(*object.Array) vm.checkArrayFrozen(a) var out []object.Value @@ -3990,7 +3990,7 @@ func (vm *VM) bootstrap() { a.Elems = out return self }) - vm.cArray.define("uniq!", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("uniq!", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { a := self.(*object.Array) vm.checkArrayFrozen(a) out := vm.arrayUniq(a.Elems, blk) @@ -4000,7 +4000,7 @@ func (vm *VM) bootstrap() { a.Elems = out return self }) - vm.cArray.define("compact", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("compact", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { var out []object.Value for _, e := range self.(*object.Array).Elems { if _, isNil := e.(object.Nil); !isNil { @@ -4009,14 +4009,14 @@ func (vm *VM) bootstrap() { } return object.NewArrayFromSlice(out) }) - vm.cArray.define("flatten", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("flatten", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { depth := -1 if len(args) > 0 && !object.IsNil(args[0]) { depth = int(vm.repeatLong(args[0])) // #to_int coercion (a nil depth is unbounded) } return object.NewArrayFromSlice(vm.flattenDepth(self.(*object.Array).Elems, depth)) }) - vm.cArray.define("flatten!", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("flatten!", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { depth := -1 if len(args) > 0 && !object.IsNil(args[0]) { depth = int(vm.repeatLong(args[0])) // #to_int coercion (a nil depth is unbounded) @@ -4030,14 +4030,14 @@ func (vm *VM) bootstrap() { a.Elems = out return self }) - vm.cArray.define("sum", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("sum", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { init := object.Value(object.IntValue(0)) if len(args) > 0 { init = args[0] } return vm.arraySum(self.(*object.Array), init, blk) }) - vm.cArray.define("to_h", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("to_h", 0, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { if len(args) != 0 { raise("ArgumentError", "wrong number of arguments (given %d, expected 0)", len(args)) } @@ -4065,7 +4065,7 @@ func (vm *VM) bootstrap() { } return h }) - vm.cArray.define("each_slice", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("each_slice", 1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { if len(args) < 1 { raise("ArgumentError", "wrong number of arguments (given 0, expected 1)") } @@ -4088,7 +4088,7 @@ func (vm *VM) bootstrap() { } return self }) - vm.cArray.define("each_cons", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("each_cons", 1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { if len(args) < 1 { raise("ArgumentError", "wrong number of arguments (given 0, expected 1)") } @@ -4107,7 +4107,7 @@ func (vm *VM) bootstrap() { } return self }) - vm.cArray.define("transpose", func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("transpose", 0, func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { rows := self.(*object.Array).Elems if len(rows) == 0 { return object.NewArray() @@ -4141,7 +4141,7 @@ func (vm *VM) bootstrap() { } return object.NewArrayFromSlice(out) }) - vm.cArray.define("product", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("product", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { lists := [][]object.Value{self.(*object.Array).Elems} for _, a := range args { // checkArrayType coerces via #to_ary, including a #to_ary reached only @@ -4196,7 +4196,7 @@ func (vm *VM) bootstrap() { } return object.NewArrayFromSlice(out) }) - vm.cArray.define("combination", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("combination", 1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { k := int(intArg(args[0])) elems := self.(*object.Array).Elems var combos []object.Value @@ -4233,7 +4233,7 @@ func (vm *VM) bootstrap() { } return self }) - vm.cArray.define("permutation", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("permutation", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { // No block: a lazy Enumerator that re-reads self when iterated (MRI sees a // later mutation of the array), with an explicit descending-factorial #size. if blk == nil { @@ -4280,7 +4280,7 @@ func (vm *VM) bootstrap() { } return self }) - vm.cArray.define("repeated_combination", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("repeated_combination", 1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { k := int(vm.repeatLong(args[0])) // No block: a lazy Enumerator that re-reads self when iterated (MRI sees // later mutations of the array), with an explicit combinatorial #size. @@ -4325,7 +4325,7 @@ func (vm *VM) bootstrap() { // k < 0, or k > 0 with an empty receiver, yields nothing. return self }) - vm.cArray.define("repeated_permutation", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("repeated_permutation", 1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { k := int(vm.repeatLong(args[0])) if blk == nil { return enumForSized(self, "repeated_permutation", func(vm *VM) object.Value { @@ -4363,7 +4363,7 @@ func (vm *VM) bootstrap() { } return self }) - vm.cArray.define("take_while", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("take_while", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return enumFor(self, "take_while") } @@ -4379,7 +4379,7 @@ func (vm *VM) bootstrap() { } return object.NewArrayFromSlice(out) }) - vm.cArray.define("drop_while", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("drop_while", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return enumFor(self, "drop_while") } @@ -4392,7 +4392,7 @@ func (vm *VM) bootstrap() { copy(out, a.Elems[i:]) return object.NewArrayFromSlice(out) }) - vm.cArray.define("rotate", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("rotate", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) n := len(a.Elems) if n == 0 { @@ -4409,7 +4409,7 @@ func (vm *VM) bootstrap() { } return object.NewArrayFromSlice(out) }) - vm.cArray.define("join", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("join", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) // An empty Array joins to an empty US-ASCII string without ever touching the // separator (MRI does not call #to_str on it), regardless of $,. @@ -4433,7 +4433,7 @@ func (vm *VM) bootstrap() { // record, so Array.instance_method(:index) == Array.instance_method(:find_index), // as in MRI — both are the same C function there). The live-index loop lets a // block that grows the array scan the appended tail too. - vm.cArray.define("index", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("index", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { if len(args) > 1 { raise("ArgumentError", "wrong number of arguments (given %d, expected 0..1)", len(args)) } @@ -4464,7 +4464,7 @@ func (vm *VM) bootstrap() { // element == obj; rindex { |e| … } → the last index whose block is truthy (an // argument, if given, wins over the block); no argument and no block → a sized // Enumerator. Returns nil when nothing matches. - vm.cArray.define("rindex", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("rindex", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { a := self.(*object.Array) if len(args) == 0 && blk == nil { // MRI's rindex Enumerator reports an unknown (nil) size. @@ -4493,13 +4493,13 @@ func (vm *VM) bootstrap() { } return object.NilV }) - vm.cArray.define("assoc", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("assoc", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.arrayAssoc(self.(*object.Array), args[0], 0) }) - vm.cArray.define("rassoc", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("rassoc", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.arrayAssoc(self.(*object.Array), args[0], 1) }) - vm.cArray.define("take", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("take", 1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) n := int(intArg(args[0])) if n < 0 { @@ -4512,7 +4512,7 @@ func (vm *VM) bootstrap() { copy(out, a.Elems[:n]) return object.NewArrayFromSlice(out) }) - vm.cArray.define("drop", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("drop", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) n := int(vm.toIntCoerce(args[0])) if n < 0 { @@ -4525,14 +4525,14 @@ func (vm *VM) bootstrap() { copy(out, a.Elems[n:]) return object.NewArrayFromSlice(out) }) - vm.cArray.define("sort", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("sort", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { a := self.(*object.Array) out := make([]object.Value, len(a.Elems)) copy(out, a.Elems) vm.sortSlice(out, blk) return object.NewArrayFromSlice(out) }) - vm.cArray.define("<=>", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("<=>", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { sa := self.(*object.Array) b, ok := args[0].(*object.Array) if !ok { @@ -4636,7 +4636,7 @@ func (vm *VM) bootstrap() { } return object.NilV }) - vm.cArray.define("sort_by", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("sort_by", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return enumFor(self, "sort_by") } @@ -4662,21 +4662,21 @@ func (vm *VM) bootstrap() { } return object.NewArrayFromSlice(out) }) - vm.cArray.define("min_by", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("min_by", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { a := self.(*object.Array) if blk != nil && len(args) > 0 && !object.IsNil(args[0]) { return vm.arrayByExtremeN(a, blk, int(coerceInt(vm, args[0])), -1) } return vm.arrayByExtreme(a, blk, "min_by", -1) }) - vm.cArray.define("max_by", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("max_by", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { a := self.(*object.Array) if blk != nil && len(args) > 0 && !object.IsNil(args[0]) { return vm.arrayByExtremeN(a, blk, int(coerceInt(vm, args[0])), 1) } return vm.arrayByExtreme(a, blk, "max_by", 1) }) - vm.cArray.define("each_with_object", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cArray.defineArgc("each_with_object", 1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { if len(args) < 1 { raise("ArgumentError", "wrong number of arguments (given 0, expected 1)") } @@ -4721,7 +4721,7 @@ func (vm *VM) bootstrap() { } return self } - vm.cHash.define("initialize", hashInit) + vm.cHash.defineArgc("initialize", -1, hashInit) // Hash#initialize is private, like MRI's (Hash.private_instance_methods // includes :initialize). vm.setInstanceVisibility(vm.cHash, "initialize", visPrivate) @@ -4765,7 +4765,7 @@ func (vm *VM) bootstrap() { } return self } - vm.cString.define("initialize", stringInit) + vm.cString.defineArgc("initialize", -1, stringInit) vm.setInstanceVisibility(vm.cString, "initialize", visPrivate) // MRI keeps #initialize private // String.new builds a real String (it was falling through to the // instance-allocating Class#new and producing a bogus object). A subclass @@ -4892,29 +4892,29 @@ func (vm *VM) bootstrap() { // #[] reads args[0] and returns a stored/default value; #[]= copies element // values into the hash. Neither retains the args slice, so both take the // no-copy OpSend fast path (defineNR). - vm.cHash.defineNR("[]", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgcNR("[]", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) if v, ok := h.Get(args[0]); ok { return v } return vm.hashDefault(h, args[0]) }) - vm.cHash.defineNR("[]=", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgcNR("[]=", 2, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) vm.checkHashFrozen(h) h.Set(args[0], args[1]) return args[1] }) - vm.cHash.define("size", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("size", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.IntValue(int64(self.(*object.Hash).Len())) }) // length is a true alias of size (Hash.instance_method(:length) == // Hash.instance_method(:size)). aliasBuiltin(vm.cHash, "length", "size") - vm.cHash.define("empty?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("empty?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Bool(self.(*object.Hash).Len() == 0) }) - vm.cHash.define("clear", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("clear", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) vm.checkHashFrozen(h) h.Clear() @@ -4929,17 +4929,17 @@ func (vm *VM) bootstrap() { // Hash.instance_method(:include?). Install the record once and alias the rest // (a separate #define per name would make four distinct definitions that // compare unequal as UnboundMethods). - vm.cHash.define("key?", hashKeyP) + vm.cHash.defineArgc("key?", 1, hashKeyP) aliasBuiltin(vm.cHash, "has_key?", "key?") aliasBuiltin(vm.cHash, "include?", "key?") aliasBuiltin(vm.cHash, "member?", "key?") - vm.cHash.define("keys", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("keys", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) ks := make([]object.Value, len(h.Keys)) copy(ks, h.Keys) return object.NewArrayFromSlice(ks) }) - vm.cHash.define("values", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("values", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) vs := make([]object.Value, 0, len(h.Keys)) for _, k := range h.Keys { @@ -4948,7 +4948,7 @@ func (vm *VM) bootstrap() { } return object.NewArrayFromSlice(vs) }) - vm.cHash.define("each", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cHash.defineArgc("each", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return hashSizedEnum(self, "each") } @@ -4960,7 +4960,7 @@ func (vm *VM) bootstrap() { }) vm.cHash.methods["each_pair"] = vm.cHash.methods["each"] bumpMethodSerial() - vm.cHash.define("each_key", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cHash.defineArgc("each_key", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return hashSizedEnum(self, "each_key") } @@ -4970,7 +4970,7 @@ func (vm *VM) bootstrap() { }) return h }) - vm.cHash.define("each_value", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cHash.defineArgc("each_value", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return hashSizedEnum(self, "each_value") } @@ -4999,7 +4999,7 @@ func (vm *VM) bootstrap() { } } } - vm.cHash.define("merge", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cHash.defineArgc("merge", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { h := self.(*object.Hash) out := newHashLike(h) // merge (unlike slice/except/select) carries over the receiver's default @@ -5019,11 +5019,11 @@ func (vm *VM) bootstrap() { mergeInto(vm, h, args, blk) return h } - vm.cHash.define("merge!", mergeBang) + vm.cHash.defineArgc("merge!", -1, mergeBang) // update is a true alias of merge! (shared record: instance_method(:update) // == instance_method(:merge!)). aliasBuiltin(vm.cHash, "update", "merge!") - vm.cHash.define("slice", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("slice", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) out := newHashLike(h) for _, k := range args { @@ -5033,7 +5033,7 @@ func (vm *VM) bootstrap() { } return out }) - vm.cHash.define("except", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("except", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { // Copy then drop each key by value (Delete keys by hashKey, not by the // argument's object identity — a previous identity-keyed Go map dropped // nothing, since stored keys are distinct objects from the arguments). @@ -5048,7 +5048,7 @@ func (vm *VM) bootstrap() { } return out }) - vm.cHash.define("fetch", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cHash.defineArgc("fetch", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { if len(args) < 1 || len(args) > 2 { raise("ArgumentError", "wrong number of arguments (given %d, expected 1..2)", len(args)) } @@ -5070,10 +5070,10 @@ func (vm *VM) bootstrap() { map[string]object.Value{"@key": args[0], "@receiver": self}) return object.NilV }) - vm.cHash.define("dig", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("dig", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.digValue(self, args) }) - vm.cHash.define("values_at", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("values_at", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) out := make([]object.Value, len(args)) for i, k := range args { @@ -5087,7 +5087,7 @@ func (vm *VM) bootstrap() { // key) when a block is given, else raises KeyError with MRI's "key not found: // %p" message. With no keys it returns []. Unlike values_at, a bare missing // key is an error rather than nil. - vm.cHash.define("fetch_values", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cHash.defineArgc("fetch_values", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { h := self.(*object.Hash) out := make([]object.Value, 0, len(args)) for _, k := range args { @@ -5107,7 +5107,7 @@ func (vm *VM) bootstrap() { // replace(other) discards the receiver's contents and copies other's pairs, // default value, default proc and compare_by_identity flag, returning self. // other is coerced with #to_hash (a non-convertible argument raises TypeError). - vm.cHash.define("replace", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("replace", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) vm.checkHashFrozen(h) h.ReplaceWith(vm.hashOperand(args[0])) @@ -5116,17 +5116,17 @@ func (vm *VM) bootstrap() { // compare_by_identity switches the receiver to identity-based key comparison // (distinct objects with equal content become distinct keys) and returns self, // rehashing existing entries so they stay reachable by their original objects. - vm.cHash.define("compare_by_identity", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("compare_by_identity", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) vm.checkHashFrozen(h) h.CompareByIdentity() return h }) // compare_by_identity? reports whether the receiver compares keys by identity. - vm.cHash.define("compare_by_identity?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("compare_by_identity?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Bool(self.(*object.Hash).Identity) }) - vm.cHash.define("transform_values", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cHash.defineArgc("transform_values", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return hashSizedEnum(self, "transform_values") } @@ -5138,7 +5138,7 @@ func (vm *VM) bootstrap() { } return out }) - vm.cHash.define("transform_keys", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cHash.defineArgc("transform_keys", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { var mapping *object.Hash if len(args) > 0 { mapping, _ = args[0].(*object.Hash) @@ -5154,7 +5154,7 @@ func (vm *VM) bootstrap() { } return out }) - vm.cHash.define("transform_values!", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cHash.defineArgc("transform_values!", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return hashSizedEnum(self, "transform_values!") } @@ -5166,7 +5166,7 @@ func (vm *VM) bootstrap() { } return h }) - vm.cHash.define("transform_keys!", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cHash.defineArgc("transform_keys!", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { var mapping *object.Hash if len(args) > 0 { mapping, _ = args[0].(*object.Hash) @@ -5199,7 +5199,7 @@ func (vm *VM) bootstrap() { } return h }) - vm.cHash.define("invert", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("invert", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) out := object.NewHash() for _, k := range h.Keys { @@ -5208,7 +5208,7 @@ func (vm *VM) bootstrap() { } return out }) - vm.cHash.define("to_h", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cHash.defineArgc("to_h", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { h := self.(*object.Hash) if blk == nil { return self @@ -5238,7 +5238,7 @@ func (vm *VM) bootstrap() { }) // to_hash returns the receiver itself (a plain Hash and, in MRI, an instance // of a Hash subclass alike), unlike to_h which produces a plain-Hash copy. - vm.cHash.define("to_hash", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("to_hash", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return self }) // store is a true alias of []= (shared record: instance_method(:store) == @@ -5247,7 +5247,7 @@ func (vm *VM) bootstrap() { // default / default= and default_proc / default_proc= manage the value (or // block) returned for an absent key. The static default and the default block // are mutually exclusive in MRI: setting one clears the other. - vm.cHash.define("default", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("default", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) // default(key) invokes the default block (passing the hash and key); with // no argument it returns the static default, ignoring any block. @@ -5259,14 +5259,14 @@ func (vm *VM) bootstrap() { } return object.NilV }) - vm.cHash.define("default=", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("default=", 1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) vm.checkHashFrozen(h) h.Default = args[0] h.DefaultProc = nil return args[0] }) - vm.cHash.define("default_proc", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("default_proc", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) if !object.IsNil(h.DefaultProc) { return h.DefaultProc @@ -5286,7 +5286,7 @@ func (vm *VM) bootstrap() { h.DefaultProc = p h.Default = nil } - vm.cHash.define("default_proc=", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("default_proc=", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) vm.checkHashFrozen(h) switch p := args[0].(type) { @@ -5307,7 +5307,7 @@ func (vm *VM) bootstrap() { } return args[0] }) - vm.cHash.define("delete", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cHash.defineArgc("delete", 1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { h := self.(*object.Hash) vm.checkHashFrozen(h) v, ok := h.Delete(args[0]) @@ -5320,7 +5320,7 @@ func (vm *VM) bootstrap() { }) // #shift removes the first inserted pair and returns it as [key, value], or // nil when the hash is empty (ruby 3.4+). - vm.cHash.define("shift", func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("shift", 0, func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) vm.checkHashFrozen(h) if h.Len() == 0 { @@ -5333,7 +5333,7 @@ func (vm *VM) bootstrap() { }) // #rehash recomputes every key's hash (e.g. after a mutable key changed) and // returns self; keys that have become #eql? collapse to the first inserted. - vm.cHash.define("rehash", func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("rehash", 0, func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) vm.checkHashFrozen(h) h.Rehash() @@ -5349,11 +5349,11 @@ func (vm *VM) bootstrap() { } return object.False } - vm.cHash.define("value?", hashHasValue) + vm.cHash.defineArgc("value?", 1, hashHasValue) // has_value? is a true alias of value?. aliasBuiltin(vm.cHash, "has_value?", "value?") // Hash#key(value): the first key whose value equals value (by ==), else nil. - vm.cHash.define("key", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("key", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) for _, k := range h.Keys { v, _ := h.Get(k) @@ -5365,7 +5365,7 @@ func (vm *VM) bootstrap() { }) // select/reject return a Hash (unlike Enumerable's Array forms), so they are // native rather than inherited. - vm.cHash.define("select", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cHash.defineArgc("select", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return hashSizedEnum(self, "select") } @@ -5383,7 +5383,7 @@ func (vm *VM) bootstrap() { } return out }) - vm.cHash.define("reject", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cHash.defineArgc("reject", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return hashSizedEnum(self, "reject") } @@ -5419,7 +5419,7 @@ func (vm *VM) bootstrap() { // delete_if / reject! delete the pairs the block accepts; keep_if / select! // keep them (delete the rest). delete_if and keep_if always return the Hash; // reject! and select! return nil when they removed nothing, matching MRI. - vm.cHash.define("delete_if", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cHash.defineArgc("delete_if", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return hashSizedEnum(self, "delete_if") } @@ -5427,7 +5427,7 @@ func (vm *VM) bootstrap() { hashDeleteWhere(vm, self.(*object.Hash), blk, true) return self }) - vm.cHash.define("reject!", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cHash.defineArgc("reject!", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return hashSizedEnum(self, "reject!") } @@ -5437,7 +5437,7 @@ func (vm *VM) bootstrap() { } return self }) - vm.cHash.define("keep_if", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cHash.defineArgc("keep_if", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return hashSizedEnum(self, "keep_if") } @@ -5445,7 +5445,7 @@ func (vm *VM) bootstrap() { hashDeleteWhere(vm, self.(*object.Hash), blk, false) return self }) - vm.cHash.define("select!", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cHash.defineArgc("select!", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return hashSizedEnum(self, "select!") } @@ -5462,7 +5462,7 @@ func (vm *VM) bootstrap() { // assoc/rassoc scan the pairs in insertion order and return the first // [key, value] whose key (assoc) or value (rassoc) is Ruby-== to the // argument, or nil when none matches. They never consult the default. - vm.cHash.define("assoc", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("assoc", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) for _, k := range h.Keys { if vm.vmValueEqual(k, args[0]) { @@ -5472,7 +5472,7 @@ func (vm *VM) bootstrap() { } return object.NilV }) - vm.cHash.define("rassoc", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("rassoc", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) for _, k := range h.Keys { v, _ := h.Get(k) @@ -5487,22 +5487,22 @@ func (vm *VM) bootstrap() { // rb_equal, so 1 and 1.0 match). A non-Hash argument is coerced via #to_hash; // one without #to_hash raises TypeError "no implicit conversion of X into // Hash" (matching MRI's rb_check_hash_type + Check_Type). - vm.cHash.define("<=", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("<=", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return object.Bool(vm.hashSubset(self.(*object.Hash), vm.toHash(args[0]))) }) // Hash#< is proper-subset: <= and strictly smaller (fewer pairs). Because a // subset with equal size is an equal hash, the size test alone distinguishes // proper from improper containment. - vm.cHash.define("<", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("<", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { h, other := self.(*object.Hash), vm.toHash(args[0]) return object.Bool(h.Len() < other.Len() && vm.hashSubset(h, other)) }) // Hash#>= is superset-by-pair: every pair of other is present in the receiver. - vm.cHash.define(">=", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc(">=", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return object.Bool(vm.hashSubset(vm.toHash(args[0]), self.(*object.Hash))) }) // Hash#> is proper-superset: >= and strictly larger. - vm.cHash.define(">", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc(">", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { h, other := self.(*object.Hash), vm.toHash(args[0]) return object.Bool(h.Len() > other.Len() && vm.hashSubset(other, h)) }) @@ -5512,7 +5512,7 @@ func (vm *VM) bootstrap() { // reference, so later mutations are visible through the proc. As a lambda it is // arity-strict (exactly 1 argument), raising ArgumentError otherwise, and // Proc#lambda? is true. - vm.cHash.define("to_proc", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("to_proc", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return &Proc{isLambda: true, nativeArity: 1, native: func(vm *VM, args []object.Value) object.Value { if len(args) != 1 { raise("ArgumentError", "wrong number of arguments (given %d, expected 1)", len(args)) @@ -5524,7 +5524,7 @@ func (vm *VM) bootstrap() { // 1 (the pair wrappers are removed but Array values are left intact); a depth // >= 2 recurses that many further levels into nested Array values. A // non-Integer argument raises TypeError (via intArg). - vm.cHash.define("flatten", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("flatten", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { depth := 1 if len(args) > 0 { depth = int(intArg(args[0])) @@ -5540,7 +5540,7 @@ func (vm *VM) bootstrap() { // compact returns a copy without the nil-valued pairs, preserving the // receiver's default value and default proc. compact! removes them in place, // returning self, or nil when nothing was removed. - vm.cHash.define("compact", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("compact", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) out := newHashLike(h) for _, k := range h.Keys { @@ -5553,7 +5553,7 @@ func (vm *VM) bootstrap() { out.DefaultProc = h.DefaultProc return out }) - vm.cHash.define("compact!", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("compact!", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) vm.checkHashFrozen(h) var drop []object.Value @@ -5574,7 +5574,7 @@ func (vm *VM) bootstrap() { // #inspect on every key and value through the object's own (possibly Ruby- // level) method — unlike the low-level repr used for internal formatting, // which cannot dispatch to a user-defined #inspect. to_s is a true alias. - vm.cHash.define("inspect", func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("inspect", 0, func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { h := self.(*object.Hash) if h.Len() == 0 { return object.NewString("{}") @@ -5613,10 +5613,10 @@ func (vm *VM) bootstrap() { aliasBuiltin(vm.cHash, "to_s", "inspect") // Range. - vm.cRange.define("begin", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cRange.defineArgc("begin", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return self.(*object.Range).Lo }) - vm.cRange.define("first", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cRange.defineArgc("first", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { r := self.(*object.Range) if len(args) == 0 { // Bare #first is the begin — but a beginless range has no first element. @@ -5633,10 +5633,10 @@ func (vm *VM) bootstrap() { } return object.NewArrayFromSlice(vm.rangeFirstN(r, n)) }) - vm.cRange.define("end", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cRange.defineArgc("end", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return self.(*object.Range).Hi }) - vm.cRange.define("last", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cRange.defineArgc("last", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { r := self.(*object.Range) if _, isNil := r.Hi.(object.Nil); isNil { raise("RangeError", "cannot get the last element of endless range") @@ -5650,7 +5650,7 @@ func (vm *VM) bootstrap() { copy(out, elems[len(elems)-n:]) return object.NewArrayFromSlice(out) }) - vm.cRange.define("exclude_end?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cRange.defineArgc("exclude_end?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Bool(self.(*object.Range).Exclusive) }) // include?/member? test discrete membership (MRI's range_include_internal: @@ -5667,8 +5667,8 @@ func (vm *VM) bootstrap() { rangeArgCheck(args) return object.Bool(vm.rangeInclude(self.(*object.Range), args[0])) } - vm.cRange.define("include?", rangeIncludeFn) - vm.cRange.define("cover?", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cRange.defineArgc("include?", 1, rangeIncludeFn) + vm.cRange.defineArgc("cover?", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { rangeArgCheck(args) r := self.(*object.Range) if o, ok := args[0].(*object.Range); ok { @@ -5686,7 +5686,7 @@ func (vm *VM) bootstrap() { // bounds. It enforces arity 2..3 (ArgumentError otherwise) and the same // comparable-endpoints check as Range.new (a nil #<=> — e.g. two plain // Objects — is an ArgumentError "bad value for range"). - vm.cRange.define("initialize", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cRange.defineArgc("initialize", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { if _, raw := self.(*object.Range); raw || isFrozen(self) { vm.raiseFrozen(self) } @@ -5698,19 +5698,19 @@ func (vm *VM) bootstrap() { return object.NilV }) vm.setInstanceVisibility(vm.cRange, "initialize", visPrivate) - vm.cRange.define("===", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cRange.defineArgc("===", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { rangeArgCheck(args) return object.Bool(vm.rangeCoverValueV(self.(*object.Range), args[0])) }) // Range#overlap?(other) reports whether the two ranges share any element. - vm.cRange.define("overlap?", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cRange.defineArgc("overlap?", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { o, ok := args[0].(*object.Range) if !ok { raise("TypeError", "wrong argument type %s (expected Range)", vm.classOf(args[0]).name) } return object.Bool(rangeOverlap(self.(*object.Range), o)) }) - vm.cRange.define("min", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cRange.defineArgc("min", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { r := self.(*object.Range) if len(args) > 0 { // min(n): the n smallest (the range is ascending) if object.IsNil(r.Lo) { @@ -5738,7 +5738,7 @@ func (vm *VM) bootstrap() { } return r.Lo }) - vm.cRange.define("max", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cRange.defineArgc("max", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { r := self.(*object.Range) if len(args) > 0 { // max(n): the n largest, descending if object.IsNil(r.Hi) { @@ -5796,10 +5796,10 @@ func (vm *VM) bootstrap() { rangeSizeFn := func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.IntValue(rangeSize(self.(*object.Range))) } - vm.cRange.define("size", func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cRange.defineArgc("size", 0, func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return vm.rangeSizeVal(self.(*object.Range)) }) - vm.cRange.define("count", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cRange.defineArgc("count", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { // Bare count is the range size; with a block or argument it counts // matching elements (Enumerable#count). An unbounded range (a nil begin // or end) holds infinitely many elements, so MRI's range_count returns @@ -5824,12 +5824,12 @@ func (vm *VM) bootstrap() { } return object.IntValue(n) }) - vm.cRange.define("to_a", func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cRange.defineArgc("to_a", 0, func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.NewArrayFromSlice(vm.rangeElemsV(self.(*object.Range))) }) // take(n) mirrors first(n) (it works on endless ranges); drop(n) needs the // full materialised range, so it is bounded only. - vm.cRange.define("take", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cRange.defineArgc("take", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { r := self.(*object.Range) n := int(intArg(args[0])) if n < 0 { @@ -5852,7 +5852,7 @@ func (vm *VM) bootstrap() { copy(out, elems[:n]) return object.NewArrayFromSlice(out) }) - vm.cRange.define("drop", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cRange.defineArgc("drop", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { elems := vm.rangeElemsV(self.(*object.Range)) n := int(intArg(args[0])) if n < 0 { @@ -5865,7 +5865,7 @@ func (vm *VM) bootstrap() { copy(out, elems[n:]) return object.NewArrayFromSlice(out) }) - vm.cRange.define("each", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cRange.defineArgc("each", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return enumFor(self, "each") } @@ -5899,7 +5899,7 @@ func (vm *VM) bootstrap() { } return r }) - vm.cRange.define("map", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cRange.defineArgc("map", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return enumFor(self, "map") } @@ -5910,7 +5910,7 @@ func (vm *VM) bootstrap() { } return object.NewArrayFromSlice(out) }) - vm.cRange.define("step", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cRange.defineArgc("step", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { step := object.Value(object.IntValue(1)) if len(args) > 0 { step = args[0] @@ -6008,7 +6008,7 @@ func (vm *VM) bootstrap() { // (no-block) ArithmeticSequence machinery — the only difference is that the // sequence remembers it was built by #% so its #inspect reads // "((1..10).%(2))" rather than "((1..10).step(2))". - vm.cRange.define("%", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cRange.defineArgc("%", 1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { res := vm.send(self, "step", args, blk) if e, ok := res.(*Enumerator); ok && e.isArithSeq { e.asMethod = "%" @@ -6026,43 +6026,43 @@ func (vm *VM) bootstrap() { raise("RangeError", "bignum too big to convert into `long'") return 0 } - vm.cInteger.define("abs", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("abs", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.NormInt(new(big.Int).Abs(bigVal(self))) }) - vm.cInteger.define("even?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("even?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Bool(bigVal(self).Bit(0) == 0) }) - vm.cInteger.define("odd?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("odd?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Bool(bigVal(self).Bit(0) == 1) }) - vm.cInteger.define("zero?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("zero?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Bool(bigVal(self).Sign() == 0) }) - vm.cInteger.define("positive?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("positive?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Bool(bigVal(self).Sign() > 0) }) - vm.cInteger.define("negative?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("negative?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Bool(bigVal(self).Sign() < 0) }) intSucc := func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.NormInt(new(big.Int).Add(bigVal(self), big.NewInt(1))) } - vm.cInteger.define("succ", intSucc) - vm.cInteger.define("next", intSucc) - vm.cInteger.define("pred", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("succ", 0, intSucc) + vm.cInteger.defineArgc("next", 0, intSucc) + vm.cInteger.defineArgc("pred", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.NormInt(new(big.Int).Sub(bigVal(self), big.NewInt(1))) }) - vm.cInteger.define("to_i", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("to_i", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return self }) - vm.cInteger.define("to_int", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("to_int", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return self }) - vm.cInteger.define("to_f", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("to_f", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { f, _ := toFloat(self) return object.Float(f) }) - vm.cInteger.define("to_s", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("to_s", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { base := int64(10) if len(args) > 0 { base = intArg(args[0]) @@ -6078,29 +6078,29 @@ func (vm *VM) bootstrap() { }) // Bitwise / shift operators (arbitrary precision via big.Int, so a left shift // promotes to a Bignum and bitwise ops work on Bignums too). - vm.cInteger.define("<<", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("<<", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.integerShift(bigVal(self), args[0], false) }) - vm.cInteger.define(">>", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc(">>", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.integerShift(bigVal(self), args[0], true) }) - vm.cInteger.define("&", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("&", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.integerBitOp(bigVal(self), args[0], "&", (*big.Int).And) }) - vm.cInteger.define("|", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("|", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.integerBitOp(bigVal(self), args[0], "|", (*big.Int).Or) }) - vm.cInteger.define("^", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("^", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.integerBitOp(bigVal(self), args[0], "^", (*big.Int).Xor) }) - vm.cInteger.define("~", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("~", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.NormInt(new(big.Int).Not(bigVal(self))) }) - vm.cInteger.define("gcd", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("gcd", 1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a, _ := object.BigOf(self) return object.NormInt(bigGCD(a, integerGcdArg(args[0]))) }) - vm.cInteger.define("lcm", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("lcm", 1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a, _ := object.BigOf(self) return object.NormInt(bigLCM(a, integerGcdArg(args[0]))) }) @@ -6124,21 +6124,21 @@ func (vm *VM) bootstrap() { }) // Integer#bit_length is defined in registerNumericEdges (numeric_edges.go), // which handles Bignums; the fixnum-only version that was here is superseded. - vm.cInteger.define("divmod", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("divmod", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.integerDivmod(self, args[0]) }) - vm.cInteger.define("gcdlcm", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("gcdlcm", 1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a, _ := object.BigOf(self) b := integerGcdArg(args[0]) return object.NewArray(object.NormInt(bigGCD(a, b)), object.NormInt(bigLCM(a, b))) }) - vm.cInteger.define("remainder", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("remainder", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.integerRemainder(self, args[0]) }) // truncate with ndigits >= 0 leaves an Integer unchanged; with ndigits < 0 it // truncates toward zero to the nearest 10**(-ndigits). Integer#round is defined // (with the half: keyword) in registerNumericEdges, which runs later. - vm.cInteger.define("truncate", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("truncate", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { n := intArgOr(args, 0) if n >= 0 { return self @@ -6154,7 +6154,7 @@ func (vm *VM) bootstrap() { } return object.IntValue(r) }) - vm.cInteger.define("floor", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("floor", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { // floor(n>=0) is self; floor(n<0) rounds toward negative infinity to the // nearest multiple of 10**(-n) — exactly, see intFloorPrecision. n := intArgOr(args, 0) @@ -6163,7 +6163,7 @@ func (vm *VM) bootstrap() { } return intFloorPrecision(bigVal(self), -n) }) - vm.cInteger.define("ceil", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("ceil", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { // ceil(n>=0) is self; ceil(n<0) rounds toward positive infinity. n := intArgOr(args, 0) if n >= 0 { @@ -6171,7 +6171,7 @@ func (vm *VM) bootstrap() { } return intCeilPrecision(bigVal(self), -n) }) - vm.cInteger.define("digits", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("digits", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { n := intOf(self) base := int64(10) if len(args) > 0 { @@ -6193,7 +6193,7 @@ func (vm *VM) bootstrap() { } return object.NewArrayFromSlice(out) }) - vm.cInteger.define("chr", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("chr", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { if _, big := self.(*object.Bignum); big { raise("RangeError", "bignum out of char range") } @@ -6212,7 +6212,7 @@ func (vm *VM) bootstrap() { enc := vm.encodingName(args[0]) return object.NewStringBytesEnc(chrEncode(n, enc), enc) }) - vm.cInteger.define("upto", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cInteger.defineArgc("upto", 1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { if len(args) < 1 { raise("ArgumentError", "wrong number of arguments (given 0, expected 1)") } @@ -6224,7 +6224,7 @@ func (vm *VM) bootstrap() { } return self }) - vm.cInteger.define("downto", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cInteger.defineArgc("downto", 1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { if len(args) < 1 { raise("ArgumentError", "wrong number of arguments (given 0, expected 1)") } @@ -6239,25 +6239,25 @@ func (vm *VM) bootstrap() { // Float methods. floatOf := func(self object.Value) float64 { return float64(self.(object.Float)) } - vm.cFloat.define("abs", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("abs", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Float(math.Abs(floatOf(self))) }) - vm.cFloat.define("zero?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("zero?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Bool(floatOf(self) == 0) }) - vm.cFloat.define("positive?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("positive?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Bool(floatOf(self) > 0) }) - vm.cFloat.define("negative?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("negative?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Bool(floatOf(self) < 0) }) - vm.cFloat.define("to_f", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("to_f", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return self }) - vm.cFloat.define("to_i", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("to_i", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return floatToInt(floatOf(self)) }) - vm.cFloat.define("to_int", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("to_int", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return floatToInt(floatOf(self)) }) // numeric.c v3_4_0 rb_float_ceil / rb_float_floor round the double to an @@ -6266,14 +6266,14 @@ func (vm *VM) bootstrap() { // if (ndigits < 0) num = rb_int_ceil(num, ndigits); // so a large negative ndigits stays exact. Scaling the double by 10**ndigits // instead gave 123.0.ceil(-50) as 1.00000000000000007629...e+50. - vm.cFloat.define("ceil", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("ceil", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { if n := intArgOr(args, 0); n < 0 { iv, _ := object.BigOf(floatToInt(math.Ceil(floatOf(self)))) return intCeilPrecision(iv, -n) } return floatRound(floatOf(self), args, math.Ceil) }) - vm.cFloat.define("floor", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("floor", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { if n := intArgOr(args, 0); n < 0 { iv, _ := object.BigOf(floatToInt(math.Floor(floatOf(self)))) return intFloorPrecision(iv, -n) @@ -6284,11 +6284,11 @@ func (vm *VM) bootstrap() { // which runs later and overrides any definition here. // Float#divmod is defined in registerNumericEdges (numeric_edges.go), which // supersedes this version. - vm.cFloat.define("truncate", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("truncate", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { // Truncate toward zero. ndigits > 0 keeps a Float; otherwise an Integer. return floatRound(floatOf(self), args, math.Trunc) }) - vm.cFloat.define("to_r", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("to_r", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { f := floatOf(self) r := new(big.Rat).SetFloat64(f) if r == nil { // NaN or ±Infinity has no rational value @@ -6302,7 +6302,7 @@ func (vm *VM) bootstrap() { } return &object.Rational{R: r} }) - vm.cFloat.define("rationalize", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("rationalize", -1, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { f := floatOf(self) if math.IsNaN(f) || math.IsInf(f, 0) { msg := "NaN" @@ -6320,16 +6320,16 @@ func (vm *VM) bootstrap() { intToR := func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return &object.Rational{R: new(big.Rat).SetInt(bigVal(self))} } - vm.cInteger.define("to_r", intToR) - vm.cInteger.define("rationalize", intToR) - vm.cFloat.define("nan?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("to_r", 0, intToR) + vm.cInteger.defineArgc("rationalize", -1, intToR) + vm.cFloat.defineArgc("nan?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Bool(math.IsNaN(floatOf(self))) }) - vm.cFloat.define("finite?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("finite?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { f := floatOf(self) return object.Bool(!math.IsInf(f, 0) && !math.IsNaN(f)) }) - vm.cFloat.define("infinite?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("infinite?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { f := floatOf(self) if math.IsInf(f, 1) { return object.IntValue(1) @@ -6448,7 +6448,7 @@ func (vm *VM) bootstrap() { return out }) - vm.cInteger.define("step", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cInteger.defineArgc("step", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { limit, step := vm.stepBounds(args) if blk == nil { return vm.stepEnum(self, self, limit, step, false, args...) @@ -6463,7 +6463,7 @@ func (vm *VM) bootstrap() { }) // Integer#times — the first block-driven iterator. - vm.cInteger.define("times", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cInteger.defineArgc("times", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { if blk == nil { return enumFor(self, "times") } diff --git a/internal/vm/complex.go b/internal/vm/complex.go index 5883a890..12a43983 100644 --- a/internal/vm/complex.go +++ b/internal/vm/complex.go @@ -601,9 +601,9 @@ func (vm *VM) registerComplex() { realToC := func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return &object.Complex{Re: self, Im: object.IntValue(0)} } - vm.cInteger.define("to_c", realToC) - vm.cFloat.define("to_c", realToC) - vm.cString.define("to_c", func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("to_c", 0, realToC) + vm.cFloat.defineArgc("to_c", 0, realToC) + vm.cString.defineArgc("to_c", 0, func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { vm.mustASCIICompat(self) // rb_str_to_c's first act c, _ := stringToC(self.(*object.String).Str(), false) return c diff --git a/internal/vm/deep_merge.go b/internal/vm/deep_merge.go index f965dc1a..803781f2 100644 --- a/internal/vm/deep_merge.go +++ b/internal/vm/deep_merge.go @@ -43,7 +43,7 @@ func (vm *VM) registerDeepMerge() { }} // Hash#deep_merge!(source, opts={}) — the receiver is dest, mutated in place. - vm.cHash.define("deep_merge!", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cHash.defineArgc("deep_merge!", 1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return deepMergeHashBang(self, args) }) } diff --git a/internal/vm/encoding.go b/internal/vm/encoding.go index 21b5f5af..09eb0f7f 100644 --- a/internal/vm/encoding.go +++ b/internal/vm/encoding.go @@ -390,28 +390,28 @@ func asciiOnly(b []byte) bool { // registerStringEncoding adds the encoding-aware String methods. (Called from the // String setup so it shares cString.) func (vm *VM) registerStringEncoding() { - vm.cString.define("encoding", func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("encoding", 0, func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return vm.internEncoding(self.(*object.String).EncName()) }) - vm.cString.define("force_encoding", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("force_encoding", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { s := self.(*object.String) vm.checkFrozen(s) s.Enc = vm.forceEncodingName(args[0]) return s }) - vm.cString.define("b", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("b", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { d := self.(*object.String).Dup() d.Enc = "ASCII-8BIT" return d }) - vm.cString.define("ascii_only?", func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("ascii_only?", 0, func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { // #ascii_only? is ENC_CODERANGE_7BIT: content in a non-ASCII-compatible // encoding (UTF-16/32, …) is never 7-bit, even when empty or all-ASCII in // bytes, so it reports false regardless of the bytes. s := self.(*object.String) return object.Bool(vm.internEncoding(s.EncName()).asciiCompat && asciiOnly(s.Bytes())) }) - vm.cString.define("valid_encoding?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("valid_encoding?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { s := self.(*object.String) return object.Bool(validInEncoding(s.Bytes(), s.EncName())) }) diff --git a/internal/vm/lazy.go b/internal/vm/lazy.go index c3592ed6..98d44b3a 100644 --- a/internal/vm/lazy.go +++ b/internal/vm/lazy.go @@ -133,9 +133,9 @@ func (vm *VM) registerLazy() { } // Sources that lazy iteration can drive directly. (#lazy on a Lazy is itself, // defined separately below.) - vm.cArray.define("lazy", makeLazy) - vm.cRange.define("lazy", makeLazy) - vm.cHash.define("lazy", makeLazy) + vm.cArray.defineArgc("lazy", 0, makeLazy) + vm.cRange.defineArgc("lazy", 0, makeLazy) + vm.cHash.defineArgc("lazy", 0, makeLazy) vm.cEnumerator.define("lazy", makeLazy) // Every Enumerable (a class that mixes in Enumerable and defines #each) gets // #lazy too; the source is driven through #each (see lazySource's default). diff --git a/internal/vm/method.go b/internal/vm/method.go index 00a3ee13..c81c38f0 100644 --- a/internal/vm/method.go +++ b/internal/vm/method.go @@ -124,7 +124,14 @@ func (vm *VM) resolveMethod(recv object.Value, name string) *Method { // when it is variadic — i.e. it has an optional positional or a splat, or it // accepts extra keywords (an optional keyword or a **rest) without demanding any // required keyword. A required keyword keeps the arity non-negative even -// alongside optional keywords or a **rest. Native methods report -1 (variadic). +// alongside optional keywords or a **rest. +// +// A NATIVE method has no ISeq to read, so its arity comes from the argument +// count its registration site declared (Method.argc, set by defineArgc) — MRI's +// cfunc branch of method_def_min_max_arity. A site that declared nothing still +// reports -1; that is the pre-#714 answer for every native, and it is a value +// carrying no information rather than a measurement, which is why +// TestNativeArityDeclaredForCoreClasses refuses an undeclared core-class native. func methodArity(m *Method) int { switch m.attrKind { case attrReaderMethod: @@ -137,7 +144,7 @@ func methodArity(m *Method) int { if m.proc != nil { return m.proc.arityVal() } - return -1 + return m.argc.nativeArity() } reqKw, optKw := keywordCounts(is) req := iseqRequiredPositional(is) diff --git a/internal/vm/method_reflect2.go b/internal/vm/method_reflect2.go index 154fe117..d3b786e3 100644 --- a/internal/vm/method_reflect2.go +++ b/internal/vm/method_reflect2.go @@ -215,9 +215,38 @@ func methodParameters(m *Method) object.Value { case attrWriterMethod: return object.NewArray(object.NewArray(object.Symbol("req"))) } + if is := methodISeq(m); is == nil { + // A native: MRI has no parameter names for a cfunc either, and answers + // rb_unnamed_parameters(arity) — the arity being the declared argc + // (method_def_parameters falls through to it for VM_METHOD_TYPE_CFUNC, + // proc.c r4:3237-3277). So #parameters is a RENDERING of #arity here, not a + // second opinion about the method, and the two cannot disagree. + return unnamedParameters(methodArity(m)) + } return object.NewArray(buildParamsList(methodISeq(m), "req")...) } +// unnamedParameters builds the parameter list MRI reports for a callable with no +// names to report — a cfunc method or a Go-backed Proc: exactly +// rb_unnamed_parameters (proc.c r4:1556-1572). A non-negative arity gives that +// many bare [:req] entries; a negative one gives ~arity (= -arity-1) of them +// followed by a single [:rest]. So 0 is [], 1 is [[:req]], -1 is [[:rest]] — the +// answer every native used to give — and -2 is [[:req], [:rest]]. +func unnamedParameters(arity int) object.Value { + n := arity + if arity < 0 { + n = ^arity + } + out := make([]object.Value, 0, n+1) + for i := 0; i < n; i++ { + out = append(out, object.NewArray(object.Symbol("req"))) + } + if arity < 0 { + out = append(out, object.NewArray(object.Symbol("rest"))) + } + return object.NewArray(out...) +} + // buildParamsList builds the MRI #parameters descriptor list shared by // Method#parameters and Proc#parameters. reqKind selects the symbol used for a // required positional (:req for a method or a lambda, :opt for a non-lambda @@ -384,7 +413,12 @@ func lookupOwnClassMethod(c *RClass, name string) *Method { func formatParamList(m *Method) string { is := methodISeq(m) if is == nil { - return "(*)" + // MRI builds this fragment by WALKING rb_method_parameters(method) + // (method_inspect, proc.c r4:3418-3470), rendering a nameless [:req] as "_" + // and a nameless [:rest] as "*". For a native that list is + // unnamedParameters(arity), so the signature follows the declared argc for + // free: arity 0 renders "()", 1 "(_)", 2 "(_, _)" and -1 "(*)". + return formatUnnamedParams(methodArity(m)) } var toks []string for i, name := range is.Params { @@ -438,3 +472,22 @@ func joinComma(toks []string) string { } return out } + +// formatUnnamedParams renders the # signature fragment for a callable +// whose parameters are unnamed, matching what MRI's method_inspect prints when it +// walks an rb_unnamed_parameters list: a nameless :req becomes "_" and a nameless +// :rest becomes the bare "*" (proc.c r4:3449-3460). +func formatUnnamedParams(arity int) string { + n := arity + if arity < 0 { + n = ^arity + } + toks := make([]string, 0, n+1) + for i := 0; i < n; i++ { + toks = append(toks, "_") + } + if arity < 0 { + toks = append(toks, "*") + } + return "(" + joinComma(toks) + ")" +} diff --git a/internal/vm/numeric_edges.go b/internal/vm/numeric_edges.go index 2f3578e4..5e0e5099 100644 --- a/internal/vm/numeric_edges.go +++ b/internal/vm/numeric_edges.go @@ -428,7 +428,7 @@ func (vm *VM) registerNumericEdges() { floatOf := func(self object.Value) float64 { return float64(self.(object.Float)) } // Integer#[] — bit reference, with two-arg (start, len) and Range forms. - vm.cInteger.define("[]", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("[]", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { n := bigVal(self) if len(args) == 1 { if rng, ok := args[0].(*object.Range); ok { @@ -444,7 +444,7 @@ func (vm *VM) registerNumericEdges() { // Integer#size — bytes in the machine representation (8 for a fixnum-range // value; the exact byte count for a Bignum). - vm.cInteger.define("size", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("size", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { if _, ok := self.(object.Integer); ok { return object.IntValue(8) } @@ -453,17 +453,17 @@ func (vm *VM) registerNumericEdges() { }) // Integer#ord — an integer is its own codepoint. - vm.cInteger.define("ord", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("ord", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return self }) // Integer#integer? — always true (overrides Numeric#integer?). - vm.cInteger.define("integer?", func(_ *VM, _ object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("integer?", 0, func(_ *VM, _ object.Value, _ []object.Value, _ *Proc) object.Value { return object.True }) // Integer#round — MRI-faithful, honouring negative ndigits and `half:`. - vm.cInteger.define("round", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("round", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { ndigits, mode := vm.roundNdigits(args) if ndigits >= 0 { return self @@ -472,15 +472,15 @@ func (vm *VM) registerNumericEdges() { }) // Float#next_float / #prev_float — the adjacent representable doubles. - vm.cFloat.define("next_float", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("next_float", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Float(math.Nextafter(floatOf(self), math.Inf(1))) }) - vm.cFloat.define("prev_float", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("prev_float", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Float(math.Nextafter(floatOf(self), math.Inf(-1))) }) // Float#round — MRI-faithful, honouring `half:` and the ndigits contract. - vm.cFloat.define("round", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("round", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { ndigits, mode := vm.roundNdigits(args) return floRound(floatOf(self), ndigits, mode) }) @@ -493,15 +493,15 @@ func (vm *VM) registerNumericEdges() { mask = vm.arefToInt(args[0]) return new(big.Int).And(bigVal(self), mask), mask } - vm.cInteger.define("allbits?", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("allbits?", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { and, mask := andMask(vm, self, args) return object.Bool(and.Cmp(mask) == 0) }) - vm.cInteger.define("anybits?", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("anybits?", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { and, _ := andMask(vm, self, args) return object.Bool(and.Sign() != 0) }) - vm.cInteger.define("nobits?", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("nobits?", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { and, _ := andMask(vm, self, args) return object.Bool(and.Sign() == 0) }) @@ -509,7 +509,7 @@ func (vm *VM) registerNumericEdges() { // Integer#ceildiv (Ruby 3.2+): the quotient rounded toward +Infinity, defined // as -((-self).div(other)). Delegating to #div inherits its floor semantics, // its numeric coercion of Float/Rational operands, and its ZeroDivisionError. - vm.cInteger.define("ceildiv", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("ceildiv", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { neg := object.NormInt(new(big.Int).Neg(bigVal(self))) q := vm.send(neg, "div", []object.Value{args[0]}, nil) return object.NormInt(new(big.Int).Neg(bigVal(q))) @@ -517,7 +517,7 @@ func (vm *VM) registerNumericEdges() { // Integer#quo: an exact quotient. A Float operand yields a Float (like #fdiv); // an Integer/Rational operand yields a Rational. A zero divisor raises. - vm.cInteger.define("quo", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("quo", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { if f, ok := args[0].(object.Float); ok { sf, _ := toFloat(self) return object.Float(sf / float64(f)) @@ -537,7 +537,7 @@ func (vm *VM) registerNumericEdges() { // Float#remainder: self - other*(self/other).truncate — the remainder keeps the // dividend's sign (unlike #modulo/%). A zero divisor raises ZeroDivisionError. - vm.cFloat.define("remainder", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("remainder", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { b, ok := toFloat(args[0]) if !ok { raise("TypeError", "%s can't be coerced into Float", vm.classOf(args[0]).name) @@ -565,7 +565,7 @@ func (vm *VM) registerNumericEdges() { // Float#step — mirrors Integer#step: an enumerator without a block, otherwise // walking [self, limit] by step (default 1), yielding Floats via numericStep. - vm.cFloat.define("step", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cFloat.defineArgc("step", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { limit, step := vm.stepBounds(args) if blk == nil { return vm.stepEnum(self, self, limit, step, false, args...) @@ -611,7 +611,7 @@ func (vm *VM) registerNumericAliasesAndEdges() { // MRI coerces a non-Integer operand of Integer#coerce with rb_Float (parsing a // String, dispatching #to_f on an object, raising ArgumentError/TypeError like // Float()); an Integer/Bignum operand stays exact and returns [other, self]. - vm.cInteger.define("coerce", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("coerce", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { checkArgc(args, 1, "coerce") other := args[0] if _, ok := object.BigOf(other); ok { @@ -623,7 +623,7 @@ func (vm *VM) registerNumericAliasesAndEdges() { }) // Float#coerce converts the operand with Kernel#Float and returns [other, self] // (self is already a Float). - vm.cFloat.define("coerce", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("coerce", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { checkArgc(args, 1, "coerce") of := vm.send(vm.main, "Float", []object.Value{args[0]}, nil) return object.NewArray(of, self) @@ -633,7 +633,7 @@ func (vm *VM) registerNumericAliasesAndEdges() { // self.to_f / other.to_f overflows to Inf/Inf = NaN for operands beyond a // double's range; MRI divides exactly (rb_big_fdiv) so 1.fdiv(10**323) is // 1e-323 rather than 0.0 and (10**400).fdiv(10**402) is 0.01 rather than NaN. - vm.cInteger.define("fdiv", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("fdiv", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { checkArgc(args, 1, "fdiv") other := args[0] if f, ok := other.(object.Float); ok { @@ -663,7 +663,7 @@ func (vm *VM) registerNumericAliasesAndEdges() { // Float#fdiv is exactly one argument, then float division (a zero divisor // yields ±Infinity, never a ZeroDivisionError); a non-numeric divisor is // coerced, matching Numeric#coerce ("into Float"). - vm.cFloat.define("fdiv", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("fdiv", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { checkArgc(args, 1, "fdiv") b, ok := toFloat(args[0]) if !ok { @@ -680,7 +680,7 @@ func (vm *VM) registerNumericAliasesAndEdges() { // self.to_f/other but raises ZeroDivisionError on a 0.0 divisor (unlike // Float#/); a Rational divisor floors the exact quotient; anything else runs // the coerce protocol (other.coerce(self) then #div on the pair). - vm.cInteger.define("div", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("div", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { checkArgc(args, 1, "div") other := args[0] if b, ok := object.BigOf(other); ok { @@ -710,7 +710,7 @@ func (vm *VM) registerNumericAliasesAndEdges() { // the dividend, and the result takes the divisor's sign (so 4.2 % Infinity is // 4.2 and 4.2 % -Infinity is -Infinity). A zero divisor raises; a non-numeric // divisor is coerced (mock#coerce, then re-dispatch %). - vm.cFloat.define("%", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("%", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { checkArgc(args, 1, "%") b, ok := toFloat(args[0]) if !ok { @@ -725,7 +725,7 @@ func (vm *VM) registerNumericAliasesAndEdges() { // Float#divmod: an Integer quotient (floored) and a Float modulo. A NaN or // Infinite quotient has no Integer value, so MRI raises FloatDomainError; a // zero divisor raises ZeroDivisionError. - vm.cFloat.define("divmod", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cFloat.defineArgc("divmod", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { checkArgc(args, 1, "divmod") b, ok := toFloat(args[0]) if !ok { @@ -744,7 +744,7 @@ func (vm *VM) registerNumericAliasesAndEdges() { // long and raised RangeError for a Bignum; compute it over big.Int so a // Bignum answers (a negative number uses ~n, since -n-1 has the same magnitude // pattern as MRI's leftmost-0-bit count). - vm.cInteger.define("bit_length", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("bit_length", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { n := bigVal(self) if n.Sign() < 0 { n = new(big.Int).Not(n) // ~n = -n-1 @@ -809,6 +809,13 @@ func argcMessage(given, min, max int) string { // guardArgc re-wraps each named method of cls with a positional-argument-count // check ([min, max]; max < 0 means unbounded), preserving the original body. // A name the class does not define is skipped. +// +// It replaces only the BODY, keeping the original Method record's other fields. +// Building a fresh one with cls.define instead silently reset every one of them, +// and the declared argc was the field that made that visible: Integer#gcd, #lcm, +// #gcdlcm and #to_r each declared a count at their registration site and then +// answered #arity -1 anyway, because the guard had replaced the record that held +// it. nonRetaining, attrKind, vis and origName travelled the same path unseen. func guardArgc(cls *RClass, min, max int, names ...string) { for _, name := range names { m, ok := cls.methods[name] @@ -816,12 +823,21 @@ func guardArgc(cls *RClass, min, max int, names ...string) { continue } orig := m.native - cls.define(name, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + guarded := *m + guarded.native = func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { if len(args) < min || (max >= 0 && len(args) > max) { raise("ArgumentError", "%s", argcMessage(len(args), min, max)) } return orig(vm, self, args, blk) - }) + } + // A guard with equal bounds IS an arity declaration for a site that made + // none: the method provably accepts exactly min arguments, so #arity can + // say so rather than fall back to -1. + if !guarded.argc.declared && min == max { + guarded.argc = declareArgc(min) + } + cls.methods[name] = &guarded + bumpMethodSerial() } } diff --git a/internal/vm/object_model.go b/internal/vm/object_model.go index c4841bca..28928c9d 100644 --- a/internal/vm/object_model.go +++ b/internal/vm/object_model.go @@ -182,6 +182,29 @@ type Method struct { // instance_methods all treat the name as absent (a call routes to // method_missing → NoMethodError). undefined bool + // argc is the argument count the native method's registration site DECLARED, + // in MRI's rb_define_method convention (vm_method.c r4:877, which rejects + // anything outside -2..15): + // + // n >= 0 the method takes exactly n arguments + // -1 variadic: MRI's (int argc, VALUE *argv, VALUE self) shape + // + // MRI also accepts -2, its (VALUE self, VALUE args) shape. rbgo has no such + // shape — every native is one NativeFn signature, the analogue of MRI's -1 — + // so declareArgc rejects -2 rather than offering a value no site can honestly + // use. Nothing is lost from the reflection surface either way: MRI's + // method_def_min_max_arity gives EVERY argc < 0 cfunc min 0 and an unlimited + // max (proc.c r4:2942-2947), so a -2 cfunc reports arity -1 exactly as a -1 + // one does. + // + // The zero value is "the site declared nothing", which is NOT the same as a + // declared -1: it is what lets a test tell a site that has been given a + // truthful arity from one that has not. Nothing in the type system forces a + // site to declare — `define` still compiles — so the barrier is + // TestNativeArityDeclaredForCoreClasses, which enumerates the core classes' + // method tables and fails on any undeclared native. Only defineArgc / + // defineArgcNR set this, so argc and its declared flag cannot drift apart. + argc nativeArgc // attrKind marks a method synthesised by attr_reader/attr_writer/attr_accessor, // so #parameters reports MRI's [] (a reader) or [[:req]] (a writer) instead of // a native method's default [[:rest]]. The zero value means "not an attr". @@ -335,12 +358,75 @@ func (c *RClass) ToS() string { func (c *RClass) Inspect() string { return c.ToS() } func (c *RClass) Truthy() bool { return true } -// define installs a native method on the class. +// nativeArgc is a native method's declared argument count. Its zero value means +// "undeclared", which is distinguishable from a declared -1 — the distinction +// the whole of #arity / #parameters / #inspect for natives rests on. It is a +// struct rather than a bare int so the count and the fact that a count was given +// are set together, by declareArgc, and can never disagree. +type nativeArgc struct { + n int8 + declared bool +} + +// declareArgc validates an argc and packs it. The upper bound is MRI's, which +// raises "arity out of range: %d for -2..15" (vm_method.c r4:877); the lower +// bound is -1 rather than -2 because rbgo has only MRI's -1 shape (see +// nativeArgc). An out-of-range argc is a mistake at a registration site, not a +// Ruby-level condition, so it panics rather than raising. +func declareArgc(argc int) nativeArgc { + if argc < -1 || argc > 15 { + panic(fmt.Sprintf("arity out of range: %d for -1..15", argc)) + } + return nativeArgc{n: int8(argc), declared: true} +} + +// nativeArity is the arity a native method's declared argc reports, mirroring +// MRI's method_def_min_max_arity for VM_METHOD_TYPE_CFUNC followed by +// method_def_arity (proc.c r4:2940-3004): a non-negative argc is both the +// minimum and the maximum, so the arity is that count; ANY negative argc — -1 or +// -2 — gives min 0 and an unlimited max, so the arity is -1. An undeclared site +// also reads -1, which is what made this value carry no information before it +// could be declared. +func (a nativeArgc) nativeArity() int { + if !a.declared || a.n < 0 { + return -1 + } + return int(a.n) +} + +// define installs a native method on the class WITHOUT declaring its argument +// count, so #arity reports -1 and #parameters [[:rest]]. That is the truthful +// answer for a genuinely variadic body and a placeholder for every other one; +// prefer defineArgc, which says which it is. func (c *RClass) define(name string, fn NativeFn) { c.methods[name] = &Method{name: name, native: fn, owner: c} bumpMethodSerial() } +// defineArgc is define with the argument count declared, MRI's +// rb_define_method(klass, name, func, argc). argc is a positional parameter, so +// a site that uses this entry point cannot omit it; a site that wants to omit it +// has to fall back to define, which a core-class completeness test then reports. +// The declared count reaches Ruby through #arity, #parameters and #inspect, all +// three derived from this one field (see methodArity), so they cannot disagree. +// +// It does not ENFORCE the count: rbgo's natives check their own arguments, and +// making the registration reject a call would change dispatch behaviour rather +// than the reflection surface this records. A declaration is therefore a claim +// about the method, pinned against MRI by the golden table in +// method_arity_conformance_test.go rather than by the call path. +func (c *RClass) defineArgc(name string, argc int, fn NativeFn) { + c.define(name, fn) + c.methods[name].argc = declareArgc(argc) +} + +// defineArgcNR is defineArgc for a native whose body has been audited not to +// retain its args slice (see Method.nonRetaining and defineNR). +func (c *RClass) defineArgcNR(name string, argc int, fn NativeFn) { + c.defineNR(name, fn) + c.methods[name].argc = declareArgc(argc) +} + // defineNR is define for a native whose body has been audited not to retain its // args slice (see Method.nonRetaining). Only the OpSend fast path (invokeInPlace) // reads the flag, to elide its defensive per-call args copy; every other caller diff --git a/internal/vm/prime_bind.go b/internal/vm/prime_bind.go index 23a13d98..86787ecc 100644 --- a/internal/vm/prime_bind.go +++ b/internal/vm/prime_bind.go @@ -100,10 +100,10 @@ func (vm *VM) installPrime() { // Integer#prime? and Integer#prime_division — the core extensions `require // "prime"` adds. They delegate to the same library functions on the receiver. - vm.cInteger.define("prime?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("prime?", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.Bool(prime.IsPrime(bigVal(self))) }) - vm.cInteger.define("prime_division", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cInteger.defineArgc("prime_division", -1, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return primeDivision(bigVal(self)) }) } diff --git a/internal/vm/proc.go b/internal/vm/proc.go index 53529fc8..c6fc896a 100644 --- a/internal/vm/proc.go +++ b/internal/vm/proc.go @@ -29,6 +29,15 @@ func (vm *VM) registerProcMethods() { asLambda = v.Truthy() } } + if p.iseq == nil { + // A Go-backed Proc — notably Method#to_proc, whose nativeArity is the + // method's arity. MRI answers rb_unnamed_parameters(rb_proc_arity(self)) + // here (rb_proc_parameters, proc.c r4:1617), so `method(:length).to_proc` + // reports arity 0 AND parameters []. Reading the arity keeps the two + // sides of one Proc agreeing once a native method can declare a count; a + // flat [[:rest]] would have contradicted the arity for every one of them. + return unnamedParameters(p.arityVal()) + } return procParameters(p.iseq, asLambda) }) diff --git a/internal/vm/range_edges.go b/internal/vm/range_edges.go index 4499e1e1..f53f1840 100644 --- a/internal/vm/range_edges.go +++ b/internal/vm/range_edges.go @@ -19,7 +19,7 @@ import ( func (vm *VM) registerRangeEdges() { // bsearch finds an element by binary search over the range's numeric domain // (see rangeBsearch); without a block it returns an Enumerator. - vm.cRange.define("bsearch", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cRange.defineArgc("bsearch", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { r := self.(*object.Range) // A non-numeric bound cannot be searched — MRI reports this even without a // block, before returning the Enumerator. @@ -44,7 +44,7 @@ func (vm *VM) registerRangeEdges() { // iterate from " for a begin that cannot be enumerated — then walked // backwards. Without a block it returns an Enumerator whose #size is the // Range#size. - vm.cRange.define("reverse_each", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { + vm.cRange.defineArgc("reverse_each", 0, func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { r := self.(*object.Range) if blk == nil { return enumForSized(self, "reverse_each", func(vm *VM) object.Value { return vm.rangeReverseSizeVal(r) }) diff --git a/internal/vm/rational.go b/internal/vm/rational.go index 83e2d254..e87e8e31 100644 --- a/internal/vm/rational.go +++ b/internal/vm/rational.go @@ -428,7 +428,7 @@ func (vm *VM) registerRational() { // String#to_r parses a rational literal leniently (unrecognised input yields // Rational(0, 1)); a zero denominator ("1/0") raises ZeroDivisionError. - vm.cString.define("to_r", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("to_r", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { r, _ := stringToR(self.(*object.String).Str(), false) return r }) diff --git a/internal/vm/scanf.go b/internal/vm/scanf.go index 27b1fe78..b5a6cd8b 100644 --- a/internal/vm/scanf.go +++ b/internal/vm/scanf.go @@ -50,7 +50,7 @@ func (vm *VM) registerScanf() { // buffered input. // - Kernel#scanf(format) — IO#scanf on $stdin (the top-level form). func (vm *VM) installScanf() { - vm.cString.define("scanf", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { + vm.cString.defineArgc("scanf", 1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { return vm.doScanf(strArg(self), strArg(args[0]), blk) }) diff --git a/internal/vm/shellwords.go b/internal/vm/shellwords.go index 89ea3204..24fdeaa0 100644 --- a/internal/vm/shellwords.go +++ b/internal/vm/shellwords.go @@ -80,13 +80,13 @@ func (vm *VM) installShellwords() { // Core extensions added on require, MRI-style: String#shellsplit / // #shellescape and Array#shelljoin operate on the receiver. - vm.cString.define("shellsplit", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("shellsplit", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return shellSplit(strArg(self)) }) - vm.cString.define("shellescape", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("shellescape", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.NewString(libsw.Escape(strArg(self))) }) - vm.cArray.define("shelljoin", func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { + vm.cArray.defineArgc("shelljoin", 0, func(vm *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.NewString(libsw.Join(vm.shellWords(self))) }) } diff --git a/internal/vm/string_encode.go b/internal/vm/string_encode.go index b3b5245b..84e51599 100644 --- a/internal/vm/string_encode.go +++ b/internal/vm/string_encode.go @@ -624,10 +624,10 @@ func (vm *VM) stringEncode(s *object.String, args []object.Value) *object.String // registerStringEncodeMethods wires String#encode / #encode! and the // Encoding::*Error classes. Called from registerStringEncoding. func (vm *VM) registerStringEncodeMethods() { - vm.cString.define("encode", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("encode", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { return vm.stringEncode(self.(*object.String), args) }) - vm.cString.define("encode!", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("encode!", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { s := self.(*object.String) // #encode! mutates in place, so a frozen receiver is a FrozenError — even for // a no-op transcoding (utf-8 to utf-8), which MRI still refuses. diff --git a/internal/vm/unicode_normalize.go b/internal/vm/unicode_normalize.go index 66358e2b..4e452eb8 100644 --- a/internal/vm/unicode_normalize.go +++ b/internal/vm/unicode_normalize.go @@ -118,7 +118,7 @@ func (vm *VM) normResult(out, back string) *object.String { // registerStringUnicodeNormalize adds the unicode_normalize core-ext String // methods. (Called from the String setup so it shares cString.) func (vm *VM) registerStringUnicodeNormalize() { - vm.cString.define("unicode_normalize", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("unicode_normalize", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { form := normForm(args) src, back, done := vm.normTarget(self) if done { @@ -126,7 +126,7 @@ func (vm *VM) registerStringUnicodeNormalize() { } return vm.normResult(norm.Normalize(src, form), back) }) - vm.cString.define("unicode_normalized?", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("unicode_normalized?", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { form := normForm(args) src, _, done := vm.normTarget(self) if done { @@ -137,7 +137,7 @@ func (vm *VM) registerStringUnicodeNormalize() { // unicode_normalize! normalizes the receiver in place and returns self (even // when already normalized). The form argument is validated before the frozen // check, matching MRI. - vm.cString.define("unicode_normalize!", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { + vm.cString.defineArgc("unicode_normalize!", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { form := normForm(args) s := self.(*object.String) src, back, done := vm.normTarget(self) From 3ca2baaa0fea8ffc8bad50f551fc8fa2e101e186 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Sun, 27 Sep 2026 16:39:54 +0200 Subject: [PATCH 2/5] Refuse an undeclared core-class native arity, and pin every count to the oracle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four tests, in the order they close the hole: - TestNativeArityDeclaredForCoreClasses is the barrier. Nothing in Go's type system can force a registration site to declare an argc — define still compiles, at roughly three thousand sites — so what stops a core-class site from omitting one is this: an undeclared argc is DISTINGUISHABLE from a declared -1, and this enumerates the seven classes' tables and names every native that has not declared. 471 natives, 10 undeclared, and the exempt set is asserted in BOTH directions so neither a new omission nor a stale exemption passes. A population floor stops it being satisfied by emptying the tables. - TestNativeArityMatchesMRIOracle pins 461 declared counts against testdata/native_arity_oracle.tsv, measured with ruby 4.0.5 (and activesupport 8.1.3 / the stdlib requires for the shims rbgo boots with), so an expectation cannot drift into agreeing with rbgo and disagreeing with MRI. The oracle load asserts its own integrity: no empty key, no duplicate, a row floor, and more than one arity verdict. - TestNativeAccessorsCannotDisagree asserts the property over the whole table: #parameters and the #inspect signature are renderings of #arity, so no method can be found where two of the three disagree. It also refuses to pass on a table with fewer than four distinct arities — before #714 the table answered -1 for 98% of itself, and the property held there trivially. - TestNativeArityThroughEveryAccessor checks the Ruby-level surfaces agree: instance_method, method, public_method, unbind, bind, singleton_method and Method#to_proc, plus the optional-argument shapes where -1 is correct (String#sub, Array#first, Hash#fetch) and :upcase.to_proc's arity -2. Co-Authored-By: Claude Opus 5 --- internal/vm/method_arity_native_test.go | 373 ++++++++++++++ internal/vm/testdata/native_arity_oracle.tsv | 501 +++++++++++++++++++ 2 files changed, 874 insertions(+) create mode 100644 internal/vm/method_arity_native_test.go create mode 100644 internal/vm/testdata/native_arity_oracle.tsv diff --git a/internal/vm/method_arity_native_test.go b/internal/vm/method_arity_native_test.go new file mode 100644 index 00000000..72e8d0ee --- /dev/null +++ b/internal/vm/method_arity_native_test.go @@ -0,0 +1,373 @@ +// Copyright (c) the go-embedded-ruby/ruby authors +// +// SPDX-License-Identifier: BSD-3-Clause + +package vm + +import ( + "bufio" + "fmt" + "os" + "sort" + "strconv" + "strings" + "testing" + + "github.com/go-embedded-ruby/ruby/internal/object" +) + +// coreArityClasses names the population every count in this file is taken over: +// the classes whose native instance methods #714 measured. A class removed from +// this list would shrink every count silently, so coreNativeFloor puts a floor +// under the population itself. +func coreArityClasses(vm *VM) map[string]*RClass { + return map[string]*RClass{ + "String": vm.cString, "Array": vm.cArray, "Hash": vm.cHash, + "Integer": vm.cInteger, "Symbol": vm.cSymbol, "Float": vm.cFloat, + "Range": vm.cRange, + } +} + +// coreNativeFloor is a floor on how many native methods those seven classes +// carry at boot. It was 476 when this was written; the point of the floor is +// that a change which stops REGISTERING methods cannot make the completeness +// test below pass by emptying the tables it walks. +const coreNativeFloor = 460 + +// argcUndeclarableCoreMethods are the only core-class natives left without a +// declared argument count, and each is here because NO oracle states one: +// neither ruby 4.0.5, nor activesupport 8.1.3, nor the stdlib libraries rbgo's +// other shims came from define a method of that name on that class. Declaring a +// count for them would mean inventing one from rbgo's own body, which is how a +// wrong expectation gets written into a test that then agrees with the bug. +// +// Every other entry is a method some oracle answers, and the completeness test +// requires it to carry that oracle's count. The set is asserted to be EXACTLY +// this, in both directions: a new undeclared native fails, and so does an entry +// here that has since been declared. +var argcUndeclarableCoreMethods = map[string]bool{ + "Float#pow": false, // no Float#pow in ruby 4.0.5 (Integer#pow only) or activesupport + "String#to_html": false, // rbgo's own name; kramdown 2.5.2 defines no String#to_html + "String#to_kramdown_html": false, // likewise + "Array#page": false, // kaminari/pagy shim; neither gem defines Array#page + "String#truncate": true, // activesupport arity -2 (truncate_to, options = {}) + "Array#in_groups": true, // activesupport arity -2 (number, fill_with = nil, &block) + "Array#in_groups_of": true, // activesupport arity -2 + "Array#pack": true, // ruby arity -2, from + "String#unpack": true, // ruby arity -2, from + "String#unpack1": true, // ruby arity -2, from +} + +// isPlainNative reports whether m is a native method whose arity can only come +// from a declared argc: no ISeq, no Proc body, and not an attr_* accessor (both +// of which methodArity answers from their own shape). +func isPlainNative(m *Method) bool { + return m.native != nil && m.iseq == nil && m.proc == nil && m.attrKind == notAttr && !m.undefined +} + +// coreNatives lists the plain-native methods of the core classes as +// "Class#name", together with their Method records. +func coreNatives(vm *VM) map[string]*Method { + out := map[string]*Method{} + for cn, cls := range coreArityClasses(vm) { + for name, m := range cls.methods { + if isPlainNative(m) { + out[cn+"#"+name] = m + } + } + } + return out +} + +// TestNativeArityDeclaredForCoreClasses is the barrier the whole of #714 rests +// on. Nothing in Go's type system can force a registration site to declare an +// argument count — `define` still compiles, and roughly three thousand sites +// across the VM still use it — so what stops a core-class site from omitting one +// is this test: an undeclared native is DISTINGUISHABLE from a declared -1 +// (nativeArgc carries the fact of the declaration, not just the number), and +// this enumerates the tables and names every method that has not declared. +// +// Before #714 there was no such distinction: every native answered -1 and no +// test could tell a truthful -1 from a missing one, which is why 98% of the +// table went unnoticed for as long as it did. +func TestNativeArityDeclaredForCoreClasses(t *testing.T) { + vm := newTestVM() + natives := coreNatives(vm) + if len(natives) < coreNativeFloor { + t.Fatalf("only %d native core-class methods, below the %d floor: the population this test walks has shrunk, so its other counts mean nothing", len(natives), coreNativeFloor) + } + var undeclared []string + for key, m := range natives { + if !m.argc.declared { + undeclared = append(undeclared, key) + } + } + sort.Strings(undeclared) + got := map[string]bool{} + for _, k := range undeclared { + got[k] = true + if _, ok := argcUndeclarableCoreMethods[k]; !ok { + t.Errorf("%s registers a native with no declared argc: call defineArgc with the count a measured oracle gives (see testdata/native_arity_oracle.tsv), or add it to argcUndeclarableCoreMethods saying which oracle has no answer", k) + } + } + for k := range argcUndeclarableCoreMethods { + if !got[k] { + t.Errorf("%s is listed as having no declarable argc but now declares one (or is gone): remove the exemption", k) + } + } + t.Logf("population %d native core-class methods, %d undeclared", len(natives), len(undeclared)) +} + +type oracleRow struct { + arity int + params string + source string +} + +// loadArityOracle reads the measured oracle, asserting the sweep integrity the +// table itself needs: a non-empty key on every row, no duplicate key, a floor on +// the row count, and more than one arity verdict — a table that answered the same +// arity for every method would not be an oracle, it would be the defect. +func loadArityOracle(t *testing.T) map[string]oracleRow { + t.Helper() + f, err := os.Open("testdata/native_arity_oracle.tsv") + if err != nil { + t.Fatalf("oracle: %v", err) + } + defer f.Close() + out := map[string]oracleRow{} + verdicts := map[int]int{} + rows := 0 + sc := bufio.NewScanner(f) + for sc.Scan() { + line := sc.Text() + if line == "" || strings.HasPrefix(line, "#") { + continue + } + p := strings.Split(line, "\t") + if len(p) != 5 { + t.Fatalf("oracle row %d has %d columns, want 5: %q", rows+1, len(p), line) + } + if p[0] == "" || p[1] == "" { + t.Fatalf("oracle row %d has an empty key column", rows+1) + } + a, err := strconv.Atoi(p[2]) + if err != nil { + t.Fatalf("oracle row %d: arity %q: %v", rows+1, p[2], err) + } + key := p[0] + "#" + p[1] + if _, dup := out[key]; dup { + t.Fatalf("oracle names %s twice", key) + } + out[key] = oracleRow{a, p[3], p[4]} + verdicts[a]++ + rows++ + } + if rows < coreNativeFloor { + t.Fatalf("oracle has %d rows, below the %d floor", rows, coreNativeFloor) + } + if len(verdicts) < 4 { + t.Fatalf("oracle records only %d distinct arities (%v): an oracle that answers one value for everything cannot tell a fix from no fix", len(verdicts), verdicts) + } + return out +} + +// TestNativeArityMatchesMRIOracle pins every declared count against the measured +// oracle rather than against rbgo's behaviour, so an expectation cannot drift +// into agreeing with us and disagreeing with MRI. +// +// The three rows it excuses are MRI's arity -2, which no argc can express: -2 is +// min 1 with an unbounded max, and MRI reaches it by implementing pack/unpack in +// RUBY (), not through rb_define_method. Every cfunc with a +// negative argc — -1 and -2 alike — reports -1. +func TestNativeArityMatchesMRIOracle(t *testing.T) { + oracle := loadArityOracle(t) + vm := newTestVM() + natives := coreNatives(vm) + var checked, diverged int + for key, m := range natives { + row, ok := oracle[key] + if !ok { + continue + } + got := methodArity(m) + if row.arity == -2 { + if !argcUndeclarableCoreMethods[key] { + t.Errorf("%s: oracle arity -2 but the method is not listed as inexpressible", key) + } + if got != -1 { + t.Errorf("%s: arity %d, want -1 (the answer any negative argc gives)", key, got) + } + diverged++ + continue + } + checked++ + if got != row.arity { + t.Errorf("%s: arity %d, want %d (%s)", key, got, row.arity, row.source) + } + } + if checked < coreNativeFloor-len(argcUndeclarableCoreMethods) { + t.Fatalf("only %d of %d natives were checked against the oracle: the join is losing rows", checked, len(natives)) + } + t.Logf("%d natives match the oracle's arity, %d diverge by MRI arity -2", checked, diverged) +} + +// TestNativeAccessorsCannotDisagree asserts the property that makes this one +// change rather than several hundred: for a native, #parameters and the +// #inspect signature are RENDERINGS of #arity, so no method can be found where +// two of the three tell different stories. MRI holds the same property by the +// same construction — method_inspect walks rb_method_parameters, which falls +// through to rb_unnamed_parameters(arity) for a cfunc. +// +// Asserting it over the whole table is the point: giving one method an arity +// while #parameters still said [[:rest]] is the inconsistency this fix could +// have introduced, and one method at a time is exactly how it would arrive. +func TestNativeAccessorsCannotDisagree(t *testing.T) { + vm := newTestVM() + natives := coreNatives(vm) + if len(natives) < coreNativeFloor { + t.Fatalf("population %d below the %d floor", len(natives), coreNativeFloor) + } + seen := map[int]int{} + for key, m := range natives { + a := methodArity(m) + seen[a]++ + if got, want := methodParameters(m).Inspect(), unnamedParameters(a).Inspect(); got != want { + t.Errorf("%s: arity %d but parameters %s, want %s", key, a, got, want) + } + if got, want := formatParamList(m), formatUnnamedParams(a); got != want { + t.Errorf("%s: arity %d but inspect signature %s, want %s", key, a, got, want) + } + } + if len(seen) < 4 { + t.Fatalf("the table answers only %d distinct arities (%v): before #714 it answered -1 for 98%% of it, and a property that holds trivially over one verdict proves nothing", len(seen), seen) + } + t.Logf("arity verdicts across %d natives: %v", len(natives), seen) +} + +// TestNativeArityThroughEveryAccessor checks the Ruby-level surfaces agree with +// each other on one method of each shape: a Method and an UnboundMethod, reached +// through instance_method, method, public_method and singleton_method, all report +// the same arity and parameters, and Method#to_proc carries them too (MRI's +// rb_proc_parameters answers rb_unnamed_parameters(rb_proc_arity) for a +// Go-backed proc, so :upcase.to_proc — arity -2 — is [[:req], [:rest]]). +func TestNativeArityThroughEveryAccessor(t *testing.T) { + const src = ` +s = "abc" +probes = { + "instance_method" => String.instance_method(:length), + "method" => s.method(:length), + "public_method" => s.public_method(:length), + "unbound" => s.method(:length).unbind, + "rebound" => String.instance_method(:length).bind(s), + "to_proc" => s.method(:length).to_proc, +} +probes.each { |k, p| puts "#{k} #{p.arity} #{p.parameters.inspect}" } +def String.tagged; 1; end +puts "singleton #{String.singleton_method(:tagged).arity} #{String.singleton_method(:tagged).parameters.inspect}" +# the optional-argument shapes, where -1 is the CORRECT answer +%w[sub].each { |m| puts "String##{m} #{String.instance_method(m).arity} #{String.instance_method(m).parameters.inspect}" } +puts "Array#first #{Array.instance_method(:first).arity} #{Array.instance_method(:first).parameters.inspect}" +puts "Hash#fetch #{Hash.instance_method(:fetch).arity} #{Hash.instance_method(:fetch).parameters.inspect}" +puts "Hash#store #{Hash.instance_method(:store).arity} #{Hash.instance_method(:store).parameters.inspect}" +puts "sym_to_proc #{:upcase.to_proc.arity} #{:upcase.to_proc.parameters.inspect}" +puts String.instance_method(:length).inspect +# Hash#store is deliberately NOT probed for #inspect: rbgo aliases it to #[]= and +# renders "#" where ruby 4.0.5 gives +# "#" (MRI registers a separate cfunc). That is +# an alias/origName divergence, not an arity one -- its signature fragment is +# right -- and softening this expectation to match rbgo would bury it. +puts String.instance_method(:tr).inspect +puts String.instance_method(:sub).inspect +` + // Every expectation below was read from ruby 4.0.5, not from rbgo. + want := strings.Join([]string{ + "instance_method 0 []", + "method 0 []", + "public_method 0 []", + "unbound 0 []", + "rebound 0 []", + "to_proc 0 []", + "singleton 0 []", + "String#sub -1 [[:rest]]", + "Array#first -1 [[:rest]]", + "Hash#fetch -1 [[:rest]]", + "Hash#store 2 [[:req], [:req]]", + "sym_to_proc -2 [[:req], [:rest]]", + "#", + "#", + "#", + }, "\n") + if got := runSrc(t, src); got != want { + t.Errorf("accessors disagree:\ngot:\n%s\nwant:\n%s", got, want) + } +} + +// TestDeclareArgcRejectsOutOfRange pins the validation. MRI's rb_define_method +// raises "arity out of range: %d for -2..15" (vm_method.c r4:877); rbgo's lower +// bound is -1 because it has no (VALUE self, VALUE args) shape to name with -2, +// and offering a value no site could honestly use would be an untested one. +func TestDeclareArgcRejectsOutOfRange(t *testing.T) { + for _, argc := range []int{-3, -2, 16, 99} { + func() { + defer func() { + r := recover() + if r == nil { + t.Errorf("declareArgc(%d) did not panic", argc) + return + } + if want := fmt.Sprintf("arity out of range: %d for -1..15", argc); r != want { + t.Errorf("declareArgc(%d) panicked with %v, want %q", argc, r, want) + } + }() + declareArgc(argc) + }() + } + for _, argc := range []int{-1, 0, 1, 15} { + a := declareArgc(argc) + wantArity := argc + if argc < 0 { + wantArity = -1 + } + if !a.declared || a.nativeArity() != wantArity { + t.Errorf("declareArgc(%d) = %+v, arity %d want %d", argc, a, a.nativeArity(), wantArity) + } + } + if got := declareArgc(3).nativeArity(); got != 3 { + t.Errorf("declared argc 3 reports arity %d", got) + } + if got := (nativeArgc{}).nativeArity(); got != -1 { + t.Errorf("an undeclared argc reports arity %d, want -1", got) + } +} + +// TestGuardArgcKeepsTheMethodRecord pins the defect guardArgc had: it rebuilt the +// Method it wrapped, so every field on the record was reset. The declared argc is +// the one that made it visible (Integer#gcd declared 1 and answered -1), but +// nonRetaining went the same way, and that one is a silent performance loss +// rather than a wrong answer. +func TestGuardArgcKeepsTheMethodRecord(t *testing.T) { + vm := newTestVM() + for name, want := range map[string]int{"gcd": 1, "lcm": 1, "gcdlcm": 1, "to_r": 0, "bit_length": 0} { + m := vm.cInteger.methods[name] + if m == nil { + t.Fatalf("Integer#%s is not defined", name) + } + if got := methodArity(m); got != want { + t.Errorf("Integer#%s arity %d, want %d (the guard must not discard the declared argc)", name, got, want) + } + } + // A guard with equal bounds declares the count it enforces, so a site that + // declared nothing still gets a truthful arity rather than -1. + cls := newClass("GuardProbe", vm.cObject) + cls.define("undeclared", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return self }) + cls.define("stays_variadic", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return self }) + guardArgc(cls, 2, 2, "undeclared") + guardArgc(cls, 0, 1, "stays_variadic") + if got := methodArity(cls.methods["undeclared"]); got != 2 { + t.Errorf("a [2,2] guard left arity %d, want 2", got) + } + if got := methodArity(cls.methods["stays_variadic"]); got != -1 { + t.Errorf("a [0,1] guard gave arity %d, want -1: unequal bounds ARE variadic", got) + } +} diff --git a/internal/vm/testdata/native_arity_oracle.tsv b/internal/vm/testdata/native_arity_oracle.tsv new file mode 100644 index 00000000..86ba7aac --- /dev/null +++ b/internal/vm/testdata/native_arity_oracle.tsv @@ -0,0 +1,501 @@ +# Measured arity / parameters oracle for the core-class instance methods rbgo +# registers natively. Every row was READ FROM AN ORACLE, never from rbgo: +# +# ruby-4.0.5 ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +PRISM +# [arm64-darwin25], Klass.instance_method(m) +# ruby-4.0.5-stdlib the same, after require "abbrev" / "shellwords" / +# "prime" / "scanf" (and for the private #initialize, +# which instance_method reaches but instance_methods +# does not list) +# activesupport-8.1.3 the same, after require "active_support/all", for +# the shims rbgo registers on the core classes at boot +# +# The arity column is the ORACLE, so a row where rbgo cannot match it is a +# divergence to report, not a number to soften: see +# arityInexpressibleAsArgc in method_arity_native_test.go, which names the +# only three and why. +# +# class method arity parameters oracle +Array & 1 [[:req]] ruby-4.0.5 +Array << 1 [[:req]] ruby-4.0.5 +Array <=> 1 [[:req]] ruby-4.0.5 +Array [] -1 [[:rest]] ruby-4.0.5 +Array []= -1 [[:rest]] ruby-4.0.5 +Array abbrev -1 [[:opt, :pattern]] ruby-4.0.5-stdlib +Array append -1 [[:rest]] ruby-4.0.5 +Array assoc 1 [[:req]] ruby-4.0.5 +Array at 1 [[:req]] ruby-4.0.5 +Array blank? 0 [] activesupport-8.1.3 +Array bsearch 0 [] ruby-4.0.5 +Array bsearch_index 0 [] ruby-4.0.5 +Array clear 0 [] ruby-4.0.5 +Array collect 0 [] ruby-4.0.5 +Array collect! 0 [] ruby-4.0.5 +Array combination 1 [[:req]] ruby-4.0.5 +Array compact 0 [] ruby-4.0.5 +Array compact! 0 [] ruby-4.0.5 +Array concat -1 [[:rest]] ruby-4.0.5 +Array cycle -1 [[:rest]] ruby-4.0.5 +Array deconstruct 0 [] ruby-4.0.5 +Array delete 1 [[:req]] ruby-4.0.5 +Array delete_at 1 [[:req]] ruby-4.0.5 +Array delete_if 0 [] ruby-4.0.5 +Array difference -1 [[:rest]] ruby-4.0.5 +Array dig -1 [[:rest]] ruby-4.0.5 +Array drop 1 [[:req]] ruby-4.0.5 +Array drop_while 0 [] ruby-4.0.5 +Array each 0 [] ruby-4.0.5 +Array each_cons 1 [[:req]] ruby-4.0.5 +Array each_index 0 [] ruby-4.0.5 +Array each_slice 1 [[:req]] ruby-4.0.5 +Array each_with_object 1 [[:req]] ruby-4.0.5 +Array empty? 0 [] ruby-4.0.5 +Array fetch -1 [[:rest]] ruby-4.0.5 +Array fetch_values -1 [[:rest, :indexes], [:block, :block]] ruby-4.0.5 +Array fifth 0 [] activesupport-8.1.3 +Array fill -1 [[:rest]] ruby-4.0.5 +Array filter 0 [] ruby-4.0.5 +Array filter! 0 [] ruby-4.0.5 +Array find_index -1 [[:rest]] ruby-4.0.5 +Array first -1 [[:opt, :n]] ruby-4.0.5 +Array flatten -1 [[:rest]] ruby-4.0.5 +Array flatten! -1 [[:rest]] ruby-4.0.5 +Array fourth 0 [] activesupport-8.1.3 +Array in_groups -2 [[:req, :number], [:opt, :fill_with], [:block, :block]] activesupport-8.1.3 +Array in_groups_of -2 [[:req, :number], [:opt, :fill_with], [:block, :block]] activesupport-8.1.3 +Array include? 1 [[:req]] ruby-4.0.5 +Array index -1 [[:rest]] ruby-4.0.5 +Array initialize -1 [[:rest]] ruby-4.0.5-stdlib +Array insert -1 [[:rest]] ruby-4.0.5 +Array inspect 0 [] ruby-4.0.5 +Array intersect? 1 [[:req]] ruby-4.0.5 +Array intersection -1 [[:rest]] ruby-4.0.5 +Array join -1 [[:rest]] ruby-4.0.5 +Array keep_if 0 [] ruby-4.0.5 +Array last -1 [[:opt, :n]] ruby-4.0.5 +Array lazy 0 [] ruby-4.0.5 +Array length 0 [] ruby-4.0.5 +Array map 0 [] ruby-4.0.5 +Array map! 0 [] ruby-4.0.5 +Array max_by -1 [[:rest]] ruby-4.0.5 +Array member? 1 [[:req]] ruby-4.0.5 +Array min_by -1 [[:rest]] ruby-4.0.5 +Array pack -2 [[:req, :fmt], [:key, :buffer]] ruby-4.0.5 +Array permutation -1 [[:rest]] ruby-4.0.5 +Array pop -1 [[:rest]] ruby-4.0.5 +Array prepend -1 [[:rest]] ruby-4.0.5 +Array product -1 [[:rest]] ruby-4.0.5 +Array push -1 [[:rest]] ruby-4.0.5 +Array rassoc 1 [[:req]] ruby-4.0.5 +Array reduce -1 [[:rest]] ruby-4.0.5 +Array reject! 0 [] ruby-4.0.5 +Array repeated_combination 1 [[:req]] ruby-4.0.5 +Array repeated_permutation 1 [[:req]] ruby-4.0.5 +Array replace 1 [[:req]] ruby-4.0.5 +Array reverse 0 [] ruby-4.0.5 +Array reverse! 0 [] ruby-4.0.5 +Array reverse_each 0 [] ruby-4.0.5 +Array rindex -1 [[:rest]] ruby-4.0.5 +Array rotate -1 [[:rest]] ruby-4.0.5 +Array rotate! -1 [[:rest]] ruby-4.0.5 +Array sample -1 [[:opt, :n], [:key, :random]] ruby-4.0.5 +Array second 0 [] activesupport-8.1.3 +Array select 0 [] ruby-4.0.5 +Array select! 0 [] ruby-4.0.5 +Array shelljoin 0 [] ruby-4.0.5-stdlib +Array shift -1 [[:rest]] ruby-4.0.5 +Array shuffle -1 [[:key, :random]] ruby-4.0.5 +Array shuffle! -1 [[:key, :random]] ruby-4.0.5 +Array size 0 [] ruby-4.0.5 +Array slice -1 [[:rest]] ruby-4.0.5 +Array slice! -1 [[:rest]] ruby-4.0.5 +Array sort 0 [] ruby-4.0.5 +Array sort! 0 [] ruby-4.0.5 +Array sort_by 0 [] ruby-4.0.5 +Array sort_by! 0 [] ruby-4.0.5 +Array sum -1 [[:rest]] ruby-4.0.5 +Array take 1 [[:req]] ruby-4.0.5 +Array take_while 0 [] ruby-4.0.5 +Array third 0 [] activesupport-8.1.3 +Array to_ary 0 [] ruby-4.0.5 +Array to_h 0 [] ruby-4.0.5 +Array to_s 0 [] ruby-4.0.5 +Array to_sentence -1 [[:opt, :options]] activesupport-8.1.3 +Array transpose 0 [] ruby-4.0.5 +Array union -1 [[:rest]] ruby-4.0.5 +Array uniq 0 [] ruby-4.0.5 +Array uniq! 0 [] ruby-4.0.5 +Array unshift -1 [[:rest]] ruby-4.0.5 +Array values_at -1 [[:rest]] ruby-4.0.5 +Array | 1 [[:req]] ruby-4.0.5 +Float % 1 [[:req]] ruby-4.0.5 +Float ** 1 [[:req]] ruby-4.0.5 +Float <=> 1 [[:req]] ruby-4.0.5 +Float abs 0 [] ruby-4.0.5 +Float ceil -1 [[:rest]] ruby-4.0.5 +Float coerce 1 [[:req]] ruby-4.0.5 +Float denominator 0 [] ruby-4.0.5 +Float divmod 1 [[:req]] ruby-4.0.5 +Float fdiv 1 [[:req]] ruby-4.0.5 +Float finite? 0 [] ruby-4.0.5 +Float floor -1 [[:rest]] ruby-4.0.5 +Float infinite? 0 [] ruby-4.0.5 +Float magnitude 0 [] ruby-4.0.5 +Float modulo 1 [[:req]] ruby-4.0.5 +Float nan? 0 [] ruby-4.0.5 +Float negative? 0 [] ruby-4.0.5 +Float next_float 0 [] ruby-4.0.5 +Float numerator 0 [] ruby-4.0.5 +Float positive? 0 [] ruby-4.0.5 +Float prev_float 0 [] ruby-4.0.5 +Float quo 1 [[:req]] ruby-4.0.5 +Float rationalize -1 [[:rest]] ruby-4.0.5 +Float remainder 1 [[:req]] ruby-4.0.5 +Float round -1 [[:rest]] ruby-4.0.5 +Float step -1 [[:rest]] ruby-4.0.5 +Float to_c 0 [] ruby-4.0.5 +Float to_f 0 [] ruby-4.0.5 +Float to_i 0 [] ruby-4.0.5 +Float to_int 0 [] ruby-4.0.5 +Float to_r 0 [] ruby-4.0.5 +Float truncate -1 [[:rest]] ruby-4.0.5 +Float zero? 0 [] ruby-4.0.5 +Hash < 1 [[:req]] ruby-4.0.5 +Hash <= 1 [[:req]] ruby-4.0.5 +Hash > 1 [[:req]] ruby-4.0.5 +Hash >= 1 [[:req]] ruby-4.0.5 +Hash [] 1 [[:req]] ruby-4.0.5 +Hash []= 2 [[:req], [:req]] ruby-4.0.5 +Hash assoc 1 [[:req]] ruby-4.0.5 +Hash blank? 0 [] activesupport-8.1.3 +Hash clear 0 [] ruby-4.0.5 +Hash compact 0 [] ruby-4.0.5 +Hash compact! 0 [] ruby-4.0.5 +Hash compare_by_identity 0 [] ruby-4.0.5 +Hash compare_by_identity? 0 [] ruby-4.0.5 +Hash deconstruct_keys 1 [[:req]] ruby-4.0.5 +Hash deep_dup 0 [] activesupport-8.1.3 +Hash deep_merge 1 [[:req, :other], [:block, :block]] activesupport-8.1.3 +Hash deep_merge! 1 [[:req, :other], [:block, :block]] activesupport-8.1.3 +Hash default -1 [[:rest]] ruby-4.0.5 +Hash default= 1 [[:req]] ruby-4.0.5 +Hash default_proc 0 [] ruby-4.0.5 +Hash default_proc= 1 [[:req]] ruby-4.0.5 +Hash delete 1 [[:req]] ruby-4.0.5 +Hash delete_if 0 [] ruby-4.0.5 +Hash dig -1 [[:rest]] ruby-4.0.5 +Hash each 0 [] ruby-4.0.5 +Hash each_key 0 [] ruby-4.0.5 +Hash each_pair 0 [] ruby-4.0.5 +Hash each_value 0 [] ruby-4.0.5 +Hash empty? 0 [] ruby-4.0.5 +Hash except -1 [[:rest]] ruby-4.0.5 +Hash fetch -1 [[:rest]] ruby-4.0.5 +Hash fetch_values -1 [[:rest]] ruby-4.0.5 +Hash filter 0 [] ruby-4.0.5 +Hash filter! 0 [] ruby-4.0.5 +Hash flatten -1 [[:rest]] ruby-4.0.5 +Hash has_key? 1 [[:req]] ruby-4.0.5 +Hash has_value? 1 [[:req]] ruby-4.0.5 +Hash include? 1 [[:req]] ruby-4.0.5 +Hash initialize -1 [[:opt, :ifnone], [:key, :capacity], [:block, :block]] ruby-4.0.5-stdlib +Hash inspect 0 [] ruby-4.0.5 +Hash invert 0 [] ruby-4.0.5 +Hash keep_if 0 [] ruby-4.0.5 +Hash key 1 [[:req]] ruby-4.0.5 +Hash key? 1 [[:req]] ruby-4.0.5 +Hash keys 0 [] ruby-4.0.5 +Hash lazy 0 [] ruby-4.0.5 +Hash length 0 [] ruby-4.0.5 +Hash member? 1 [[:req]] ruby-4.0.5 +Hash merge -1 [[:rest]] ruby-4.0.5 +Hash merge! -1 [[:rest]] ruby-4.0.5 +Hash rassoc 1 [[:req]] ruby-4.0.5 +Hash rehash 0 [] ruby-4.0.5 +Hash reject 0 [] ruby-4.0.5 +Hash reject! 0 [] ruby-4.0.5 +Hash replace 1 [[:req]] ruby-4.0.5 +Hash reverse_merge 1 [[:req, :other_hash]] activesupport-8.1.3 +Hash select 0 [] ruby-4.0.5 +Hash select! 0 [] ruby-4.0.5 +Hash shift 0 [] ruby-4.0.5 +Hash size 0 [] ruby-4.0.5 +Hash slice -1 [[:rest]] ruby-4.0.5 +Hash store 2 [[:req], [:req]] ruby-4.0.5 +Hash stringify_keys 0 [] activesupport-8.1.3 +Hash symbolize_keys 0 [] activesupport-8.1.3 +Hash to_h 0 [] ruby-4.0.5 +Hash to_hash 0 [] ruby-4.0.5 +Hash to_proc 0 [] ruby-4.0.5 +Hash to_s 0 [] ruby-4.0.5 +Hash transform_keys -1 [[:rest]] ruby-4.0.5 +Hash transform_keys! -1 [[:rest]] ruby-4.0.5 +Hash transform_values 0 [] ruby-4.0.5 +Hash transform_values! 0 [] ruby-4.0.5 +Hash update -1 [[:rest]] ruby-4.0.5 +Hash value? 1 [[:req]] ruby-4.0.5 +Hash values 0 [] ruby-4.0.5 +Hash values_at -1 [[:rest]] ruby-4.0.5 +Integer & 1 [[:req]] ruby-4.0.5 +Integer ** 1 [[:req]] ruby-4.0.5 +Integer << 1 [[:req]] ruby-4.0.5 +Integer <=> 1 [[:req]] ruby-4.0.5 +Integer >> 1 [[:req]] ruby-4.0.5 +Integer [] -1 [[:rest]] ruby-4.0.5 +Integer ^ 1 [[:req]] ruby-4.0.5 +Integer abs 0 [] ruby-4.0.5 +Integer allbits? 1 [[:req]] ruby-4.0.5 +Integer anybits? 1 [[:req]] ruby-4.0.5 +Integer bit_length 0 [] ruby-4.0.5 +Integer ceil -1 [[:rest]] ruby-4.0.5 +Integer ceildiv 1 [[:req, :other]] ruby-4.0.5 +Integer chr -1 [[:rest]] ruby-4.0.5 +Integer coerce 1 [[:req]] ruby-4.0.5 +Integer denominator 0 [] ruby-4.0.5 +Integer digits -1 [[:rest]] ruby-4.0.5 +Integer div 1 [[:req]] ruby-4.0.5 +Integer divmod 1 [[:req]] ruby-4.0.5 +Integer downto 1 [[:req]] ruby-4.0.5 +Integer even? 0 [] ruby-4.0.5 +Integer fdiv 1 [[:req]] ruby-4.0.5 +Integer floor -1 [[:rest]] ruby-4.0.5 +Integer gcd 1 [[:req]] ruby-4.0.5 +Integer gcdlcm 1 [[:req]] ruby-4.0.5 +Integer inspect -1 [[:rest]] ruby-4.0.5 +Integer integer? 0 [] ruby-4.0.5 +Integer lcm 1 [[:req]] ruby-4.0.5 +Integer magnitude 0 [] ruby-4.0.5 +Integer multiple_of? 1 [[:req, :number]] activesupport-8.1.3 +Integer negative? 0 [] ruby-4.0.5 +Integer next 0 [] ruby-4.0.5 +Integer nobits? 1 [[:req]] ruby-4.0.5 +Integer numerator 0 [] ruby-4.0.5 +Integer odd? 0 [] ruby-4.0.5 +Integer ord 0 [] ruby-4.0.5 +Integer ordinalize 0 [] activesupport-8.1.3 +Integer positive? 0 [] ruby-4.0.5 +Integer pow -1 [[:rest]] ruby-4.0.5 +Integer pred 0 [] ruby-4.0.5 +Integer prime? 0 [] ruby-4.0.5-stdlib +Integer prime_division -1 [[:opt, :generator]] ruby-4.0.5-stdlib +Integer quo 1 [[:req]] ruby-4.0.5 +Integer rationalize -1 [[:rest]] ruby-4.0.5 +Integer remainder 1 [[:req]] ruby-4.0.5 +Integer round -1 [[:rest]] ruby-4.0.5 +Integer size 0 [] ruby-4.0.5 +Integer step -1 [[:rest]] ruby-4.0.5 +Integer succ 0 [] ruby-4.0.5 +Integer times 0 [] ruby-4.0.5 +Integer to_c 0 [] ruby-4.0.5 +Integer to_f 0 [] ruby-4.0.5 +Integer to_i 0 [] ruby-4.0.5 +Integer to_int 0 [] ruby-4.0.5 +Integer to_r 0 [] ruby-4.0.5 +Integer to_s -1 [[:rest]] ruby-4.0.5 +Integer truncate -1 [[:rest]] ruby-4.0.5 +Integer upto 1 [[:req]] ruby-4.0.5 +Integer zero? 0 [] ruby-4.0.5 +Integer | 1 [[:req]] ruby-4.0.5 +Integer ~ 0 [] ruby-4.0.5 +Range % 1 [[:req]] ruby-4.0.5 +Range == 1 [[:req]] ruby-4.0.5 +Range === 1 [[:req]] ruby-4.0.5 +Range begin 0 [] ruby-4.0.5 +Range bsearch 0 [] ruby-4.0.5 +Range count -1 [[:rest]] ruby-4.0.5 +Range cover? 1 [[:req]] ruby-4.0.5 +Range drop 1 [[:req]] ruby-4.0.5 +Range each 0 [] ruby-4.0.5 +Range end 0 [] ruby-4.0.5 +Range entries 0 [] ruby-4.0.5 +Range eql? 1 [[:req]] ruby-4.0.5 +Range exclude_end? 0 [] ruby-4.0.5 +Range first -1 [[:rest]] ruby-4.0.5 +Range hash 0 [] ruby-4.0.5 +Range include? 1 [[:req]] ruby-4.0.5 +Range initialize -1 [[:rest]] ruby-4.0.5-stdlib +Range last -1 [[:rest]] ruby-4.0.5 +Range lazy 0 [] ruby-4.0.5 +Range map 0 [] ruby-4.0.5 +Range max -1 [[:rest]] ruby-4.0.5 +Range member? 1 [[:req]] ruby-4.0.5 +Range min -1 [[:rest]] ruby-4.0.5 +Range overlap? 1 [[:req]] ruby-4.0.5 +Range reverse_each 0 [] ruby-4.0.5 +Range size 0 [] ruby-4.0.5 +Range step -1 [[:rest]] ruby-4.0.5 +Range take 1 [[:req]] ruby-4.0.5 +Range to_a 0 [] ruby-4.0.5 +String +@ 0 [] ruby-4.0.5 +String -@ 0 [] ruby-4.0.5 +String << 1 [[:req]] ruby-4.0.5 +String <=> 1 [[:req]] ruby-4.0.5 +String == 1 [[:req]] ruby-4.0.5 +String =~ 1 [[:req]] ruby-4.0.5 +String [] -1 [[:rest]] ruby-4.0.5 +String []= -1 [[:rest]] ruby-4.0.5 +String append_as_bytes -1 [[:rest]] ruby-4.0.5 +String ascii_only? 0 [] ruby-4.0.5 +String b 0 [] ruby-4.0.5 +String blank? 0 [] activesupport-8.1.3 +String byteindex -1 [[:rest]] ruby-4.0.5 +String byterindex -1 [[:rest]] ruby-4.0.5 +String bytes 0 [] ruby-4.0.5 +String bytesize 0 [] ruby-4.0.5 +String byteslice -1 [[:rest]] ruby-4.0.5 +String bytesplice -1 [[:rest]] ruby-4.0.5 +String camelize -1 [[:opt, :first_letter]] activesupport-8.1.3 +String capitalize -1 [[:rest]] ruby-4.0.5 +String capitalize! -1 [[:rest]] ruby-4.0.5 +String casecmp 1 [[:req]] ruby-4.0.5 +String casecmp? 1 [[:req]] ruby-4.0.5 +String center -1 [[:rest]] ruby-4.0.5 +String chars 0 [] ruby-4.0.5 +String chomp -1 [[:rest]] ruby-4.0.5 +String chomp! -1 [[:rest]] ruby-4.0.5 +String chop 0 [] ruby-4.0.5 +String chop! 0 [] ruby-4.0.5 +String chr 0 [] ruby-4.0.5 +String classify 0 [] activesupport-8.1.3 +String clear 0 [] ruby-4.0.5 +String codepoints 0 [] ruby-4.0.5 +String concat -1 [[:rest]] ruby-4.0.5 +String count -1 [[:rest]] ruby-4.0.5 +String delete -1 [[:rest]] ruby-4.0.5 +String delete! -1 [[:rest]] ruby-4.0.5 +String delete_prefix 1 [[:req]] ruby-4.0.5 +String delete_prefix! 1 [[:req]] ruby-4.0.5 +String delete_suffix 1 [[:req]] ruby-4.0.5 +String delete_suffix! 1 [[:req]] ruby-4.0.5 +String downcase -1 [[:rest]] ruby-4.0.5 +String downcase! -1 [[:rest]] ruby-4.0.5 +String dump 0 [] ruby-4.0.5 +String each_byte 0 [] ruby-4.0.5 +String each_char 0 [] ruby-4.0.5 +String each_codepoint 0 [] ruby-4.0.5 +String each_grapheme_cluster 0 [] ruby-4.0.5 +String each_line -1 [[:rest]] ruby-4.0.5 +String empty? 0 [] ruby-4.0.5 +String encode -1 [[:rest]] ruby-4.0.5 +String encode! -1 [[:rest]] ruby-4.0.5 +String encoding 0 [] ruby-4.0.5 +String end_with? -1 [[:rest]] ruby-4.0.5 +String ends_with? -1 [[:rest]] activesupport-8.1.3 +String eql? 1 [[:req]] ruby-4.0.5 +String force_encoding 1 [[:req]] ruby-4.0.5 +String getbyte 1 [[:req]] ruby-4.0.5 +String grapheme_clusters 0 [] ruby-4.0.5 +String gsub -1 [[:rest]] ruby-4.0.5 +String gsub! -1 [[:rest]] ruby-4.0.5 +String hex 0 [] ruby-4.0.5 +String html_safe 0 [] activesupport-8.1.3 +String html_safe? 0 [] activesupport-8.1.3 +String humanize -1 [[:key, :capitalize], [:key, :keep_id_suffix]] activesupport-8.1.3 +String include? 1 [[:req]] ruby-4.0.5 +String index -1 [[:rest]] ruby-4.0.5 +String initialize -1 [[:rest]] ruby-4.0.5-stdlib +String insert 2 [[:req], [:req]] ruby-4.0.5 +String intern 0 [] ruby-4.0.5 +String length 0 [] ruby-4.0.5 +String lines -1 [[:rest]] ruby-4.0.5 +String ljust -1 [[:rest]] ruby-4.0.5 +String lstrip -1 [[:rest]] ruby-4.0.5 +String lstrip! -1 [[:rest]] ruby-4.0.5 +String match -1 [[:rest]] ruby-4.0.5 +String match? -1 [[:rest]] ruby-4.0.5 +String next 0 [] ruby-4.0.5 +String next! 0 [] ruby-4.0.5 +String oct 0 [] ruby-4.0.5 +String ord 0 [] ruby-4.0.5 +String parameterize -1 [[:key, :separator], [:key, :preserve_case], [:key, :locale]] activesupport-8.1.3 +String partition 1 [[:req]] ruby-4.0.5 +String pluralize -1 [[:opt, :count], [:opt, :locale]] activesupport-8.1.3 +String prepend -1 [[:rest]] ruby-4.0.5 +String presence 0 [] activesupport-8.1.3 +String present? 0 [] activesupport-8.1.3 +String replace 1 [[:req]] ruby-4.0.5 +String reverse 0 [] ruby-4.0.5 +String reverse! 0 [] ruby-4.0.5 +String rindex -1 [[:rest]] ruby-4.0.5 +String rjust -1 [[:rest]] ruby-4.0.5 +String rpartition 1 [[:req]] ruby-4.0.5 +String rstrip -1 [[:rest]] ruby-4.0.5 +String rstrip! -1 [[:rest]] ruby-4.0.5 +String scan 1 [[:req]] ruby-4.0.5 +String scanf 1 [[:req, :fstr], [:block, :b]] ruby-4.0.5-stdlib +String scrub -1 [[:rest]] ruby-4.0.5 +String scrub! -1 [[:rest]] ruby-4.0.5 +String setbyte 2 [[:req], [:req]] ruby-4.0.5 +String shellescape 0 [] ruby-4.0.5-stdlib +String shellsplit 0 [] ruby-4.0.5-stdlib +String size 0 [] ruby-4.0.5 +String slice -1 [[:rest]] ruby-4.0.5 +String slice! -1 [[:rest]] ruby-4.0.5 +String split -1 [[:rest]] ruby-4.0.5 +String squeeze -1 [[:rest]] ruby-4.0.5 +String squeeze! -1 [[:rest]] ruby-4.0.5 +String squish 0 [] activesupport-8.1.3 +String start_with? -1 [[:rest]] ruby-4.0.5 +String starts_with? -1 [[:rest]] activesupport-8.1.3 +String strip -1 [[:rest]] ruby-4.0.5 +String strip! -1 [[:rest]] ruby-4.0.5 +String sub -1 [[:rest]] ruby-4.0.5 +String sub! -1 [[:rest]] ruby-4.0.5 +String succ 0 [] ruby-4.0.5 +String succ! 0 [] ruby-4.0.5 +String sum -1 [[:rest]] ruby-4.0.5 +String swapcase -1 [[:rest]] ruby-4.0.5 +String swapcase! -1 [[:rest]] ruby-4.0.5 +String titleize -1 [[:key, :keep_id_suffix]] activesupport-8.1.3 +String to_c 0 [] ruby-4.0.5 +String to_f 0 [] ruby-4.0.5 +String to_i -1 [[:rest]] ruby-4.0.5 +String to_r 0 [] ruby-4.0.5 +String to_s 0 [] ruby-4.0.5 +String to_str 0 [] ruby-4.0.5 +String to_sym 0 [] ruby-4.0.5 +String tr 2 [[:req], [:req]] ruby-4.0.5 +String tr! 2 [[:req], [:req]] ruby-4.0.5 +String tr_s 2 [[:req], [:req]] ruby-4.0.5 +String tr_s! 2 [[:req], [:req]] ruby-4.0.5 +String truncate -2 [[:req, :truncate_to], [:opt, :options]] activesupport-8.1.3 +String underscore 0 [] activesupport-8.1.3 +String undump 0 [] ruby-4.0.5 +String unicode_normalize -1 [[:rest]] ruby-4.0.5 +String unicode_normalize! -1 [[:rest]] ruby-4.0.5 +String unicode_normalized? -1 [[:rest]] ruby-4.0.5 +String unpack -2 [[:req, :fmt], [:key, :offset]] ruby-4.0.5 +String unpack1 -2 [[:req, :fmt], [:key, :offset]] ruby-4.0.5 +String upcase -1 [[:rest]] ruby-4.0.5 +String upcase! -1 [[:rest]] ruby-4.0.5 +String upto -1 [[:rest]] ruby-4.0.5 +String valid_encoding? 0 [] ruby-4.0.5 +Symbol <=> 1 [[:req]] ruby-4.0.5 +Symbol == 1 [[:req]] ruby-4.0.5 +Symbol === 1 [[:req]] ruby-4.0.5 +Symbol =~ 1 [[:req]] ruby-4.0.5 +Symbol [] -1 [[:rest]] ruby-4.0.5 +Symbol capitalize -1 [[:rest]] ruby-4.0.5 +Symbol casecmp 1 [[:req]] ruby-4.0.5 +Symbol casecmp? 1 [[:req]] ruby-4.0.5 +Symbol downcase -1 [[:rest]] ruby-4.0.5 +Symbol empty? 0 [] ruby-4.0.5 +Symbol encoding 0 [] ruby-4.0.5 +Symbol end_with? -1 [[:rest]] ruby-4.0.5 +Symbol id2name 0 [] ruby-4.0.5 +Symbol inspect 0 [] ruby-4.0.5 +Symbol intern 0 [] ruby-4.0.5 +Symbol length 0 [] ruby-4.0.5 +Symbol match -1 [[:rest]] ruby-4.0.5 +Symbol match? -1 [[:rest]] ruby-4.0.5 +Symbol name 0 [] ruby-4.0.5 +Symbol next 0 [] ruby-4.0.5 +Symbol size 0 [] ruby-4.0.5 +Symbol slice -1 [[:rest]] ruby-4.0.5 +Symbol start_with? -1 [[:rest]] ruby-4.0.5 +Symbol succ 0 [] ruby-4.0.5 +Symbol swapcase -1 [[:rest]] ruby-4.0.5 +Symbol to_proc 0 [] ruby-4.0.5 +Symbol to_s 0 [] ruby-4.0.5 +Symbol to_sym 0 [] ruby-4.0.5 +Symbol upcase -1 [[:rest]] ruby-4.0.5 From 6fd3ca600f0700768b781d921a66c251763dd534 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Sun, 27 Sep 2026 16:40:15 +0200 Subject: [PATCH 3/5] gofmt the arity test file Co-Authored-By: Claude Opus 5 --- internal/vm/method_arity_native_test.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/vm/method_arity_native_test.go b/internal/vm/method_arity_native_test.go index 72e8d0ee..6f08096d 100644 --- a/internal/vm/method_arity_native_test.go +++ b/internal/vm/method_arity_native_test.go @@ -46,16 +46,16 @@ const coreNativeFloor = 460 // this, in both directions: a new undeclared native fails, and so does an entry // here that has since been declared. var argcUndeclarableCoreMethods = map[string]bool{ - "Float#pow": false, // no Float#pow in ruby 4.0.5 (Integer#pow only) or activesupport - "String#to_html": false, // rbgo's own name; kramdown 2.5.2 defines no String#to_html - "String#to_kramdown_html": false, // likewise - "Array#page": false, // kaminari/pagy shim; neither gem defines Array#page - "String#truncate": true, // activesupport arity -2 (truncate_to, options = {}) - "Array#in_groups": true, // activesupport arity -2 (number, fill_with = nil, &block) - "Array#in_groups_of": true, // activesupport arity -2 - "Array#pack": true, // ruby arity -2, from - "String#unpack": true, // ruby arity -2, from - "String#unpack1": true, // ruby arity -2, from + "Float#pow": false, // no Float#pow in ruby 4.0.5 (Integer#pow only) or activesupport + "String#to_html": false, // rbgo's own name; kramdown 2.5.2 defines no String#to_html + "String#to_kramdown_html": false, // likewise + "Array#page": false, // kaminari/pagy shim; neither gem defines Array#page + "String#truncate": true, // activesupport arity -2 (truncate_to, options = {}) + "Array#in_groups": true, // activesupport arity -2 (number, fill_with = nil, &block) + "Array#in_groups_of": true, // activesupport arity -2 + "Array#pack": true, // ruby arity -2, from + "String#unpack": true, // ruby arity -2, from + "String#unpack1": true, // ruby arity -2, from } // isPlainNative reports whether m is a native method whose arity can only come From 403aeb2bcabf0792993808d1df8f0ba7665ce48d Mon Sep 17 00:00:00 2001 From: tannevaled Date: Sun, 27 Sep 2026 16:45:03 +0200 Subject: [PATCH 4/5] Witness guardArgc's record preservation where its own fallback cannot hide it TestGuardArgcKeepsTheMethodRecord passed with the record still being rebuilt: the equal-bounds fallback re-declared the count, so an assertion about Integer#gcd could not tell the two mechanisms apart. The witness is now a guard with UNEQUAL bounds, where no fallback can fire, over a method carrying a declared argc, nonRetaining and a private visibility -- three fields only preservation restores. Nothing rbgo ships exercises that path (the one unequal-bounds guard is on #rationalize, whose declared argc is -1, which is also what an undeclared one answers), so the witness is built rather than found. Co-Authored-By: Claude Opus 5 --- internal/vm/method_arity_native_test.go | 51 ++++++++++++++++++++----- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/internal/vm/method_arity_native_test.go b/internal/vm/method_arity_native_test.go index 6f08096d..dfce662a 100644 --- a/internal/vm/method_arity_native_test.go +++ b/internal/vm/method_arity_native_test.go @@ -342,26 +342,39 @@ func TestDeclareArgcRejectsOutOfRange(t *testing.T) { } // TestGuardArgcKeepsTheMethodRecord pins the defect guardArgc had: it rebuilt the -// Method it wrapped, so every field on the record was reset. The declared argc is -// the one that made it visible (Integer#gcd declared 1 and answered -1), but -// nonRetaining went the same way, and that one is a silent performance loss -// rather than a wrong answer. +// Method it wrapped with cls.define, so every field on the record was reset. +// +// Two mechanisms now stand between that and a wrong arity, and they are asserted +// SEPARATELY because the first one hides the second. A guard whose bounds are +// EQUAL declares the count it enforces, which alone is enough to make +// Integer#gcd, #lcm, #gcdlcm and #to_r report truthfully — so an assertion about +// those four passes even with the record still being rebuilt. The witness for +// the preservation itself has to be a guard with UNEQUAL bounds, where no +// fallback can fire, and a field the fallback does not restore. +// +// Nothing rbgo ships exercises that today: the only unequal-bounds guard is on +// #rationalize, whose declared argc is -1, which is also what an undeclared one +// answers. That is why the witness is built here rather than found. func TestGuardArgcKeepsTheMethodRecord(t *testing.T) { vm := newTestVM() - for name, want := range map[string]int{"gcd": 1, "lcm": 1, "gcdlcm": 1, "to_r": 0, "bit_length": 0} { + // The equal-bounds path: these four declared a count at their registration site + // and answered -1 before, because the guard had replaced the record holding it. + for name, want := range map[string]int{"gcd": 1, "lcm": 1, "gcdlcm": 1, "to_r": 0} { m := vm.cInteger.methods[name] if m == nil { t.Fatalf("Integer#%s is not defined", name) } if got := methodArity(m); got != want { - t.Errorf("Integer#%s arity %d, want %d (the guard must not discard the declared argc)", name, got, want) + t.Errorf("Integer#%s arity %d, want %d", name, got, want) } } // A guard with equal bounds declares the count it enforces, so a site that - // declared nothing still gets a truthful arity rather than -1. + // declared nothing still gets a truthful arity rather than -1; unequal bounds + // ARE variadic and must stay -1. cls := newClass("GuardProbe", vm.cObject) - cls.define("undeclared", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return self }) - cls.define("stays_variadic", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return self }) + body := func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return self } + cls.define("undeclared", body) + cls.define("stays_variadic", body) guardArgc(cls, 2, 2, "undeclared") guardArgc(cls, 0, 1, "stays_variadic") if got := methodArity(cls.methods["undeclared"]); got != 2 { @@ -370,4 +383,24 @@ func TestGuardArgcKeepsTheMethodRecord(t *testing.T) { if got := methodArity(cls.methods["stays_variadic"]); got != -1 { t.Errorf("a [0,1] guard gave arity %d, want -1: unequal bounds ARE variadic", got) } + // The preservation path. Unequal bounds, so the fallback cannot fire, and a + // declared 1 that only survives if the record does. nonRetaining and vis go + // the same way and are the fields whose loss is silent: one costs a per-call + // args copy, the other makes a private method public. + cls.defineArgcNR("kept", 1, body) + cls.methods["kept"].vis = visPrivate + guardArgc(cls, 0, 3, "kept") + kept := cls.methods["kept"] + if got := methodArity(kept); got != 1 { + t.Errorf("a [0,3] guard over a declared argc 1 gave arity %d, want 1", got) + } + if !kept.nonRetaining { + t.Error("the guard dropped nonRetaining: every call to this method now pays a defensive args copy, and nothing else would say so") + } + if kept.vis != visPrivate { + t.Errorf("the guard reset vis to %v: a private method became public", kept.vis) + } + if kept.name != "kept" || kept.owner != cls { + t.Errorf("the guard lost the method's identity: %q on %v", kept.name, kept.owner) + } } From 4d3f8416adcd3deb819704f7ddcb594005bc864a Mon Sep 17 00:00:00 2001 From: tannevaled Date: Sun, 27 Sep 2026 17:16:00 +0200 Subject: [PATCH 5/5] Correct a Proc#parameters expectation that agreed with rbgo, not with MRI TestProcParameters asserted `:foo.to_proc.parameters == [[:rest]]`, with a comment saying MRI reports a catch-all rest. It does not. MRI answers rb_unnamed_parameters(rb_proc_arity(self)) for an ISeq-less Proc (rb_proc_parameters, proc.c r4:1617), and Symbol#to_proc's arity is -2, so the answer is [[:req], [:rest]]. ruby 4.0.5 gives that, and so does ruby/spec's own core/symbol/to_proc_spec.rb -- the example is literally named "produces a Proc that always returns [[:req], [:rest]] for #parameters", and it is the one corpus example this branch's ratchet delta comes from. The arity of the same Proc is now asserted beside it, so the two cannot be corrected apart. Also drop buildParamsList's nil arm, which became unreachable: an ISeq-less callable now takes the unnamedParameters(arity) route at each call site, which is where MRI puts it. Keeping it cost a new below-100 coverage entry (buildParamsList 95.0%) for a branch no caller can reach. And name the C function the argc range check actually lives in: rb_add_method_cfunc, not rb_define_method. Co-Authored-By: Claude Opus 5 --- internal/vm/method_reflect2.go | 12 +++++++----- internal/vm/object_model.go | 7 ++++--- internal/vm/proc.go | 5 ++++- internal/vm/proc_internal_test.go | 13 +++++++++++-- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/internal/vm/method_reflect2.go b/internal/vm/method_reflect2.go index d3b786e3..f449700a 100644 --- a/internal/vm/method_reflect2.go +++ b/internal/vm/method_reflect2.go @@ -252,8 +252,13 @@ func unnamedParameters(arity int) object.Value { // required positional (:req for a method or a lambda, :opt for a non-lambda // proc, which never enforces its positionals). The positional shape — leading // required, optionals, a *rest, then the POST required — is read from the iseq, -// followed by the keyword, keyword-rest and block parameters. A nil iseq is a -// native (ISeq-less) callable: MRI reports a single catch-all rest. +// followed by the keyword, keyword-rest and block parameters. +// +// is must be non-nil. An ISeq-less callable — a native method or a Go-backed +// Proc — does not come here at all: MRI answers it with rb_unnamed_parameters +// over its arity, which unnamedParameters does at each call site. This function +// used to carry a nil arm returning a flat [[:rest]], which WAS the whole of +// #parameters for a native before #714 and is now unreachable. // // The post run is the one rb_iseq_parameters walks last among the positionals, // [post_start, post_start+post_num), emitting :req for each (iseq.c @@ -263,9 +268,6 @@ func unnamedParameters(arity int) object.Value { // run as optional unless a splat separated it, so `def m(a=1, b)` answered // [[:opt, :a], [:opt, :b]] where MRI answers [[:opt, :a], [:req, :b]]. func buildParamsList(is *bytecode.ISeq, reqKind string) []object.Value { - if is == nil { - return []object.Value{object.NewArray(object.Symbol("rest"))} - } var out []object.Value postStart := iseqPostStart(is) for i, name := range is.Params { diff --git a/internal/vm/object_model.go b/internal/vm/object_model.go index 28928c9d..ea9f5979 100644 --- a/internal/vm/object_model.go +++ b/internal/vm/object_model.go @@ -183,8 +183,8 @@ type Method struct { // method_missing → NoMethodError). undefined bool // argc is the argument count the native method's registration site DECLARED, - // in MRI's rb_define_method convention (vm_method.c r4:877, which rejects - // anything outside -2..15): + // in MRI's rb_define_method convention (rb_add_method_cfunc, vm_method.c + // r4:877, which rejects anything outside -2..15): // // n >= 0 the method takes exactly n arguments // -1 variadic: MRI's (int argc, VALUE *argv, VALUE self) shape @@ -369,7 +369,8 @@ type nativeArgc struct { } // declareArgc validates an argc and packs it. The upper bound is MRI's, which -// raises "arity out of range: %d for -2..15" (vm_method.c r4:877); the lower +// raises "arity out of range: %d for -2..15" (rb_add_method_cfunc, vm_method.c +// r4:877, the check every rb_define_method* goes through); the lower // bound is -1 rather than -2 because rbgo has only MRI's -1 shape (see // nativeArgc). An out-of-range argc is a mistake at a registration site, not a // Ruby-level condition, so it panics rather than raising. diff --git a/internal/vm/proc.go b/internal/vm/proc.go index c6fc896a..5658e697 100644 --- a/internal/vm/proc.go +++ b/internal/vm/proc.go @@ -95,7 +95,10 @@ func (vm *VM) registerProcMethods() { // procParameters builds a Proc's MRI #parameters array by way of the shared // buildParamsList: a non-lambda proc reports every required positional as :opt -// (it never enforces them), while a lambda reports them as :req. +// (it never enforces them), while a lambda reports them as :req. is must be +// non-nil — a Go-backed Proc has no ISeq to read and takes the +// unnamedParameters(arity) route MRI's rb_proc_parameters takes, at the call +// site. func procParameters(is *bytecode.ISeq, isLambda bool) object.Value { reqKind := "opt" if isLambda { diff --git a/internal/vm/proc_internal_test.go b/internal/vm/proc_internal_test.go index 847d4606..be7f9718 100644 --- a/internal/vm/proc_internal_test.go +++ b/internal/vm/proc_internal_test.go @@ -33,8 +33,17 @@ func TestProcParameters(t *testing.T) { {"kw_truthy_int", `p proc{|x| }.parameters(lambda: 123)`, "[[:req, :x]]\n"}, {"kw_nil_ignored_proc", `p proc{|x| }.parameters(lambda: nil)`, "[[:opt, :x]]\n"}, {"kw_nil_ignored_lambda", `p(->x{}.parameters(lambda: nil))`, "[[:req, :x]]\n"}, - // A native proc (Symbol#to_proc has no iseq): MRI reports a catch-all rest. - {"native_proc", `p :foo.to_proc.parameters`, "[[:rest]]\n"}, + // A native proc (Symbol#to_proc has no iseq). The expectation here used to be + // a flat [[:rest]], and it was WRONG: MRI answers + // rb_unnamed_parameters(rb_proc_arity(self)) for an ISeq-less Proc + // (rb_proc_parameters, proc.c r4:1617), and Symbol#to_proc's arity is -2, so + // the answer is [[:req], [:rest]]. ruby 4.0.5 gives that, and so does + // ruby/spec's own core/symbol/to_proc_spec.rb ("produces a Proc that always + // returns [[:req], [:rest]] for #parameters"). The old value agreed with rbgo + // and disagreed with the oracle -- see #714. + {"native_proc", `p :foo.to_proc.parameters`, "[[:req], [:rest]]\n"}, + // The same Proc's arity, so the two cannot be corrected apart. + {"native_proc_arity", `p :foo.to_proc.arity`, "-2\n"}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) {