Skip to content

internal/vm: Kernel#to_s prints the address for a plain object, and Thread::* carry their qualified names - #818

Open
tannevaled wants to merge 1 commit into
mainfrom
fix/kernel-to-s-address
Open

tannevaled wants to merge 1 commit into
mainfrom
fix/kernel-to-s-address

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Closes issue 756. Closes the kind-A half of issue 759.

Two issues that meet on one line of output

756 — Kernel#to_s is MRI's rb_any_to_s, which always prints the
address. Object.new.to_s is #<Object:0x…>, not #<Object>. rbgo printed the
short form, so #to_s and #inspect disagreed with each other inside rbgo
while #inspect agreed with MRI.

759 (kind A) — Thread::Mutex and three siblings answered their bare
name. In MRI the class has one name, the qualified one, and top-level Mutex is
a second constant pointing at the same class: Mutex.name is "Thread::Mutex"
however it was reached, and Mutex.equal?(Thread::Mutex) is true in both.

They are coupled: RMutex.ToS hardcoded "#<Thread::Mutex>", so once
Kernel#to_s builds its string from classOf(self).name the bare name becomes
visible as #<Mutex:0x…> — a correct address with a wrong name. The first
version of this branch did exactly that. Fixing either alone trades one
divergence for another.

⚠ The scope is narrower than MRI's, deliberately

The fix applies to a plain *RObject only. Thirteen core classes reach
Kernel#to_s here and MRI gives all thirteen an address — but most are Go types
whose own ToS() is their #to_s: rbgo implements many objects' to_s as
the Go method rather than as a registered Ruby one, and they arrive at
Kernel#to_s relying on the delegation.

A version that applied the identity form to everything reaching Kernel#to_s
broke 78 tests, and every one of them was right to break:

receiver its #to_s must be
Capistrano::Server "db1" — the hostname
Capistrano::Task "solo" — the task name
Net::SMTP::Response "250 2.0.0 queued"
OpenIDConnect::ProviderMetadata #<… issuer=https://…>
the top-level self "main" — which is what irb's prompt is built from

I had transposed a rule that is true of MRI — "reaching rb_any_to_s means
the receiver has no useful representation"
— into an engine whose dispatch works
differently. My seven targeted probes were all green; only the full suite said
otherwise. The count is in the code comment so the next person to widen this
knows what they will meet.

The three tests that needed updating, each checked against MRI

  • builtin_subclass_test.go and phase1_test.go pinned "#<Plain>" and
    "#<E>" for a plain object. MRI 4.0.7 answers "#<Plain:0xA>" and
    #<E:0xA>, so those were green only while rbgo omitted the address.
  • wave59_puts_to_s_test.go's wrapper block already normalised addresses to
    0xA — it was built for this. That block is now byte-identical to MRI on 13
    of its 14 lines
    .

The fourteenth is worth naming rather than folding in: rbgo's Enumerator#to_s
answers MRI's inspect form, #<Enumerator: [1, 2]:each>, where MRI's
#to_s inherits Kernel's and answers #<Enumerator:0x…>. A separate
divergence, recorded in the test.

Measured

Reach established by census, not assumption: of 31 core classes asked
instance_method(:to_s).owner, thirteen answer Kernel in rbgo and MRI
answers Kernel for the same thirteen
. Everything else — including a subclass
of String or Array — defines or inherits its own, and a control case asserts
each of those is untouched.

Full suite: 0 failures.

🤖 Generated with Claude Code

…hread::* carry their qualified names

Two issues that meet on one line of output, so they are fixed together: doing
either alone trades one divergence for another.

#756 -- Kernel#to_s is MRI's rb_any_to_s, which ALWAYS prints the address.
`Object.new.to_s` is "#<Object:0x…>", not "#<Object>". rbgo printed the short
form, so #to_s and #inspect disagreed with each other INSIDE rbgo while #inspect
agreed with MRI.

#759 (kind A) -- Thread::Mutex, Thread::Queue, Thread::SizedQueue and
Thread::ConditionVariable answered their BARE name. In MRI the class has one
name, the qualified one, and the top-level `Mutex` is a second constant pointing
at the same class: `Mutex.name` is "Thread::Mutex" however it was reached, and
`Mutex.equal?(Thread::Mutex)` is true in both engines.

They are coupled because RMutex.ToS hardcoded "#<Thread::Mutex>". Once
Kernel#to_s builds its string from classOf(self).name, the bare name becomes
visible as "#<Mutex:0x…>" -- a correct address with a wrong name. The first
version of this commit did exactly that.

⚠ THE SCOPE IS NARROWER THAN MRI'S, deliberately. The fix applies to a plain
*RObject only. Thirteen core classes reach Kernel#to_s here and MRI gives all
thirteen an address, but most are Go types whose own ToS() IS their #to_s --
rbgo implements many objects' to_s as the Go method rather than as a registered
Ruby one, and they arrive at Kernel#to_s relying on the delegation.

A version that applied the identity form to everything reaching Kernel#to_s
broke 78 TESTS, and they were right to break:

    Capistrano::Server#to_s          "db1"               the hostname
    Capistrano::Task#to_s            "solo"              the task name
    Net::SMTP::Response#to_s         "250 2.0.0 queued"
    OpenIDConnect::ProviderMetadata  "#<… issuer=…>"
    the top-level self               "main"              irb's prompt

I had transposed a rule that is true of MRI -- "reaching rb_any_to_s means the
receiver has no useful representation" -- into an engine whose dispatch works
differently. The count is in the code comment so the next person to widen this
knows what they will meet.

Three tests needed updating, each checked against MRI 4.0.7 rather than assumed:
two pinned "#<Plain>" / "#<E>" for a PLAIN object, which MRI contradicts, and
wave59's wrapper block already normalised addresses to 0xA, so it was built for
this. That block is now byte-identical to MRI on 13 of its 14 lines; the
fourteenth, Enumerator#to_s answering MRI's *inspect* form, is a separate
divergence named in the test rather than folded into the expectation.

The full suite is green: 0 failures.

Closes #756. Closes the kind-A half of issue 759.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant