Skip to content

Commit 8a6053b

Browse files
committed
Add hook for customizing the debug representation of objects
1 parent 48d487d commit 8a6053b

File tree

2 files changed

+49
-0
lines changed

2 files changed

+49
-0
lines changed

Diff for: lib/debug/variable_inspector.rb

+14
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,19 @@ def indexed_members_of(obj, start:, count:)
1717
def named_members_of(obj)
1818
return [] if NaiveString === obj
1919

20+
if M_RESPOND_TO.bind_call(obj, :debug_representation)
21+
debug_representation = obj.debug_representation
22+
members = named_members_of(debug_representation)
23+
24+
# Discard the "#class" member of the debug representation, if any.
25+
members.delete_if { |m| m.name == '#class' }
26+
27+
# Add the real "#class" of the object being inspected.
28+
members.unshift Variable.internal(name: '#class', value: M_CLASS.bind_call(obj))
29+
30+
return members
31+
end
32+
2033
members = case obj
2134
when Hash then obj.map { |k, v| Variable.new(name: value_inspect(k), value: v) }
2235
when Struct then obj.members.map { |name| Variable.new(name: name, value: obj[name]) }
@@ -69,6 +82,7 @@ def self.value_inspect(obj, short: true)
6982

7083
# TODO: Replace with Reflection helpers once they are merged
7184
# https://github.com/ruby/debug/pull/1002
85+
M_RESPOND_TO = method(:respond_to?).unbind
7286
M_INSTANCE_VARIABLES = method(:instance_variables).unbind
7387
M_INSTANCE_VARIABLE_GET = method(:instance_variable_get).unbind
7488
M_CLASS = method(:class).unbind

Diff for: test/debug/variable_inspector_test.rb

+35
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,31 @@ def test_named_members_of_other_objects
196196
assert_equal expected, @inspector.named_members_of(point)
197197
end
198198

199+
def test_debug_representation_hook
200+
object_with_simple_repr = ClassWithCustomDebugRepresentation.new({ a: 1, b: 2 })
201+
202+
expected = [
203+
# We should always show the `#class` when using this hook, even if the
204+
# debug_representation is a simple value.
205+
Variable.internal(name: '#class', value: ClassWithCustomDebugRepresentation),
206+
Variable.new(name: ':a', value: 1),
207+
Variable.new(name: ':b', value: 2),
208+
]
209+
210+
assert_equal expected, @inspector.named_members_of(object_with_simple_repr)
211+
212+
object_with_complex_repr = ClassWithCustomDebugRepresentation.new(Point.new(x: 1, y: 2))
213+
214+
expected = [
215+
# Make sure we don't add the '#class' twice for non-simple debug representations
216+
Variable.internal(name: '#class', value: ClassWithCustomDebugRepresentation),
217+
Variable.new(name: :@x, value: 1),
218+
Variable.new(name: :@y, value: 2),
219+
]
220+
221+
assert_equal expected, @inspector.named_members_of(object_with_complex_repr)
222+
end
223+
199224
private
200225

201226
class PointStruct < Struct.new(:x, :y, keyword_init: true)
@@ -211,5 +236,15 @@ def initialize(x:, y:)
211236
@y = y
212237
end
213238
end
239+
240+
class ClassWithCustomDebugRepresentation
241+
def initialize(debug_representation)
242+
@debug_representation = debug_representation
243+
end
244+
245+
def debug_representation
246+
@debug_representation
247+
end
248+
end
214249
end
215250
end

0 commit comments

Comments
 (0)