From 2754662564538836a5c39dfe7b6fe7f0b90127d2 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Tue, 3 Nov 2026 13:55:42 +0100 Subject: [PATCH 1/2] Ruby 4.0.9: the version rbgo reports, and the numbered parameters RUBY_VERSION moves from 3.4.1 to 4.0.9, and the one behaviour that could not land without it lands with it. ## Why these two are one commit compiler.go said so already: hiding `_1` from a Binding "belongs with the RUBY_VERSION bump". Below 4.0 ruby/spec asserts the OPPOSITE -- language/numbered_parameters_spec.rb:93 is `ruby_version_is ""..."4.0"` and pins `binding.local_variables == [:_1]` -- so doing it alone would have traded four passing examples for four failing ones. ## The asymmetry is the whole change `_1` leaves the LOCAL TABLE and stays in #parameters: proc { _1 }.parameters # [[:opt, :_1]] -- UNGATED -> { _1; binding.local_variables }.call("a") # [:_1] below 4.0, [] from 4.0 numbered_parameters_spec.rb:85 carries no version guard, so blanking b.params as well would trade four examples for two. `it` is blanked from BOTH, because MRI gives it no name in #parameters either. So: b.locals for both kinds, b.params for `it` only. ## Refusing by NAME, before any lookup MRI tests rb_numparam_id_p at the TOP of #local_variable_get, #local_variable_set and #local_variable_defined? (proc.c v4.0.9:549, 602, 649): if (rb_numparam_id_p(lid)) { rb_name_err_raise("numbered parameter '%1$s' is not a local variable", ...) } Two consequences that are easy to miss and are both tested: - it raises in a binding that has NO numbered parameter, and at the top level. `_1` is not a local being hidden; it is not a local variable NAME. - #local_variable_defined? RAISES rather than answering false. "Not defined" is the natural reading and the wrong one. And it uses rb_numparam_id_p, NOT rb_implicit_param_p (proc.c:517-521), which also covers `it`. That narrowing is deliberate in MRI and necessary here: `it` is a LEGAL local variable name -- `it = 5` compiles -- so widening the refusal would break every binding holding a variable called `it`. There is a control for it, and for the `_0` / `_10` / `_` boundary. ## What the bump breaks, measured `go test ./...` over the whole repository: FOUR assertions, every one of them a deliberate pin of the pre-4.0 behaviour, and nothing else in a 210-second VM suite. internal/compiler TestImplicitBlockParamsAreHidden "want the numbered names kept" internal/vm TestImplicitItIsInvisible "_1 is a binding local" internal/vm TestImplicitParametersAreNotLocalVariables subtest: "while _1 stays visible at the version rbgo reports" internal/vm TestPBRbConfigKeys the version string Each is updated to the 4.0 behaviour with its comment rewritten, not flipped silently. The third gains a case for the point that `it` and `_1` are refused by DIFFERENT messages. The fourth raises a question this commit does not answer: MRI may report the API version ("4.0.0") rather than the full one for RbConfig::CONFIG["ruby_version"]. Nothing in ruby/spec asserts that key, so the corpus cannot settle it. Filed as #878 rather than changed on a hunch. Refs #799 Refs #878 Co-Authored-By: Claude Opus 5 --- internal/compiler/compiler.go | 30 ++-- internal/compiler/wave29_kwargs_test.go | 13 +- internal/vm/binding.go | 45 +++++- internal/vm/binding_implicit_params_test.go | 13 +- internal/vm/binding_numbered_params_test.go | 165 ++++++++++++++++++++ internal/vm/kernel_introspect.go | 2 +- internal/vm/puppet_boot_modified_test.go | 8 +- internal/vm/wave29_kwargs_test.go | 7 +- 8 files changed, 256 insertions(+), 27 deletions(-) create mode 100644 internal/vm/binding_numbered_params_test.go diff --git a/internal/compiler/compiler.go b/internal/compiler/compiler.go index c8d4f318..404fe94a 100644 --- a/internal/compiler/compiler.go +++ b/internal/compiler/compiler.go @@ -2202,12 +2202,22 @@ func blockParamsAreImplicit(blk *ast.Block) bool { // name AFTER the body is compiled: resolution during the body used the real // name, and the ISeq that remains carries none. // -// The numbered parameters `_1`..`_9` are deliberately NOT hidden. They are named -// in #parameters ([[:opt, :_1]]) and, for the Ruby version rbgo reports -// (RUBY_VERSION 3.4.1), they are visible to a Binding too — ruby/spec pins that -// under `ruby_version_is ""..."4.0"` and pins the hiding only from 4.0. Hiding -// them here would be answering a question rbgo has not yet said it is answering; -// it belongs with the RUBY_VERSION bump. +// ⛔ THE NUMBERED PARAMETERS ARE HIDDEN FROM b.locals AND NOT FROM b.params, and +// that asymmetry is the whole of Ruby 4.0's change here. `_1` leaves +// Binding#local_variables but STAYS in Proc#parameters: +// +// proc { _1 }.parameters # => [[:opt, :_1]] -- ungated, every version +// -> { _1; binding.local_variables }.call("a") +// # => [:_1] below 4.0, [] from 4.0 +// +// numbered_parameters_spec.rb:85 ("affects block parameters") carries no version +// guard at all, so blanking b.params as well would trade four examples for two. +// `it` is blanked from BOTH, because MRI gives it no name in #parameters either +// (`-> { it }.parameters` is [[:req]], NAMELESS). +// +// This was held back until the RUBY_VERSION bump, correctly: below 4.0 +// ruby/spec asserts the OPPOSITE, under `ruby_version_is ""..."4.0"`. The two +// land together. // // ⛔ BOTH KINDS ARE RECORDED, hidden or not. Ruby 4.0 adds // Binding#implicit_parameters, #implicit_parameter_get and @@ -2229,13 +2239,13 @@ func hideImplicitBlockParams(b *builder, blk *ast.Block, positionals []string) { // gate named it, which is the right way to find out. for i, name := range positionals { b.markImplicitParam(i, name) - if name != "it" { - continue - } + // Out of the local table: both kinds, from 4.0. if i < len(b.locals) { b.locals[i] = "" } - if i < len(b.params) { + // Out of #parameters: `it` only. A numbered parameter keeps its name + // there, which numbered_parameters_spec.rb:85 asserts with no guard. + if name == "it" && i < len(b.params) { b.params[i] = "" } } diff --git a/internal/compiler/wave29_kwargs_test.go b/internal/compiler/wave29_kwargs_test.go index 3fa7e792..131b0dfd 100644 --- a/internal/compiler/wave29_kwargs_test.go +++ b/internal/compiler/wave29_kwargs_test.go @@ -360,13 +360,18 @@ func TestImplicitBlockParamsAreHidden(t *testing.T) { if len(writtenIt.Params) != 1 || writtenIt.Params[0] != "it" { t.Errorf("f { |it| it }: Params = %q, want [\"it\"]", writtenIt.Params) } - // The numbered parameters keep theirs, in both tables. + // ⛔ THE NUMBERED PARAMETERS SPLIT THE TWO TABLES, and this asymmetry is the + // whole of Ruby 4.0's change. They keep their names in Params, because + // `proc { _1 }.parameters` is [[:opt, :_1]] under no version guard at all + // (numbered_parameters_spec.rb:85), and lose them in Locals, because from + // 4.0 they are not local variables (:112-135). `it` loses both; a numbered + // parameter loses one. np := blockOf(t, "f { _1 + _2 }") if len(np.Params) != 2 || np.Params[0] != "_1" || np.Params[1] != "_2" { - t.Errorf("f { _1 + _2 }: Params = %q, want [\"_1\" \"_2\"]", np.Params) + t.Errorf("f { _1 + _2 }: Params = %q, want [\"_1\" \"_2\"] -- #parameters is ungated", np.Params) } - if len(np.Locals) < 2 || np.Locals[0] != "_1" || np.Locals[1] != "_2" { - t.Errorf("f { _1 + _2 }: Locals = %q, want the numbered names kept", np.Locals) + if len(np.Locals) < 2 || np.Locals[0] != "" || np.Locals[1] != "" { + t.Errorf("f { _1 + _2 }: Locals = %q, want the numbered names blanked from 4.0", np.Locals) } } diff --git a/internal/vm/binding.go b/internal/vm/binding.go index fc64cf71..d8df9428 100644 --- a/internal/vm/binding.go +++ b/internal/vm/binding.go @@ -292,6 +292,7 @@ func (vm *VM) registerBinding() { cBinding.define("local_variable_get", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { b := self.(*Binding) name := vm.bindingVarName(args[0]) + refuseNumberedParam(name) i := b.slotOf(name) if i < 0 { raise("NameError", "local variable '%s' is not defined for %s", name, b.ToS()) @@ -302,6 +303,7 @@ func (vm *VM) registerBinding() { cBinding.define("local_variable_set", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { b := self.(*Binding) name := vm.bindingVarName(args[0]) + refuseNumberedParam(name) if i := b.slotOf(name); i >= 0 { // The write lands where the name LIVES, which for a binding taken inside a // block is an ANCESTOR env. Writing b.env.slots[i] instead put the value in @@ -318,7 +320,12 @@ func (vm *VM) registerBinding() { return args[1] }) cBinding.define("local_variable_defined?", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { - return object.Bool(self.(*Binding).slotOf(vm.bindingVarName(args[0])) >= 0) + name := vm.bindingVarName(args[0]) + // ⛔ RAISES, it does not answer false. MRI puts the refusal before the + // lookup (proc.c v4.0.9:649), so this is not "no such local" — it is + // "that is not the kind of question this method answers". + refuseNumberedParam(name) + return object.Bool(self.(*Binding).slotOf(name) >= 0) }) // Ruby 4.0's implicit-parameter trio (proc.c v4.0.9:680-783). They are the @@ -388,15 +395,41 @@ func copyImplicit(m map[string]int) map[string]int { return out } -// isImplicitParamName is MRI's rb_implicit_param_p: `it`, or `_` followed by a -// single digit 1..9. `_0` and `_10` are ordinary locals. +// isImplicitParamName is MRI's rb_implicit_param_p (proc.c v4.0.9:517-521): +// `it`, or `_` followed by a single digit 1..9. `_0` and `_10` are ordinary +// locals. func isImplicitParamName(name string) bool { - if name == "it" { - return true - } + return name == "it" || isNumberedParamName(name) +} + +// isNumberedParamName is MRI's rb_numparam_id_p (proc.c v4.0.9:511-515), which +// tests `_1`..`_9` and NOT `it`. +// +// ⛔ THE TWO PREDICATES ARE NOT INTERCHANGEABLE, and MRI uses this narrower one +// in all three of Binding's local-variable methods (proc.c:549, 602, 649) while +// rb_implicit_param_p is used elsewhere. The reason is not a detail: `it` is a +// LEGAL local variable name in Ruby — `it = 5` compiles — so +// `binding.local_variable_get(:it)` must answer normally, while `_1` can never +// be a local and is refused. Widening the refusal to rb_implicit_param_p would +// break every binding that happens to hold a variable called `it`. +func isNumberedParamName(name string) bool { return len(name) == 2 && name[0] == '_' && name[1] >= '1' && name[1] <= '9' } +// refuseNumberedParam is the guard MRI puts at the top of #local_variable_get, +// #local_variable_set and #local_variable_defined? (proc.c v4.0.9:549, 602, +// 649), from Ruby 4.0. +// +// ⛔ IT TESTS THE NAME, NOT THE BINDING, and fires BEFORE any lookup — so +// `binding.local_variable_get(:_1)` raises this even in a binding that has no +// numbered parameter at all, and even at the top level. `_1` is not a local +// variable that happens to be hidden; it is not a local variable name. +func refuseNumberedParam(name string) { + if isNumberedParamName(name) { + raise("NameError", "numbered parameter '%s' is not a local variable", name) + } +} + // implicitParameterNames is bind_implicit_parameters (proc.c v4.0.9:680): [:it] // when `it` is this scope's implicit parameter, otherwise the numbered ones in // order. A block has one kind or the other and never both — `proc { |x| _1 }` is diff --git a/internal/vm/binding_implicit_params_test.go b/internal/vm/binding_implicit_params_test.go index 1a2ad397..55073334 100644 --- a/internal/vm/binding_implicit_params_test.go +++ b/internal/vm/binding_implicit_params_test.go @@ -227,9 +227,18 @@ func TestImplicitParametersAreNotLocalVariables(t *testing.T) { `rescue NameError => e; r = e.message; end }; r`, want: `local variable 'it' is not defined for #`, }, { - name: "while _1 stays visible at the version rbgo reports", + name: "and _1 is hidden the same way from 4.0", src: `r = nil; [1].each { _1; r = binding.local_variables.include?(:_1) }; r.inspect`, - want: `true`, + want: `false`, + }, { + // ⛔ But NOT the same way through local_variable_get. `it` is a legal + // local variable name, so asking for it gets the ordinary "not defined" + // above; `_1` can never be one, and MRI refuses it by name before any + // lookup (proc.c v4.0.9:549). Two different messages, deliberately. + name: "though with a different refusal, because _1 is not a NAME a local can have", + src: `r = nil; [1].each { _1; begin; binding.local_variable_get(:_1); ` + + `rescue NameError => e; r = e.message; end }; r`, + want: `numbered parameter '_1' is not a local variable`, }} { t.Run(tc.name, func(t *testing.T) { if got := evalString(t, tc.src); got != tc.want { diff --git a/internal/vm/binding_numbered_params_test.go b/internal/vm/binding_numbered_params_test.go new file mode 100644 index 00000000..598b5bae --- /dev/null +++ b/internal/vm/binding_numbered_params_test.go @@ -0,0 +1,165 @@ +package vm + +import "testing" + +// Ruby 4.0 takes the numbered parameters out of Binding's local-variable API +// (language/numbered_parameters_spec.rb:112-135, opened by `ruby_version_is +// "4.0"`; the block at :93-110 asserts the opposite below it and closes). +// +// ⛔ THE REFUSAL IS ABOUT THE NAME, NOT THE BINDING. MRI tests +// rb_numparam_id_p(lid) at the TOP of each of the three methods, before any +// lookup happens (proc.c v4.0.9:549, 602, 649), so `_1` raises in a binding +// that has no numbered parameter at all. `_1` is not a local variable that is +// being hidden; it is not a local variable name. +func TestNumberedParametersAreNotLocalVariables(t *testing.T) { + const msg = "numbered parameter '_1' is not a local variable" + for _, tc := range []struct{ name, src, want string }{ + { + "local_variable_get raises", + `begin; -> { _1; binding.local_variable_get(:_1) }.call("a"); rescue NameError => e; e.message; end`, + msg, + }, + { + "local_variable_set raises", + `begin; -> { _1; binding.local_variable_set(:_1, "b") }.call("a"); rescue NameError => e; e.message; end`, + msg, + }, + { + // RAISES rather than answering false, which is the easy one to get + // wrong: "not defined" is the natural reading and the wrong one. + "local_variable_defined? raises rather than answering false", + `begin; -> { _1; binding.local_variable_defined?(:_1) }.call("a"); rescue NameError => e; e.message; end`, + msg, + }, + + // ⛔ Before the lookup: no numbered parameter is in scope anywhere here, + // and it still raises. A guard placed after the lookup would answer + // "local variable '_1' is not defined for ..." instead. + { + "it raises even where no numbered parameter exists", + `begin; binding.local_variable_get(:_1); rescue NameError => e; e.message; end`, + msg, + }, + { + "and for defined?, where false would otherwise be the answer", + `begin; binding.local_variable_defined?(:_9); rescue NameError => e; e.message; end`, + "numbered parameter '_9' is not a local variable", + }, + + // ⛔ CONTROL: `it` is a LEGAL local variable name, so the refusal must + // NOT widen to rb_implicit_param_p. MRI uses the narrower + // rb_numparam_id_p in all three methods for exactly this reason. + { + "it is an ordinary local variable name and is not refused", + `it = 5; binding.local_variable_get(:it).inspect`, + "5", + }, + { + "and can be set through a binding", + `it = 5; binding.local_variable_set(:it, 6); it.inspect`, + "6", + }, + { + "and answers defined?", + `it = 5; binding.local_variable_defined?(:it).inspect`, + "true", + }, + + // ⛔ CONTROL: the boundary of the name shape. `_0` and `_10` are + // ordinary locals in MRI, so a refusal that tested "starts with _" or + // "_ then digits" would reject names the program may legitimately use. + { + "_0 is an ordinary local", + `_0 = 1; binding.local_variable_defined?(:_0).inspect`, + "true", + }, + { + "_10 is an ordinary local", + `_10 = 1; binding.local_variable_defined?(:_10).inspect`, + "true", + }, + { + "a plain underscore is an ordinary local", + `_ = 1; binding.local_variable_defined?(:_).inspect`, + "true", + }, + + // ⛔ CONTROL, and the trade this change must NOT make: #parameters is + // ungated (numbered_parameters_spec.rb:85) and still names `_1`. Only + // the LOCAL TABLE loses the name, never the parameter list -- which is + // why the compiler blanks b.locals and leaves b.params alone. + { + "a proc still names its numbered parameters", + `proc { _1 }.parameters.inspect`, + "[[:opt, :_1]]", + }, + { + "and a lambda does too", + `-> { _2 }.parameters.inspect`, + "[[:req, :_1], [:req, :_2]]", + }, + + // ⛔ CONTROL: they did not vanish, they moved. 4.0 gives them an API of + // their own, and taking them out of one must not take them out of both. + { + "implicit_parameters still sees them", + `-> { _1; binding.implicit_parameters }.call("a").inspect`, + "[:_1]", + }, + { + "and implicit_parameter_get still reads them", + `-> { _1; binding.implicit_parameter_get(:_1) }.call("a").inspect`, + `"a"`, + }, + } { + t.Run(tc.name, func(t *testing.T) { + if got := evalString(t, tc.src); got != tc.want { + t.Errorf("%s\n got %q\nwant %q", tc.src, got, tc.want) + } + }) + } +} + +// #local_variables is tested through runSrc rather than evalString because +// evalString wraps the source in `__v = begin … end`, and that assignment makes +// `__v` a local of the ENCLOSING scope — which a binding taken inside a block +// lists as well. The first run of the cases above read `[:__v]` where it wanted +// `[]`: the numbered parameter was already gone and the harness was supplying +// the remaining entry. An assertion written against that output would have +// pinned my wrapper's name as Ruby behaviour. +func TestNumberedParametersAreNotInLocalVariables(t *testing.T) { + for _, tc := range []struct{ name, src, want string }{ + // The two examples numbered_parameters_spec.rb:113-116 opens at 4.0. + { + "one numbered parameter", + `p(-> { _1; binding.local_variables }.call("a"))`, + "[]", + }, + { + "several", + `p(-> { _2; binding.local_variables }.call("a", "b"))`, + "[]", + }, + // ⛔ CONTROL: the list is not simply empty for every block. A real local + // still appears, so the two cases above measure the hiding and not a + // #local_variables that stopped working. + { + "a real local in the same shape still appears", + `p(-> { x = 1; binding.local_variables }.call)`, + "[:x]", + }, + // ⛔ CONTROL: `it` is blanked from the local table the same way, and + // this is the 4.0 behaviour rbgo already had before the version moved. + { + "it is not listed either", + `p(-> { it; binding.local_variables }.call("a"))`, + "[]", + }, + } { + t.Run(tc.name, func(t *testing.T) { + if got := runSrc(t, tc.src); got != tc.want { + t.Errorf("%s\n got %q\nwant %q", tc.src, got, tc.want) + } + }) + } +} diff --git a/internal/vm/kernel_introspect.go b/internal/vm/kernel_introspect.go index 11df7471..03ec73ef 100644 --- a/internal/vm/kernel_introspect.go +++ b/internal/vm/kernel_introspect.go @@ -21,7 +21,7 @@ import ( // function-local, which is how a CLI ends up printing a version the running VM // does not report. const ( - rubyVersion = "3.4.1" + rubyVersion = "4.0.9" releaseDate = "2024-12-25" revision = "48d4efcb85000e1ebae42004e963b5d0cedddcf2" ) diff --git a/internal/vm/puppet_boot_modified_test.go b/internal/vm/puppet_boot_modified_test.go index dcd5a168..cb3b4c1b 100644 --- a/internal/vm/puppet_boot_modified_test.go +++ b/internal/vm/puppet_boot_modified_test.go @@ -271,7 +271,13 @@ puts RbConfig::CONFIG["RUBY_INSTALL_NAME"] puts RbConfig::CONFIG["bindir"] puts RbConfig::CONFIG["ruby_version"] puts RbConfig::CONFIG["rubylibdir"]` - want := "ruby\nruby\n/usr/bin\n3.4.1\n/usr/lib/ruby" + // ⛔ "ruby_version" here is rbgo's FULL version string, and in MRI that key + // may hold the API version instead ("4.0.0" for every 4.0.x), since it + // names a library directory shared across teeny releases. Filed as #878 + // rather than guessed at: nothing in ruby/spec asserts this key's value, so + // the corpus cannot settle it, and a version bump is not the place to + // change behaviour on a hunch. + want := "ruby\nruby\n/usr/bin\n4.0.9\n/usr/lib/ruby" if got := runSrc(t, src); got != want { t.Errorf("RbConfig keys => %q, want %q", got, want) } diff --git a/internal/vm/wave29_kwargs_test.go b/internal/vm/wave29_kwargs_test.go index b7e5756f..e678817e 100644 --- a/internal/vm/wave29_kwargs_test.go +++ b/internal/vm/wave29_kwargs_test.go @@ -275,10 +275,11 @@ func TestImplicitItIsInvisible(t *testing.T) { {"defined?(it) is still resolved lexically", `p(-> { it; defined?(it) }.call(1))`, `"local-variable"`}, {"it still binds the argument", `p(-> { it }.call(7))`, "7"}, {"it affects arity", "p(-> { it }.arity)", "1"}, - // The numbered parameters are NOT hidden: they are named in #parameters, - // and for the Ruby version rbgo reports they are visible to a Binding. + // ⛔ The numbered parameters are hidden from ONE table of the two. They + // keep their names in #parameters, which carries no version guard, and + // leave the local table from 4.0 -- unlike `it`, which leaves both. {"_1 keeps its name", "p(proc { _1 }.parameters)", "[[:opt, :_1]]"}, - {"_1 is a binding local", `p(-> { _1; binding.local_variables }.call("a"))`, "[:_1]"}, + {"_1 is not a binding local", `p(-> { _1; binding.local_variables }.call("a"))`, "[]"}, } { if got := runSrc(t, tc.src); got != tc.want { t.Errorf("%s:\ngot %q\nwant %q", tc.name, got, tc.want) From 3a22a2d052a0209fc5bfad4dc45b9e75693dd081 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Tue, 3 Nov 2026 15:09:40 +0100 Subject: [PATCH 2/2] BASELINE re-recorded at 4.0.9, on the machine that enforces it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recorded by the rubyspec-baseline lane (#877) on the pinned ubuntu-24.04 -- the same runner the ratchet gates on -- and downloaded as its artefact. Not recorded here: this machine reads +9 against CI for no change in the code. 23,511 -> 23,673 passing examples ## The refusal was exercised on the real case first The lane was dispatched WITHOUT -absorb before it was dispatched with it, because the gate added in #876 had only ever refused a synthetic loss: re-record: 2206 files measured, 2206 in the baseline being replaced -- REGRESSED (11) -- core/objectspace/_id2ref_spec.rb: 0 passing, baseline 10 (-10) core/set/flatten_merge_spec.rb: 0 passing, baseline 3 (-3) ... ::error::REFUSING to re-record: 11 file(s) went backwards -- named above Both of the lane's deliberate asymmetries held on that run: the diffstat step ran (it carries always()) and the artefact step was SKIPPED (it does not), so no unchanged BASELINE was offered as "the re-recorded baseline". The second dispatch passed -absorb, and the baseline now carries the stamp: # ⛔ ABSORBED 11 file(s) that went backwards, recorded with -absorb on 2026-11-03: # core/gc/config_spec.rb: 7 passing, baseline 8 (-1) # ... Six of the eleven are guards CLOSING -- examples 4.0 stops asking for, each with a `ruby_version_is ""..."4.0"` site in the census -- and not defects. ## ⛔ What comparing the SETS found, which the counts hid The local sweep predicted 13 files going backwards; CI named 11. Both missing entries are informative: - language/numbered_parameters_spec.rb is absent because THIS BRANCH fixes it. Independent confirmation in CI of the +4 measured locally. - language/variables_spec.rb is absent because the baseline was STALE. CI reported "118 passing, baseline 115 (+3)" -- an improvement -- while the file has passed 119 since #871 and the move genuinely costs one example there. A real regression reported as a gain. That is a structural blind spot in the gate: a per-file floor cannot see a regression smaller than the file's unbanked improvement. Filed as #881. It is the exact complement of #876, which stops a re-record banking a LOSS; this is the cost of not banking a WIN -- and it existed because until #877 there was no correct way to re-record at all. This fresh measurement clears the accumulated slack as a side effect. ## README TestReadmeConformanceCountMatchesTheBaseline named all three stale sites and the new figure, which is how the README was updated rather than by hand. The dated table is left as the 3.4.1 sweep it is and now says so. Refs #799 Refs #881 Co-Authored-By: Claude Opus 5 --- README.md | 16 ++- scripts/conformance/rubyspec/BASELINE | 191 ++++++++------------------ 2 files changed, 73 insertions(+), 134 deletions(-) diff --git a/README.md b/README.md index 20602c44..e1f342bc 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,14 @@ [![License](https://img.shields.io/badge/license-BSD--3--Clause-blue)](LICENSE) [![Go](https://img.shields.io/github/go-mod/go-version/go-embedded-ruby/ruby?label=go&color=00ADD8)](https://go.dev/dl/) [![Release](https://img.shields.io/github/v/release/go-embedded-ruby/ruby?label=release&color=blue)](https://github.com/go-embedded-ruby/ruby/releases/latest) -[![ruby/spec](https://img.shields.io/badge/ruby%2Fspec-23%2C511%20examples%20passing-1a7f37)](#runtime-conformance--rubyspec) +[![ruby/spec](https://img.shields.io/badge/ruby%2Fspec-23%2C673%20examples%20passing-1a7f37)](#runtime-conformance--rubyspec) **A Ruby interpreter written in pure Go, with cgo disabled** — so you can embed it in a Go program with `import "github.com/go-embedded-ruby/ruby"`, or ship it as a single static binary that cross-compiles wherever Go does, with no C toolchain and no libruby. -**How complete is it?** It runs **23,511** of ruby/spec's `language/` + `core/` +**How complete is it?** It runs **23,673** of ruby/spec's `language/` + `core/` examples ([what that counts](#runtime-conformance--rubyspec)) — a large and growing subset of the language, and **not** a drop-in replacement for CRuby. Read [What does not work yet](#what-does-not-work-yet) before you depend on it; the @@ -153,6 +153,14 @@ writing. Only the baseline is machine-checked against this file from the sweep named below and are not derivable from the baseline, which records passing counts and nothing else. +⛔ **The table is a sweep taken at `RUBY_VERSION` 3.4.1**, before the move to +4.0.9. Its rows are left as they were measured rather than rewritten, because +they come from one dated run and are not derivable from the baseline; the badge +and the summary above count the current `BASELINE` and so already reflect 4.0.9. +The move was measured separately — both engines built from one commit minus the +version string — at **+144 passing and 27 examples that went red** +([#799](https://github.com/go-embedded-ruby/ruby/issues/799)). + Measured 2026-09-28 on `862a9f3`, darwin/arm64, against the corpus pinned at `SPEC_SHA=87b1631992bd00cf0c4934474766d54dad088191`, on an isolated snapshot of it so no concurrent run could swap the shim underneath, and reached through a @@ -763,7 +771,7 @@ Otherwise: | | how it differs from MRI 4.0.5 | | --- | --- | -| `RUBY_VERSION` | rbgo reports `"3.4.1"`; the differential oracle it is developed against is MRI 4.0.5, and the latest MRI release is 4.0.9 (2026-10-23). The Ruby 4.0 **`Ruby` namespace** *is* implemented — all nine of `Ruby::{VERSION,RELEASE_DATE,PLATFORM,PATCHLEVEL,REVISION,COPYRIGHT,ENGINE,ENGINE_VERSION,DESCRIPTION}` are the **same objects** as their `RUBY_`-prefixed globals, as `version.c` defines them, so `Ruby::VERSION.equal?(RUBY_VERSION)` is `true`. The conformance shim resolves `ruby_version_is` at the reported `3.4.1`, so the ten examples asserting that in `core/builtin_constants` are **not counted by the gate**: measured here, that file yields 17 at 3.4.1 and 27 at 4.0.9 ([#799](https://github.com/go-embedded-ruby/ruby/issues/799)) | +| `RUBY_VERSION` | rbgo reports `"4.0.9"`, the latest MRI release (2026-10-23). The Ruby 4.0 **`Ruby` namespace** *is* implemented — all nine of `Ruby::{VERSION,RELEASE_DATE,PLATFORM,PATCHLEVEL,REVISION,COPYRIGHT,ENGINE,ENGINE_VERSION,DESCRIPTION}` are the **same objects** as their `RUBY_`-prefixed globals, as `version.c` defines them, so `Ruby::VERSION.equal?(RUBY_VERSION)` is `true`. The move from `3.4.1` was measured before it was made, with both engines built from one commit minus the version string: **+144 passing** over `core` and `language`, and **27 examples that went red**, which no pass total shows — see [#799](https://github.com/go-embedded-ruby/ruby/issues/799) for the list of what each of the 27 needs | | `Numeric#to_int` | not defined — `Complex(2.9, 0).to_int` raises `NoMethodError`; MRI returns `2` | | `File::Stat#==` | `a == b` is `false` for two stats of the same file (MRI: `true`); `a.==(b)` answers `true` | | `File#stat` on a file unlinked while open | raises `Errno::ENOENT`; MRI answers from the open descriptor. rbgo's streams are buffered by path and hold no descriptor | @@ -1248,7 +1256,7 @@ specification of the language — through `rbgo` under a minimal MSpec-compatibl shim, and gates CI on a **per-file** baseline ([`scripts/conformance/rubyspec/`](scripts/conformance/rubyspec/), in `BASELINE`). No file may pass fewer examples than its record or stop loading, so -measured language conformance moves in one direction; the total, **23,511**, is +measured language conformance moves in one direction; the total, **23,673**, is a derived summary rather than the thing gated. Run it with `scripts/conformance/rubyspec/run.sh`, and see *Runtime conformance* under *Status* for the full breakdown. diff --git a/scripts/conformance/rubyspec/BASELINE b/scripts/conformance/rubyspec/BASELINE index f8dbcdcd..56ea62d7 100644 --- a/scripts/conformance/rubyspec/BASELINE +++ b/scripts/conformance/rubyspec/BASELINE @@ -1,87 +1,18 @@ # ruby/spec per-file baseline. Regenerate with UPDATE_BASELINE=1 scripts/conformance/rubyspec/run.sh. # One record per spec file: OKpathpassing, or FILEFAILpath. # Judged per file, so no margin is needed and every move is attributable. -# -# Recorded 2026-09-28 on main @862a9f3, corpus SPEC_SHA -# 87b1631992bd00cf0c4934474766d54dad088191, 2206 language/+core/ *_spec.rb. -# -# Updated 2026-10-08 for the frozen-def fix (#800), 23483 -> 23512 over the 20 -# files the ratchet reported improved. The twenty deltas were taken from the -# CI run on 496c39d (ubuntu-latest, TIMEOUT=60) and applied to this file, -# rather than re-measured here: this machine was carrying a desktop load of 12 -# and a pass/fail count is corrupted under load through the per-file timeout -- -# which is what "record it on an unloaded machine" above is about, and the -# reason the CI job raises TIMEOUT. Three checks before writing: the pre-edit -# sum matched the 23483 the README claimed, every one of CI's "baseline" -# columns matched the value recorded here, and the post-edit sum equals the -# 23512 CI reported. CI's verdict listed improved (20) and nothing else -- no -# regression, no new file, no change of loading -- so the deltas are the whole -# move. -# -# ⛔ 2026-10-09: ONE of those twenty was wrong to apply, and this file said so. -# core/random/bytes_spec.rb went 8 -> 9, and it is the KNOWN FLAKE held at its -# observed minimum (see "One file is recorded at its OBSERVED MINIMUM" below, -# and the two earlier waves that applied deltas and deliberately left it alone). -# The next run read 8 and the lane went red, which is the coin toss that note -# predicts in those words. Put back to 8. -# -# All three checks that commit lists were ARITHMETIC -- sums and columns -- and -# every one of them passed. None of them asked whether a number SHOULD move, -# which is the question this file answers in its own header, three times over, -# about this exact path. Measured here afterwards, same binary, six runs: -# 9 9 9 9 8 9. -# -# So: when applying a ratchet delta, read the header for the FILE being moved -# before moving it. The sum being right is not evidence that the move was. -# -# THE FRAME MATTERS, twice over, and both are why an earlier attempt at this -# file was wrong: -# -# 1. PATH. Seven path-identity specs answer differently depending on whether -# the corpus is reached through a symlink -- /tmp is a symlink to -# private/tmp on macOS, and run.sh's default CACHE lives there. Same inode, -# different spelling, 20 examples. This was swept from a REAL path. The -# underlying divergence from MRI is issue #741. -# 2. PLATFORM. This lane runs on ubuntu. FIVE files were measured on darwin and -# are recorded at CI's own LOWER ubuntu value instead: file/stat/inspect, -# kernel/backtick, marshal/load, process/setrlimit, kernel/require. Each was -# found by CI NAMING it, which is what judging per file buys -- a scalar -# floor would have reported one number and left the reader to guess which of -# 2206 files moved. Under-recording is the safe direction: a file that -# passes more is reported as improved, never failed. The baseline belongs to -# the lane that enforces it. -# -# Swept twice; the two sweeps were byte-identical across all 2206 records. -# -# 2026-09-29, #741 (ISeq path vs realpath): THREE files raised by applying the -# darwin-measured DELTA to the recorded value, not by overwriting with a darwin -# sweep -- core/kernel/require_spec.rb is one of the five held at CI's lower -# ubuntu number, and an overwrite would have lost that. The deltas, from two -# sweeps of the same real-path corpus before and after the change: -# core/thread/backtrace/location/absolute_path_spec.rb +3 (2 -> 5) -# core/kernel/require_relative_spec.rb +1 (54 -> 55) -# core/kernel/require_spec.rb +1 (68 -> 69, from 69 -> 70) -# core/random/bytes_spec.rb read 9 in both post-change sweeps and is deliberately -# left at 8: it is the known flake named below, not a mover. -# -# One file is recorded at its OBSERVED MINIMUM rather than its usual value: -# core/random/bytes_spec.rb answers 8 or 9 on the same binary, on both engines, -# and every wave measuring conformance today named it. Recorded at 8, so a run -# that reads 9 reports an improvement and one that reads 8 passes. Recording a -# flake at its high value would fail the lane on a coin toss -- two sweeps here -# read 9 and a third, under heavier load, read 8. -# -# 2026-09-30, frozen_string_literal magic comment (+ parser v0.10.0's unary -# plus): FIVE records raised by APPLYING the measured delta, not by overwriting -# from a darwin sweep -- an overwrite would discard the five values held at CI's -# lower ubuntu number and the flake note below. The deltas are the difference -# between two sweeps of the same real-path corpus run back to back, one with -# origin/main's binary and one with the branch's: -# language/magic_comment_spec.rb +5 core/kernel/eval_spec.rb +2 -# core/proc/call_spec.rb +1 core/string/uplus_spec.rb +1 -# language/encoding_spec.rb +1 -# core/random/bytes_spec.rb read 9 on BOTH binaries and stays at 8: the known -# flake named below, not a mover. +# ⛔ ABSORBED 11 file(s) that went backwards, recorded with -absorb on 2026-11-03: +# core/gc/config_spec.rb: 7 passing, baseline 8 (-1) +# core/objectspace/_id2ref_spec.rb: 0 passing, baseline 10 (-10) +# core/range/max_spec.rb: 30 passing, baseline 32 (-2) +# core/set/divide_spec.rb: 8 passing, baseline 9 (-1) +# core/set/equal_value_spec.rb: 3 passing, baseline 4 (-1) +# core/set/exclusion_spec.rb: 2 passing, baseline 3 (-1) +# core/set/flatten_merge_spec.rb: 0 passing, baseline 3 (-3) +# core/set/flatten_spec.rb: 9 passing, baseline 10 (-1) +# core/set/join_spec.rb: 4 passing, baseline 5 (-1) +# core/set/proper_superset_spec.rb: 2 passing, baseline 3 (-1) +# core/set/superset_spec.rb: 2 passing, baseline 3 (-1) OK core/argf/argf_spec.rb 2 OK core/argf/argv_spec.rb 1 OK core/argf/binmode_spec.rb 2 @@ -140,7 +71,7 @@ OK core/array/deconstruct_spec.rb 1 OK core/array/delete_at_spec.rb 6 OK core/array/delete_if_spec.rb 13 OK core/array/delete_spec.rb 7 -OK core/array/detect_spec.rb 0 +OK core/array/detect_spec.rb 1 OK core/array/difference_spec.rb 14 OK core/array/dig_spec.rb 8 OK core/array/drop_spec.rb 11 @@ -158,7 +89,7 @@ OK core/array/fetch_values_spec.rb 8 OK core/array/fill_spec.rb 47 OK core/array/filter_spec.rb 2 OK core/array/find_index_spec.rb 8 -OK core/array/find_spec.rb 0 +OK core/array/find_spec.rb 16 OK core/array/first_spec.rb 15 OK core/array/flatten_spec.rb 32 OK core/array/frozen_spec.rb 2 @@ -224,7 +155,7 @@ OK core/array/repeated_permutation_spec.rb 11 OK core/array/replace_spec.rb 7 OK core/array/reverse_each_spec.rb 8 OK core/array/reverse_spec.rb 6 -OK core/array/rfind_spec.rb 0 +OK core/array/rfind_spec.rb 16 OK core/array/rindex_spec.rb 13 OK core/array/rotate_spec.rb 14 OK core/array/sample_spec.rb 24 @@ -266,16 +197,16 @@ OK core/basicobject/singleton_method_undefined_spec.rb 2 OK core/binding/clone_spec.rb 2 OK core/binding/dup_spec.rb 3 OK core/binding/eval_spec.rb 15 -OK core/binding/implicit_parameter_defined_spec.rb 0 -OK core/binding/implicit_parameter_get_spec.rb 0 -OK core/binding/implicit_parameters_spec.rb 0 +OK core/binding/implicit_parameter_defined_spec.rb 13 +OK core/binding/implicit_parameter_get_spec.rb 14 +OK core/binding/implicit_parameters_spec.rb 10 OK core/binding/local_variable_defined_spec.rb 8 OK core/binding/local_variable_get_spec.rb 8 OK core/binding/local_variable_set_spec.rb 7 OK core/binding/local_variables_spec.rb 5 OK core/binding/receiver_spec.rb 1 OK core/binding/source_location_spec.rb 2 -OK core/builtin_constants/builtin_constants_spec.rb 17 +OK core/builtin_constants/builtin_constants_spec.rb 27 OK core/class/allocate_spec.rb 3 OK core/class/attached_object_spec.rb 0 OK core/class/dup_spec.rb 6 @@ -339,13 +270,13 @@ OK core/conditionvariable/marshal_dump_spec.rb 1 OK core/conditionvariable/signal_spec.rb 2 OK core/conditionvariable/wait_spec.rb 6 OK core/data/constants_spec.rb 2 -OK core/data/deconstruct_keys_spec.rb 8 +OK core/data/deconstruct_keys_spec.rb 12 OK core/data/deconstruct_spec.rb 1 OK core/data/define_spec.rb 5 OK core/data/eql_spec.rb 8 OK core/data/equal_value_spec.rb 8 OK core/data/hash_spec.rb 3 -OK core/data/initialize_spec.rb 20 +OK core/data/initialize_spec.rb 24 OK core/data/inspect_spec.rb 1 OK core/data/members_spec.rb 3 OK core/data/to_h_spec.rb 7 @@ -552,7 +483,7 @@ OK core/enumerator/next_values_spec.rb 8 OK core/enumerator/peek_spec.rb 5 OK core/enumerator/peek_values_spec.rb 8 OK core/enumerator/plus_spec.rb 2 -OK core/enumerator/produce_spec.rb 5 +OK core/enumerator/produce_spec.rb 9 OK core/enumerator/product/each_spec.rb 8 OK core/enumerator/product/initialize_copy_spec.rb 6 OK core/enumerator/product/initialize_spec.rb 5 @@ -619,7 +550,7 @@ OK core/exception/equal_value_spec.rb 10 OK core/exception/errno_spec.rb 8 OK core/exception/exception_spec.rb 10 OK core/exception/exit_value_spec.rb 1 -OK core/exception/frozen_error_spec.rb 1 +OK core/exception/frozen_error_spec.rb 2 OK core/exception/full_message_spec.rb 8 OK core/exception/hierarchy_spec.rb 0 OK core/exception/inspect_spec.rb 5 @@ -663,7 +594,7 @@ OK core/fiber/current_spec.rb 1 OK core/fiber/inspect_spec.rb 6 OK core/fiber/kill_spec.rb 6 OK core/fiber/new_spec.rb 4 -OK core/fiber/raise_spec.rb 35 +OK core/fiber/raise_spec.rb 54 OK core/fiber/resume_spec.rb 15 OK core/fiber/scheduler_spec.rb 0 OK core/fiber/set_scheduler_spec.rb 5 @@ -858,7 +789,7 @@ OK core/float/uminus_spec.rb 5 OK core/float/uplus_spec.rb 1 OK core/float/zero_spec.rb 1 OK core/gc/auto_compact_spec.rb 1 -OK core/gc/config_spec.rb 8 +OK core/gc/config_spec.rb 7 OK core/gc/count_spec.rb 2 OK core/gc/disable_spec.rb 1 OK core/gc/enable_spec.rb 1 @@ -1016,7 +947,7 @@ OK core/integer/zero_spec.rb 2 OK core/io/advise_spec.rb 13 OK core/io/autoclose_spec.rb 8 OK core/io/binmode_spec.rb 7 -OK core/io/binread_spec.rb 7 +OK core/io/binread_spec.rb 8 OK core/io/binwrite_spec.rb 11 OK core/io/buffer/and_spec.rb 8 OK core/io/buffer/bit_count_spec.rb 0 @@ -1027,7 +958,7 @@ OK core/io/buffer/free_spec.rb 10 OK core/io/buffer/initialize_spec.rb 13 OK core/io/buffer/internal_spec.rb 3 OK core/io/buffer/locked_spec.rb 7 -OK core/io/buffer/map_spec.rb 15 +OK core/io/buffer/map_spec.rb 20 OK core/io/buffer/mapped_spec.rb 3 OK core/io/buffer/not_spec.rb 4 OK core/io/buffer/null_spec.rb 13 @@ -1060,7 +991,7 @@ OK core/io/fdatasync_spec.rb 0 OK core/io/fileno_spec.rb 2 OK core/io/flush_spec.rb 1 OK core/io/for_fd_spec.rb 56 -OK core/io/foreach_spec.rb 34 +OK core/io/foreach_spec.rb 35 OK core/io/fsync_spec.rb 2 OK core/io/getbyte_spec.rb 5 OK core/io/getc_spec.rb 4 @@ -1088,16 +1019,16 @@ OK core/io/putc_spec.rb 9 OK core/io/puts_spec.rb 16 OK core/io/pwrite_spec.rb 9 OK core/io/read_nonblock_spec.rb 17 -OK core/io/read_spec.rb 104 +OK core/io/read_spec.rb 105 OK core/io/readbyte_spec.rb 2 OK core/io/readchar_spec.rb 14 OK core/io/readline_spec.rb 11 -OK core/io/readlines_spec.rb 54 +OK core/io/readlines_spec.rb 55 OK core/io/readpartial_spec.rb 14 OK core/io/reopen_spec.rb 27 OK core/io/rewind_spec.rb 6 OK core/io/seek_spec.rb 15 -OK core/io/select_spec.rb 14 +OK core/io/select_spec.rb 15 OK core/io/set_encoding_by_bom_spec.rb 16 OK core/io/set_encoding_spec.rb 37 OK core/io/stat_spec.rb 1 @@ -1115,10 +1046,10 @@ OK core/io/tty_spec.rb 2 OK core/io/ungetbyte_spec.rb 6 OK core/io/ungetc_spec.rb 11 OK core/io/write_nonblock_spec.rb 18 -OK core/io/write_spec.rb 43 +OK core/io/write_spec.rb 44 OK core/kernel/Array_spec.rb 14 OK core/kernel/Complex_spec.rb 68 -OK core/kernel/Float_spec.rb 97 +OK core/kernel/Float_spec.rb 98 OK core/kernel/Hash_spec.rb 10 OK core/kernel/Integer_spec.rb 243 OK core/kernel/Rational_spec.rb 32 @@ -1163,7 +1094,7 @@ OK core/kernel/gsub_spec.rb 0 OK core/kernel/initialize_clone_spec.rb 4 OK core/kernel/initialize_copy_spec.rb 4 OK core/kernel/initialize_dup_spec.rb 3 -OK core/kernel/inspect_spec.rb 4 +OK core/kernel/inspect_spec.rb 6 OK core/kernel/instance_of_spec.rb 7 OK core/kernel/instance_variable_defined_spec.rb 6 OK core/kernel/instance_variable_get_spec.rb 17 @@ -1182,7 +1113,7 @@ OK core/kernel/methods_spec.rb 18 OK core/kernel/nil_spec.rb 1 OK core/kernel/not_match_spec.rb 3 OK core/kernel/object_id_spec.rb 13 -OK core/kernel/open_spec.rb 14 +OK core/kernel/open_spec.rb 15 OK core/kernel/p_spec.rb 8 OK core/kernel/pp_spec.rb 0 OK core/kernel/print_spec.rb 4 @@ -1291,14 +1222,14 @@ OK core/math/cosh_spec.rb 7 OK core/math/erf_spec.rb 7 OK core/math/erfc_spec.rb 7 OK core/math/exp_spec.rb 7 -OK core/math/expm1_spec.rb 0 +OK core/math/expm1_spec.rb 7 OK core/math/frexp_spec.rb 6 OK core/math/gamma_spec.rb 13 OK core/math/hypot_spec.rb 7 OK core/math/ldexp_spec.rb 12 OK core/math/lgamma_spec.rb 10 OK core/math/log10_spec.rb 9 -OK core/math/log1p_spec.rb 0 +OK core/math/log1p_spec.rb 10 OK core/math/log2_spec.rb 8 OK core/math/log_spec.rb 11 OK core/math/sin_spec.rb 7 @@ -1354,7 +1285,7 @@ OK core/module/const_get_spec.rb 45 OK core/module/const_missing_spec.rb 5 OK core/module/const_set_spec.rb 15 OK core/module/const_source_location_spec.rb 40 -OK core/module/constants_spec.rb 7 +OK core/module/constants_spec.rb 11 OK core/module/define_method_spec.rb 85 OK core/module/define_singleton_method_spec.rb 1 OK core/module/deprecate_constant_spec.rb 6 @@ -1408,7 +1339,7 @@ OK core/module/refinements_spec.rb 3 OK core/module/remove_class_variable_spec.rb 8 OK core/module/remove_const_spec.rb 11 OK core/module/remove_method_spec.rb 14 -OK core/module/ruby2_keywords_spec.rb 14 +OK core/module/ruby2_keywords_spec.rb 15 OK core/module/set_temporary_name_spec.rb 13 OK core/module/singleton_class_spec.rb 5 OK core/module/to_s_spec.rb 8 @@ -1487,7 +1418,7 @@ OK core/numeric/truncate_spec.rb 1 OK core/numeric/uminus_spec.rb 3 OK core/numeric/uplus_spec.rb 1 OK core/numeric/zero_spec.rb 2 -OK core/objectspace/_id2ref_spec.rb 10 +OK core/objectspace/_id2ref_spec.rb 0 OK core/objectspace/count_objects_spec.rb 0 OK core/objectspace/define_finalizer_spec.rb 7 OK core/objectspace/each_object_spec.rb 22 @@ -1533,8 +1464,8 @@ OK core/proc/hash_spec.rb 3 OK core/proc/inspect_spec.rb 1 OK core/proc/lambda_spec.rb 10 OK core/proc/new_spec.rb 10 -OK core/proc/parameters_spec.rb 25 -OK core/proc/ruby2_keywords_spec.rb 6 +OK core/proc/parameters_spec.rb 27 +OK core/proc/ruby2_keywords_spec.rb 7 OK core/proc/source_location_spec.rb 5 OK core/proc/to_proc_spec.rb 1 OK core/proc/to_s_spec.rb 6 @@ -1646,7 +1577,7 @@ OK core/queue/pop_spec.rb 20 OK core/queue/push_spec.rb 1 OK core/queue/shift_spec.rb 1 OK core/queue/size_spec.rb 1 -OK core/random/bytes_spec.rb 8 +OK core/random/bytes_spec.rb 9 OK core/random/default_spec.rb 1 OK core/random/equal_value_spec.rb 2 OK core/random/new_seed_spec.rb 3 @@ -1676,7 +1607,7 @@ OK core/range/include_spec.rb 78 OK core/range/initialize_spec.rb 7 OK core/range/inspect_spec.rb 4 OK core/range/last_spec.rb 13 -OK core/range/max_spec.rb 32 +OK core/range/max_spec.rb 30 OK core/range/member_spec.rb 1 OK core/range/min_spec.rb 30 OK core/range/minmax_spec.rb 13 @@ -1689,7 +1620,7 @@ OK core/range/size_spec.rb 7 OK core/range/step_spec.rb 73 OK core/range/to_a_spec.rb 7 OK core/range/to_s_spec.rb 3 -OK core/range/to_set_spec.rb 3 +OK core/range/to_set_spec.rb 4 OK core/rational/abs_spec.rb 1 OK core/rational/ceil_spec.rb 9 OK core/rational/comparison_spec.rb 13 @@ -1767,16 +1698,16 @@ OK core/set/delete_if_spec.rb 4 OK core/set/delete_spec.rb 5 OK core/set/difference_spec.rb 1 OK core/set/disjoint_spec.rb 4 -OK core/set/divide_spec.rb 9 +OK core/set/divide_spec.rb 8 OK core/set/each_spec.rb 3 OK core/set/empty_spec.rb 1 OK core/set/enumerable/to_set_spec.rb 2 OK core/set/eql_spec.rb 1 -OK core/set/equal_value_spec.rb 4 -OK core/set/exclusion_spec.rb 3 +OK core/set/equal_value_spec.rb 3 +OK core/set/exclusion_spec.rb 2 OK core/set/filter_spec.rb 1 -OK core/set/flatten_merge_spec.rb 3 -OK core/set/flatten_spec.rb 10 +OK core/set/flatten_merge_spec.rb 0 +OK core/set/flatten_spec.rb 9 OK core/set/gt_spec.rb 1 OK core/set/gte_spec.rb 1 OK core/set/hash_spec.rb 1 @@ -1786,7 +1717,7 @@ OK core/set/initialize_spec.rb 10 OK core/set/inspect_spec.rb 1 OK core/set/intersect_spec.rb 4 OK core/set/intersection_spec.rb 4 -OK core/set/join_spec.rb 5 +OK core/set/join_spec.rb 4 OK core/set/keep_if_spec.rb 4 OK core/set/length_spec.rb 1 OK core/set/lt_spec.rb 1 @@ -1798,7 +1729,7 @@ OK core/set/minus_spec.rb 3 OK core/set/plus_spec.rb 1 OK core/set/pretty_print_cycle_spec.rb 0 OK core/set/proper_subset_spec.rb 2 -OK core/set/proper_superset_spec.rb 3 +OK core/set/proper_superset_spec.rb 2 OK core/set/reject_spec.rb 5 OK core/set/replace_spec.rb 5 OK core/set/select_spec.rb 5 @@ -1807,9 +1738,9 @@ OK core/set/size_spec.rb 1 OK core/set/sortedset/sortedset_spec.rb 0 OK core/set/subset_spec.rb 2 OK core/set/subtract_spec.rb 2 -OK core/set/superset_spec.rb 3 +OK core/set/superset_spec.rb 2 OK core/set/to_a_spec.rb 1 -OK core/set/to_s_spec.rb 2 +OK core/set/to_s_spec.rb 4 OK core/set/union_spec.rb 4 OK core/signal/list_spec.rb 4 OK core/signal/signame_spec.rb 6 @@ -2070,7 +2001,7 @@ OK core/thread/new_spec.rb 6 OK core/thread/pass_spec.rb 1 OK core/thread/pending_interrupt_spec.rb 3 OK core/thread/priority_spec.rb 7 -OK core/thread/raise_spec.rb 48 +OK core/thread/raise_spec.rb 68 OK core/thread/report_on_exception_spec.rb 5 OK core/thread/run_spec.rb 3 OK core/thread/set_trace_func_spec.rb 0 @@ -2214,13 +2145,13 @@ OK language/alias_spec.rb 22 OK language/and_spec.rb 10 OK language/array_spec.rb 23 OK language/assignments_spec.rb 34 -OK language/block_spec.rb 163 +OK language/block_spec.rb 164 OK language/break_spec.rb 29 OK language/case_spec.rb 48 OK language/class_spec.rb 40 OK language/class_variable_spec.rb 13 OK language/comment_spec.rb 1 -OK language/constants_spec.rb 88 +OK language/constants_spec.rb 90 OK language/def_spec.rb 63 OK language/defined_spec.rb 256 OK language/delegation_spec.rb 12 @@ -2232,7 +2163,7 @@ OK language/for_spec.rb 32 OK language/hash_spec.rb 29 OK language/heredoc_spec.rb 10 OK language/if_spec.rb 50 -OK language/it_parameter_spec.rb 15 +OK language/it_parameter_spec.rb 16 OK language/keyword_arguments_spec.rb 20 OK language/lambda_spec.rb 56 OK language/line_spec.rb 6 @@ -2244,7 +2175,7 @@ OK language/method_spec.rb 160 OK language/module_spec.rb 14 OK language/next_spec.rb 31 OK language/not_spec.rb 9 -OK language/numbered_parameters_spec.rb 16 +OK language/numbered_parameters_spec.rb 17 OK language/numbers_spec.rb 21 OK language/optional_assignments_spec.rb 74 OK language/or_spec.rb 12 @@ -2275,7 +2206,7 @@ OK language/retry_spec.rb 3 OK language/return_spec.rb 37 OK language/safe_navigator_spec.rb 9 OK language/safe_spec.rb 1 -OK language/send_spec.rb 72 +OK language/send_spec.rb 73 OK language/singleton_class_spec.rb 47 OK language/source_encoding_spec.rb 0 OK language/string_spec.rb 25 @@ -2285,6 +2216,6 @@ OK language/throw_spec.rb 9 OK language/undef_spec.rb 6 OK language/unless_spec.rb 6 OK language/until_spec.rb 25 -OK language/variables_spec.rb 115 +OK language/variables_spec.rb 118 OK language/while_spec.rb 34 OK language/yield_spec.rb 36