Skip to content

GC.config(hash) answers the full config from 4.0 — a second fix recovered from an unproposed branch - #899

Merged
tannevaled merged 3 commits into
mainfrom
gc-config-409
Nov 5, 2026
Merged

tannevaled merged 3 commits into
mainfrom
gc-config-409

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Closes core/gc/config_spec.rb from #799: pass=7 fail=1 → pass=8 fail=0 error=0.

⛔ The fix already existed, unproposed

The first commit is 3705961f from move-to-4.0.9, cherry-picked unchanged with its author and message intact. That branch is pushed to origin and has no pull request, open or closed. It carries three more unlanded fixes; the full account is on #799. I am landing it rather than rewriting it.

The behaviour

From 4.0 the Hash form of GC.config answers the full configuration, where before it answered only the writable settings — an empty Hash for rbgo, whose one key is read-only. ruby/spec asserts it both ways, which is how a reader tells a version swap from a defect:

ruby_version_is ""..."4.0" do
  it "returns the same as GC.config but without the :implementation key"
ruby_version_is "4.0" do
  it "returns the same as GC.config, including the :implementation key"

(core/gc/config_spec.rb:43 and :50.)

Verified against MRI, not taken on trust

A probe comparing shape only — :implementation's value is necessarily per-engine ("rbgo" here), so comparing the value would diverge for a reason that is not the behaviour at issue. rubydiff against /opt/homebrew/opt/ruby@4.0/bin/ruby: AGREE on all ten rows.

config has :implementation      true
config({}) == config            true
config({}) has :implementation  true
config(unknown) == config       true
config(unknown) is a Hash       Hash
an unknown key is not kept      false
config is not frozen            false
GC.config.equal?(GC.config)     false
config(nil)                     no raise
config(1)                       ArgumentError

What the second commit adds

Answering the full config where the answer used to be {} makes the identity of that Hash matter, and nothing pinned it. Had the method handed back the VM's own Hash, a caller mutating the result would be editing the garbage collector's configuration — harmless while the only key is read-only, and a defect the first time a writable one is added.

It does not: each call builds a fresh, unfrozen copy, and an unknown key is ignored rather than stored. Four rows, each measured equal on 4.0.7.

🤖 Generated with Claude Code

tannevaled and others added 2 commits November 5, 2026 13:15
`core/gc/config_spec.rb` at 4.0.9: `pass=7 fail=1` → **`pass=8 fail=0`**.

```
expected {} to == {implementation: "rbgo"}
```

What the Hash form RETURNS changed, and ruby/spec asserts it **both ways**,
which is how a reader can tell a version swap from a defect:

```ruby
ruby_version_is ""..."4.0" do
  it "returns the same as GC.config but without the :implementation key"
ruby_version_is "4.0" do
  it "returns the same as GC.config, including the :implementation key"
```

(core/gc/config_spec.rb:43 and :50). So from 4.0 the setter answers with the
full configuration — the same thing the no-argument form returns,
`GC.config({}).should == previous`. For rbgo that is the single read-only
`:implementation` key, which is why the pre-4.0 answer was an empty Hash.

## The unit test moved with the version, not because it was wrong

`gc_test.go` pinned the 3.4 contract:

```go
{`p GC.config({})`,                  "{}\n"},
{`p (GC.config(foo: "bar") == {})`,  "true\n"},
```

Both rows now assert the 4.0 answer, with the two guards quoted above beside
them — a reader who finds this diff later needs to know the expectation changed
because the target version did, not because the old one was a mistake. That
distinction is the whole reason `BASELINE` is not being regenerated for this
branch (#799).

Refs #799

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The parent commit is cherry-picked unchanged from move-to-4.0.9 (3705961,
2026-11-02), a branch that was pushed and never proposed; see #799.

I verified its claim against MRI 4.0.7 rather than taking it, with a probe that
compares SHAPE only — :implementation's value is necessarily per-engine ("rbgo"
here), so comparing the value would diverge for a reason that is not the
behaviour at issue. Ten rows, AGREE:

    config has :implementation      true
    config({}) == config            true
    config({}) has :implementation  true
    config(unknown) == config       true
    config(unknown) is a Hash       Hash
    an unknown key is not kept      false
    config is not frozen            false
    GC.config.equal?(GC.config)     false
    config(nil)                     no raise
    config(1)                       ArgumentError

## What the added rows are for

Answering the FULL config where the answer used to be {} makes the identity of
that Hash matter, and nothing pinned it. If the method handed back the VM's own
Hash, a caller mutating the result would be editing the garbage collector's
configuration — harmless while the only key is read-only, and a defect the first
time a writable one is added.

It does not: each call builds a fresh, unfrozen copy, and an unknown key is
ignored rather than stored. Four rows, each measured equal on 4.0.7.

core/gc/config_spec.rb: pass=7 fail=1 -> pass=8 fail=0 error=0.

Refs #799

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit b7d3bb0 into main Nov 5, 2026
26 checks passed
@tannevaled
tannevaled deleted the gc-config-409 branch November 5, 2026 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant