Skip to content

Commit 8942797

Browse files
committed
Add hook for customizing the debug representation of objects
1 parent 5ad4edc commit 8942797

File tree

2 files changed

+49
-0
lines changed

2 files changed

+49
-0
lines changed

lib/debug/variable_inspector.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ def indexed_members_of(obj, start:, count:)
1818
def named_members_of(obj)
1919
return [] if NaiveString === obj
2020

21+
if M_RESPOND_TO.bind_call(obj, :debug_representation)
22+
debug_representation = obj.debug_representation
23+
members = named_members_of(debug_representation)
24+
25+
# Discard the "#class" member of the debug representation, if any.
26+
members.delete_if { |m| m.name == '#class' }
27+
28+
# Add the real "#class" of the object being inspected.
29+
members.unshift Variable.internal(name: '#class', value: M_CLASS.bind_call(obj))
30+
31+
return members
32+
end
33+
2134
members = case obj
2235
when Hash then obj.map { |k, v| Variable.new(name: value_inspect(k), value: v) }
2336
when Struct then obj.members.map { |name| Variable.new(name:, value: obj[name]) }
@@ -82,6 +95,7 @@ def self.value_inspect(obj, short: true)
8295
# TODO: Replace with Reflection helpers once they are merged
8396
# https://github.com/ruby/debug/pull/1002
8497
M_INSTANCE_OF = method(:instance_of?).unbind
98+
M_RESPOND_TO = method(:respond_to?).unbind
8599
M_INSTANCE_VARIABLES = method(:instance_variables).unbind
86100
M_INSTANCE_VARIABLE_GET = method(:instance_variable_get).unbind
87101
M_CLASS = method(:class).unbind

test/debug/variable_inspector_test.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,31 @@ def test_show_class_of_subclasses_of_simple_types
212212
assert_includes @inspector.named_members_of(hash_subclass.new), Variable.internal(name: "#class", value: hash_subclass)
213213
end
214214

215+
def test_debug_representation_hook
216+
object_with_simple_repr = ClassWithCustomDebugRepresentation.new({ a: 1, b: 2 })
217+
218+
expected = [
219+
# We should always show the `#class` when using this hook, even if the
220+
# debug_representation is a simple value.
221+
Variable.internal(name: '#class', value: ClassWithCustomDebugRepresentation),
222+
Variable.new(name: ':a', value: 1),
223+
Variable.new(name: ':b', value: 2),
224+
]
225+
226+
assert_equal expected, @inspector.named_members_of(object_with_simple_repr)
227+
228+
object_with_complex_repr = ClassWithCustomDebugRepresentation.new(Point.new(x: 1, y: 2))
229+
230+
expected = [
231+
# Make sure we don't add the '#class' twice for non-simple debug representations
232+
Variable.internal(name: '#class', value: ClassWithCustomDebugRepresentation),
233+
Variable.new(name: :@x, value: 1),
234+
Variable.new(name: :@y, value: 2),
235+
]
236+
237+
assert_equal expected, @inspector.named_members_of(object_with_complex_repr)
238+
end
239+
215240
private
216241

217242
class PointStruct < Struct.new(:x, :y, keyword_init: true)
@@ -227,5 +252,15 @@ def initialize(x:, y:)
227252
@y = y
228253
end
229254
end
255+
256+
class ClassWithCustomDebugRepresentation
257+
def initialize(debug_representation)
258+
@debug_representation = debug_representation
259+
end
260+
261+
def debug_representation
262+
@debug_representation
263+
end
264+
end
230265
end
231266
end

0 commit comments

Comments
 (0)