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_arity_native_test.go b/internal/vm/method_arity_native_test.go new file mode 100644 index 00000000..dfce662a --- /dev/null +++ b/internal/vm/method_arity_native_test.go @@ -0,0 +1,406 @@ +// 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 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() + // 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", 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; unequal bounds + // ARE variadic and must stay -1. + cls := newClass("GuardProbe", vm.cObject) + 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 { + 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) + } + // 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) + } +} diff --git a/internal/vm/method_reflect2.go b/internal/vm/method_reflect2.go index 154fe117..f449700a 100644 --- a/internal/vm/method_reflect2.go +++ b/internal/vm/method_reflect2.go @@ -215,16 +215,50 @@ 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 // 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 @@ -234,9 +268,6 @@ func methodParameters(m *Method) 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 { @@ -384,7 +415,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 +474,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..ea9f5979 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 (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 + // + // 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,76 @@ 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" (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. +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..5658e697 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) }) @@ -86,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) { 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/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 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)