Skip to content

Commit

Permalink
Omit "#class" for simple values
Browse files Browse the repository at this point in the history
  • Loading branch information
amomchilov committed Jan 4, 2024
1 parent 6837459 commit 697cce3
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 12 deletions.
14 changes: 13 additions & 1 deletion lib/debug/variable_inspector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,25 @@ def named_members_of(obj)
Variable.new(name: iv, value: M_INSTANCE_VARIABLE_GET.bind_call(obj, iv))
end

members.unshift Variable.internal(name: '#class', value: M_CLASS.bind_call(obj))
members.concat(ivars_members)

unless simple_value?(obj)
members.unshift Variable.internal(name: '#class', value: M_CLASS.bind_call(obj))
end

members
end

private

SIMPLE_VALUE_TYPES = [NilClass, FalseClass, TrueClass, Symbol, String, Integer, Float, Class, Module, Array, Hash]

# A "simple" value is one that does not need its `#class` to be shown.
def simple_value?(o)
# Check `#instance_of?` instead of `#is_a?` so objects of subclasses show their `#class` for added clarity.
SIMPLE_VALUE_TYPES.any? { |t| M_INSTANCE_OF.bind_call(o, t) }
end

def value_inspect(obj, short: true)
self.class.value_inspect(obj, short: short)
end
Expand All @@ -69,6 +80,7 @@ def self.value_inspect(obj, short: true)

# TODO: Replace with Reflection helpers once they are merged
# https://github.com/ruby/debug/pull/1002
M_INSTANCE_OF = method(:instance_of?).unbind
M_INSTANCE_VARIABLES = method(:instance_variables).unbind
M_INSTANCE_VARIABLE_GET = method(:instance_variable_get).unbind
M_CLASS = method(:class).unbind
Expand Down
38 changes: 27 additions & 11 deletions test/debug/variable_inspector_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ def test_named_members_of_hash
)

expected = [
Variable.internal(name: '#class', value: Hash),
Variable.new(name: ':sym', value: "has Symbol key"),
Variable.new(name: '"str"', value: "has String key"),
Variable.new(name: '1', value: "has Integer key"),
Expand All @@ -73,7 +72,6 @@ def test_named_members_of_struct

def test_named_members_of_string
expected = [
Variable.internal(name: '#class', value: String),
Variable.internal(name: '#length', value: 5),
Variable.internal(name: '#encoding', value: Encoding::UTF_8),
# skip #dump member for short strings
Expand All @@ -85,7 +83,6 @@ def test_named_members_of_string
long_string = "A long string " + ('*' * 1000)

expected = [
Variable.internal(name: '#class', value: String),
Variable.internal(name: '#length', value: long_string.length),
Variable.internal(name: '#encoding', value: Encoding::UTF_8),
Variable.internal(name: '#dump', value: VariableInspector::NaiveString.new(long_string)),
Expand All @@ -95,10 +92,7 @@ def test_named_members_of_string
end

def test_named_members_of_class
expected = [
Variable.internal(name: '#class', value: Class),
Variable.internal(name: '%ancestors', value: PointStruct.ancestors.drop(1)),
]
expected = [Variable.internal(name: '%ancestors', value: PointStruct.ancestors.drop(1)),]

assert_equal expected, @inspector.named_members_of(PointStruct)
end
Expand All @@ -109,10 +103,7 @@ def test_named_members_of_module
include *ancestors
end

expected = [
Variable.internal(name: '#class', value: Module),
Variable.internal(name: '%ancestors', value: ancestors),
]
expected = [Variable.internal(name: '%ancestors', value: ancestors)]

assert_equal expected, @inspector.named_members_of(mod)
end
Expand Down Expand Up @@ -196,6 +187,31 @@ def test_named_members_of_other_objects
assert_equal expected, @inspector.named_members_of(point)
end

def test_hide_class_of_simple_values
assert_not_includes @inspector.named_members_of(nil).map(&:name), "#class"
assert_not_includes @inspector.named_members_of(true), "#class"
assert_not_includes @inspector.named_members_of(false), "#class"
assert_not_includes @inspector.named_members_of(:symbol), "#class"
assert_not_includes @inspector.named_members_of("string"), "#class"
assert_not_includes @inspector.named_members_of(123), "#class"
assert_not_includes @inspector.named_members_of(123.4), "#class"
assert_not_includes @inspector.named_members_of(Class.new), "#class"
assert_not_includes @inspector.named_members_of(Module.new), "#class"
assert_not_includes @inspector.named_members_of(Array.new), "#class"
assert_not_includes @inspector.named_members_of(Hash.new), "#class"
end

def test_show_class_of_subclasses_of_simple_types
# Skipping these classes, which can't be subclassed meaningfully (or at all):
# NilClass, TrueClass, FalseClass, Symbol, Integer, Float, Class, Module
string_subclass = Class.new(String)
array_subclass = Class.new(Array)
hash_subclass = Class.new(Hash) # E.g. HashWithIndifferentAccess would behave this way
assert_includes @inspector.named_members_of(string_subclass.new), Variable.internal(name: "#class", value: string_subclass)
assert_includes @inspector.named_members_of(array_subclass.new), Variable.internal(name: "#class", value: array_subclass)
assert_includes @inspector.named_members_of(hash_subclass.new), Variable.internal(name: "#class", value: hash_subclass)
end

private

class PointStruct < Struct.new(:x, :y, keyword_init: true)
Expand Down

0 comments on commit 697cce3

Please sign in to comment.