Skip to content

A String subclass is refused by $/, $-0, $\ and $, where MRI accepts it #884

Closed

Description

@tannevaled

Assigning an instance of a String subclass to $/, $-0, $\ or $, raises TypeError in rbgo. MRI accepts it.

Measured

$ rbgo -e 'sub = Class.new(String); $VERBOSE=nil; $/ = sub.new("abc")'
-e:1:in '<main>': value of $/ must be String (TypeError)

$ ruby -v
ruby 4.0.7 (2026-09-15 revision 229531a6cf) +PRISM [arm64-darwin25]
$ ruby -e 'sub = Class.new(String); $VERBOSE=nil; $/ = sub.new("abc"); p $/.class'
String

Cause

internal/vm/globals.go checkStrGvar decides with a Go type assertion:

if _, ok := v.(*object.String); !ok {
    raise("TypeError", "value of %s must be String", name)
}

A *object.String is class String by construction in rbgo (classOf maps the Go type straight to vm.cString), so an instance of a subclass is a different Go value and fails the assertion. MRI's check is T_STRING, which a subclass still is — rb_str_setter calls StringValue-style validation on the type, not the class.

Why it matters now

It is why language/predefined_spec.rb's

it "makes a new frozen String from the assigned String" do
  string_subclass = Class.new(String)
  str = string_subclass.new("abc")
  str.instance_variable_set(:@ivar, 1)
  $/ = str
  ...

still fails after the Ruby 4.0 record-separator work: the example never reaches the freezing rule, because the assignment is refused one step earlier. I found this while implementing that rule and deliberately did not widen the assertion inside it — the two are separate changes, and this one touches every $x must be String global, not just the two that 4.0 changed.

Scope

The same assertion guards $/, $-0, $\ and $, (and checkFieldSepGvar has the same shape for $;/$-F, which additionally accepts a Regexp). A fix should establish how rbgo answers "is this value a String, subclasses included" once, rather than per-setter.

Refs #799

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions