rubyinspect

#inspect on BasicObject oddity


The following happens when inheriting from BasicObject:

class Test < BasicObject
  def inspect
    "foobar"
  end
end

test = Test.new

test.inspect
# => "foobar"

test
(Object doesn't support #inspect)
=>

Is it possible to implement inspect in a way for it to behave normally in IRB?


Solution

  • It's a bug in IRb, or more precisely, in IRB::ColorPrinter#pp:

    def pp(obj)
      if obj.is_a?(String)
        # Avoid calling Ruby 2.4+ String#pretty_print that splits a string by "\n"
        text(obj.inspect)
      else
        super
      end
    end
    

    BasicObject does not have is_a?, so this will raise a NoMethodError exception. Any exception raised by an IRb Inspector, in turn, is treated the same, regardless of cause and origin:

    # 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)"
      ''
    end
    

    This is a combination of two anti-patterns: explicit inheritance checking and Pokemon exception handling. Hey, nobody ever claimed the Ruby standard library is an example of good code. (And nobody ever claimed IRb is a good REPL either.)

    The fact that this error-swallowing behavior is misleading was already filed as a bug a month ago:

    It seems like IRB::Inspector#inspect_value can swallow errors and then only provides a misleading and unhelpful message (Object doesn't support #inspect).

    There is actually a Pull Request which addresses this bug report and improves this very error message and would have immediately told you what is going wrong. In fact, the example the Bug Report and the Pull Request uses to motivate the change is literally your problem: an object that doesn't respond to is_a?:

    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">
    

    However, the Bug Report and the Pull Request are only about the error message, they do not address the error itself, which could be addressed by using Module#=== instead of Object#is_a?.