[ruby/irb] Provide more useful message when

`IRB::Inspector#inspect_value` errors
(https://github.com/ruby/irb/pull/511)

**Before**

```
irb(main):001:0> c = Cat.new "foo"
(Object doesn't support #inspect)
=>
```

**After**

```
irb(main):001:0> c = Cat.new "foo"
An error occurred when inspecting the object: #<NoMethodError: undefined method `is_a?' for foo:Cat

      if obj.is_a?(String)
            ^^^^^^>
Result of Kernel#inspect: #<Cat:0x0000000109090d80 @name="foo">
=>
```
This commit is contained in:
Stan Lo 2023-02-27 19:07:14 +08:00 коммит произвёл git
Родитель 4f611df3f7
Коммит 0aa50a03b1
2 изменённых файлов: 70 добавлений и 8 удалений

Просмотреть файл

@ -35,6 +35,7 @@ module IRB # :nodoc:
# irb(main):001:0> "what?" #=> omg! what?
#
class Inspector
KERNEL_INSPECT = Object.instance_method(:inspect)
# Default inspectors available to irb, this includes:
#
# +:pp+:: Using Kernel#pretty_inspect
@ -93,9 +94,18 @@ module IRB # :nodoc:
# Proc to call when the input is evaluated and output in irb.
def inspect_value(v)
@inspect.call(v)
rescue
puts "(Object doesn't support #inspect)"
''
rescue => e
puts "An error occurred when inspecting the object: #{e.inspect}"
begin
# TODO: change this to bind_call when we drop support for Ruby 2.6
puts "Result of Kernel#inspect: #{KERNEL_INSPECT.bind(v).call}"
''
rescue => e
puts "An error occurred when running Kernel#inspect: #{e.inspect}"
puts e.backtrace.join("\n")
''
end
end
end

Просмотреть файл

@ -125,11 +125,11 @@ module TestIRB
[:marshal, "123", Marshal.dump(123)],
],
failed: [
[false, "BasicObject.new", /\(Object doesn't support #inspect\)\n(=> )?\n/],
[:p, "class Foo; undef inspect ;end; Foo.new", /\(Object doesn't support #inspect\)\n(=> )?\n/],
[true, "BasicObject.new", /\(Object doesn't support #inspect\)\n(=> )?\n/],
[:yaml, "BasicObject.new", /\(Object doesn't support #inspect\)\n(=> )?\n/],
[:marshal, "[Object.new, Class.new]", /\(Object doesn't support #inspect\)\n(=> )?\n/]
[false, "BasicObject.new", /#<NoMethodError: undefined method `to_s' for/],
[:p, "class Foo; undef inspect ;end; Foo.new", /#<NoMethodError: undefined method `inspect' for/],
[true, "BasicObject.new", /#<NoMethodError: undefined method `is_a\?' for/],
[:yaml, "BasicObject.new", /#<NoMethodError: undefined method `inspect' for/],
[:marshal, "[Object.new, Class.new]", /#<TypeError: can't dump anonymous class #<Class:/]
]
}.each do |scenario, cases|
cases.each do |inspect_mode, input, expected|
@ -149,6 +149,58 @@ module TestIRB
end
end
def test_object_inspection_falls_back_to_kernel_inspect_when_errored
omit if RUBY_ENGINE == "truffleruby"
verbose, $VERBOSE = $VERBOSE, nil
main = Object.new
main.singleton_class.module_eval <<~RUBY
class Foo
def inspect
raise "foo"
end
end
RUBY
irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new(["Foo.new"]))
out, err = capture_output do
irb.eval_input
end
assert_empty err
assert_match(/An error occurred when inspecting the object: #<RuntimeError: foo>/, out)
assert_match(/Result of Kernel#inspect: #<#<Class:.*>::Foo:/, out)
ensure
$VERBOSE = verbose
end
def test_object_inspection_prints_useful_info_when_kernel_inspect_also_errored
omit if RUBY_VERSION < '2.7' || RUBY_ENGINE == "truffleruby"
verbose, $VERBOSE = $VERBOSE, nil
main = Object.new
main.singleton_class.module_eval <<~RUBY
class Foo
def initialize
# Kernel#inspect goes through instance variables with #inspect
# So this will cause Kernel#inspect to fail
@foo = BasicObject.new
end
def inspect
raise "foo"
end
end
RUBY
irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new(["Foo.new"]))
out, err = capture_output do
irb.eval_input
end
assert_empty err
assert_match(/An error occurred when inspecting the object: #<RuntimeError: foo>/, out)
assert_match(/An error occurred when running Kernel#inspect: #<NoMethodError: undefined method `inspect' for/, out)
ensure
$VERBOSE = verbose
end
def test_default_config
assert_equal(true, @context.use_autocomplete?)
end