Simplify Set#inspect output

As Set is now a core collection class, it should have special inspect
output.  Ideally, inspect output should be suitable to eval, similar
to array and hash (assuming the elements are also suitable to eval):

  set = Set[1, 2, 3]
  eval(set.inspect) == set # should be true

The simplest way to do this is to use the Set[] syntax.

This deliberately does not use any subclass name in the output,
similar to array and hash. It is more important that users know they
are dealing with a set than which subclass:

  Class.new(Set)[]
  # this does: Set[]
  # not: #<Class:0x00000c21c78699e0>[]

This inspect change breaks the power_assert bundled gem tests, so
add power_assert to TEST_BUNDLED_GEMS_ALLOW_FAILURES in the workflows.

Implements [Feature #21389]
This commit is contained in:
Jeremy Evans 2025-05-30 17:55:05 -07:00
parent 3b602c952d
commit 3a9c091cf3
16 changed files with 53 additions and 33 deletions

View file

@ -104,7 +104,7 @@ jobs:
timeout-minutes: 30
env:
RUBY_TESTOPTS: '-q --tty=no'
TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,rbs,repl_type_completor'
TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,rbs,repl_type_completor,power_assert'
if: ${{ steps.diff.outputs.gems }}
- name: Commit

View file

@ -146,7 +146,7 @@ jobs:
timeout-minutes: 60
env:
RUBY_TESTOPTS: '-q --tty=no'
TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,rbs,repl_type_completor'
TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,rbs,repl_type_completor,power_assert'
PRECHECK_BUNDLED_GEMS: 'no'
LAUNCHABLE_STDOUT: ${{ steps.launchable.outputs.stdout_report_path }}
LAUNCHABLE_STDERR: ${{ steps.launchable.outputs.stderr_report_path }}

View file

@ -153,7 +153,7 @@ jobs:
timeout-minutes: ${{ matrix.gc.timeout || 40 }}
env:
RUBY_TESTOPTS: '-q --tty=no'
TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,rbs,repl_type_completor'
TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,rbs,repl_type_completor,power_assert'
PRECHECK_BUNDLED_GEMS: 'no'
LAUNCHABLE_STDOUT: ${{ steps.launchable.outputs.stdout_report_path }}
LAUNCHABLE_STDERR: ${{ steps.launchable.outputs.stderr_report_path }}

View file

@ -87,7 +87,7 @@ jobs:
EXCLUDES: '../src/test/.excludes-parsey'
RUN_OPTS: ${{ matrix.run_opts || '--parser=parse.y' }}
SPECOPTS: ${{ matrix.specopts || '-T --parser=parse.y' }}
TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,rbs,repl_type_completor'
TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,rbs,repl_type_completor,power_assert'
- uses: ./.github/actions/slack
with:

View file

@ -133,7 +133,7 @@ jobs:
timeout-minutes: ${{ matrix.timeout || 40 }}
env:
RUBY_TESTOPTS: '-q --tty=no'
TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,rbs,repl_type_completor'
TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,rbs,repl_type_completor,power_assert'
PRECHECK_BUNDLED_GEMS: 'no'
LAUNCHABLE_STDOUT: ${{ steps.launchable.outputs.stdout_report_path }}
LAUNCHABLE_STDERR: ${{ steps.launchable.outputs.stderr_report_path }}

View file

@ -153,7 +153,7 @@ jobs:
timeout-minutes: 60
env:
RUBY_TESTOPTS: '-q --tty=no'
TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,rbs,repl_type_completor'
TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,rbs,repl_type_completor,power_assert'
SYNTAX_SUGGEST_TIMEOUT: '5'
PRECHECK_BUNDLED_GEMS: 'no'
LAUNCHABLE_STDOUT: ${{ steps.launchable.outputs.stdout_report_path }}

View file

@ -201,7 +201,7 @@ jobs:
timeout-minutes: 90
env:
RUBY_TESTOPTS: '-q --tty=no'
TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,rbs,repl_type_completor'
TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,rbs,repl_type_completor,power_assert'
PRECHECK_BUNDLED_GEMS: 'no'
SYNTAX_SUGGEST_TIMEOUT: '5'
YJIT_BINDGEN_DIFF_OPTS: '--exit-code'

View file

@ -150,7 +150,7 @@ jobs:
timeout-minutes: 60
env:
RUBY_TESTOPTS: '-q --tty=no'
TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof'
TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,power_assert'
SYNTAX_SUGGEST_TIMEOUT: '5'
PRECHECK_BUNDLED_GEMS: 'no'
TESTS: ${{ matrix.tests }}

View file

@ -172,7 +172,7 @@ jobs:
timeout-minutes: 90
env:
RUBY_TESTOPTS: '-q --tty=no'
TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof'
TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,power_assert'
PRECHECK_BUNDLED_GEMS: 'no'
SYNTAX_SUGGEST_TIMEOUT: '5'
ZJIT_BINDGEN_DIFF_OPTS: '--exit-code'

8
set.c
View file

@ -537,7 +537,7 @@ static int
set_inspect_i(st_data_t key, st_data_t arg)
{
VALUE str = (VALUE)arg;
if (RSTRING_LEN(str) > 8) {
if (RSTRING_LEN(str) > 4) {
rb_str_buf_cat_ascii(str, ", ");
}
rb_str_buf_append(str, rb_inspect((VALUE)key));
@ -550,10 +550,10 @@ set_inspect(VALUE set, VALUE dummy, int recur)
{
VALUE str;
if (recur) return rb_usascii_str_new2("#<Set: {...}>");
str = rb_str_buf_new2("#<Set: {");
if (recur) return rb_usascii_str_new2("Set[...]");
str = rb_str_buf_new2("Set[");
set_iter(set, set_inspect_i, str);
rb_str_buf_cat2(str, "}>");
rb_str_buf_cat2(str, "]");
return str;
}

View file

@ -30,9 +30,9 @@ describe "Enumerable#to_set" do
it "does not need explicit `require 'set'`" do
output = ruby_exe(<<~RUBY, options: '--disable-gems', args: '2>&1')
puts [1, 2, 3].to_set
puts [1, 2, 3].to_set.to_a.inspect
RUBY
output.chomp.should == "#<Set: {1, 2, 3}>"
output.chomp.should == "[1, 2, 3]"
end
end

View file

@ -95,7 +95,7 @@ describe "Set#compare_by_identity" do
set = Set.new.freeze
-> {
set.compare_by_identity
}.should raise_error(FrozenError, "can't modify frozen Set: #<Set: {}>")
}.should raise_error(FrozenError, /can't modify frozen Set: (#<)?Set(\[|: {)[\]}]>?/)
end
end

View file

@ -3,8 +3,8 @@ require_relative '../../spec_helper'
describe 'Set' do
it 'is available without explicit requiring' do
output = ruby_exe(<<~RUBY, options: '--disable-gems', args: '2>&1')
puts Set.new([1, 2, 3])
puts Set.new([1, 2, 3]).to_a.inspect
RUBY
output.chomp.should == "#<Set: {1, 2, 3}>"
output.chomp.should == "[1, 2, 3]"
end
end

View file

@ -7,19 +7,39 @@ describe :set_inspect, shared: true do
Set[:a, "b", Set[?c]].send(@method).should be_kind_of(String)
end
it "does include the elements of the set" do
Set["1"].send(@method).should == '#<Set: {"1"}>'
ruby_version_is "3.5" do
it "does include the elements of the set" do
Set["1"].send(@method).should == 'Set["1"]'
end
end
ruby_version_is ""..."3.5" do
it "does include the elements of the set" do
Set["1"].send(@method).should == '#<Set: {"1"}>'
end
end
it "puts spaces between the elements" do
Set["1", "2"].send(@method).should include('", "')
end
it "correctly handles cyclic-references" do
set1 = Set[]
set2 = Set[set1]
set1 << set2
set1.send(@method).should be_kind_of(String)
set1.send(@method).should include("#<Set: {...}>")
ruby_version_is "3.5" do
it "correctly handles cyclic-references" do
set1 = Set[]
set2 = Set[set1]
set1 << set2
set1.send(@method).should be_kind_of(String)
set1.send(@method).should include("Set[...]")
end
end
ruby_version_is ""..."3.5" do
it "correctly handles cyclic-references" do
set1 = Set[]
set2 = Set[set1]
set1 << set2
set1.send(@method).should be_kind_of(String)
set1.send(@method).should include("#<Set: {...}>")
end
end
end

View file

@ -839,24 +839,24 @@ class TC_Set < Test::Unit::TestCase
def test_inspect
set1 = Set[1, 2]
assert_equal('#<Set: {1, 2}>', set1.inspect)
assert_equal('Set[1, 2]', set1.inspect)
set2 = Set[Set[0], 1, 2, set1]
assert_equal('#<Set: {#<Set: {0}>, 1, 2, #<Set: {1, 2}>}>', set2.inspect)
assert_equal('Set[Set[0], 1, 2, Set[1, 2]]', set2.inspect)
set1.add(set2)
assert_equal('#<Set: {#<Set: {0}>, 1, 2, #<Set: {1, 2, #<Set: {...}>}>}>', set2.inspect)
assert_equal('Set[Set[0], 1, 2, Set[1, 2, Set[...]]]', set2.inspect)
end
def test_to_s
set1 = Set[1, 2]
assert_equal('#<Set: {1, 2}>', set1.to_s)
assert_equal('Set[1, 2]', set1.to_s)
set2 = Set[Set[0], 1, 2, set1]
assert_equal('#<Set: {#<Set: {0}>, 1, 2, #<Set: {1, 2}>}>', set2.to_s)
assert_equal('Set[Set[0], 1, 2, Set[1, 2]]', set2.to_s)
set1.add(set2)
assert_equal('#<Set: {#<Set: {0}>, 1, 2, #<Set: {1, 2, #<Set: {...}>}>}>', set2.to_s)
assert_equal('Set[Set[0], 1, 2, Set[1, 2, Set[...]]]', set2.to_s)
end
def test_compare_by_identity

View file

@ -10,7 +10,7 @@ github_actions = ENV["GITHUB_ACTIONS"] == "true"
allowed_failures = ENV['TEST_BUNDLED_GEMS_ALLOW_FAILURES'] || ''
if RUBY_PLATFORM =~ /mswin|mingw/
allowed_failures = [allowed_failures, "rbs,debug,irb"].join(',')
allowed_failures = [allowed_failures, "rbs,debug,irb,power_assert"].join(',')
end
allowed_failures = allowed_failures.split(',').uniq.reject(&:empty?)