Skip to content

Commit f4135fe

Browse files
nobubyroot
andcommitted
[Feature #21219] Selective inspect of instance variables
Make Kernel#inspect ask which instance variables should be dumped by the result of `#instance_variables_to_inspect`. Co-Authored-By: Jean Boussier <[email protected]>
1 parent e809494 commit f4135fe

File tree

4 files changed

+111
-8
lines changed

4 files changed

+111
-8
lines changed

NEWS.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,26 @@ Note that each entry is kept to a minimum, see links for details.
1414

1515
Note: We're only listing outstanding class updates.
1616

17+
* Kernel
18+
19+
* `Kernel#inspect` now check for the existence of a `#instance_variables_to_inspect` method
20+
allowing to control which instance variables are displayed in the `#inspect` string:
21+
22+
```ruby
23+
class DatabaseConfig
24+
def initialize(host, user, password)
25+
@host = host
26+
@user = user
27+
@password = password
28+
end
29+
30+
private def instance_variables_to_inspect = [:@host, :@user]
31+
end
32+
33+
conf = DatabaseConfig.new("localhost", "root", "hunter2")
34+
conf.inspect #=> #<DatabaseConfig:0x0000000104def350 @host="localhost", @user="root">
35+
```
36+
1737
* Binding
1838

1939
* `Binding#local_variables` does no longer include numbered parameters.

object.c

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ static VALUE rb_cFalseClass_to_s;
8383
#define id_init_dup idInitialize_dup
8484
#define id_const_missing idConst_missing
8585
#define id_to_f idTo_f
86+
static ID id_instance_variables_to_inspect;
8687

8788
#define CLASS_OR_MODULE_P(obj) \
8889
(!SPECIAL_CONST_P(obj) && \
@@ -733,11 +734,17 @@ rb_inspect(VALUE obj)
733734
static int
734735
inspect_i(ID id, VALUE value, st_data_t a)
735736
{
736-
VALUE str = (VALUE)a;
737+
VALUE *args = (VALUE *)a, str = args[0], ivars = args[1];
737738

738739
/* need not to show internal data */
739740
if (CLASS_OF(value) == 0) return ST_CONTINUE;
740741
if (!rb_is_instance_id(id)) return ST_CONTINUE;
742+
if (!NIL_P(ivars)) {
743+
VALUE name = ID2SYM(id);
744+
for (long i = 0; RARRAY_AREF(ivars, i) != name; ) {
745+
if (++i >= RARRAY_LEN(ivars)) return ST_CONTINUE;
746+
}
747+
}
741748
if (RSTRING_PTR(str)[0] == '-') { /* first element */
742749
RSTRING_PTR(str)[0] = '#';
743750
rb_str_cat2(str, " ");
@@ -752,13 +759,15 @@ inspect_i(ID id, VALUE value, st_data_t a)
752759
}
753760

754761
static VALUE
755-
inspect_obj(VALUE obj, VALUE str, int recur)
762+
inspect_obj(VALUE obj, VALUE a, int recur)
756763
{
764+
VALUE *args = (VALUE *)a, str = args[0];
765+
757766
if (recur) {
758767
rb_str_cat2(str, " ...");
759768
}
760769
else {
761-
rb_ivar_foreach(obj, inspect_i, str);
770+
rb_ivar_foreach(obj, inspect_i, a);
762771
}
763772
rb_str_cat2(str, ">");
764773
RSTRING_PTR(str)[0] = '#';
@@ -791,17 +800,47 @@ inspect_obj(VALUE obj, VALUE str, int recur)
791800
* end
792801
* end
793802
* Bar.new.inspect #=> "#<Bar:0x0300c868 @bar=1>"
803+
*
804+
* If _obj_ responds to +instance_variables_to_inspect+, then only
805+
* the instance variables listed in the returned array will be included
806+
* in the inspect string.
807+
*
808+
*
809+
* class DatabaseConfig
810+
* def initialize(host, user, password)
811+
* @host = host
812+
* @user = user
813+
* @password = password
814+
* end
815+
*
816+
* private
817+
* def instance_variables_to_inspect = [:@host, :@user]
818+
* end
819+
*
820+
* conf = DatabaseConfig.new("localhost", "root", "hunter2")
821+
* conf.inspect #=> #<DatabaseConfig:0x0000000104def350 @host="localhost", @user="root">
794822
*/
795823

796824
static VALUE
797825
rb_obj_inspect(VALUE obj)
798826
{
799-
if (rb_ivar_count(obj) > 0) {
800-
VALUE str;
827+
VALUE ivars = rb_check_funcall(obj, id_instance_variables_to_inspect, 0, 0);
828+
st_index_t n = 0;
829+
if (UNDEF_P(ivars)) {
830+
n = rb_ivar_count(obj);
831+
ivars = Qnil;
832+
}
833+
else if (!NIL_P(ivars)) {
834+
Check_Type(ivars, T_ARRAY);
835+
n = RARRAY_LEN(ivars);
836+
}
837+
if (n > 0) {
801838
VALUE c = rb_class_name(CLASS_OF(obj));
802-
803-
str = rb_sprintf("-<%"PRIsVALUE":%p", c, (void*)obj);
804-
return rb_exec_recursive(inspect_obj, obj, str);
839+
VALUE args[2] = {
840+
rb_sprintf("-<%"PRIsVALUE":%p", c, (void*)obj),
841+
ivars
842+
};
843+
return rb_exec_recursive(inspect_obj, obj, (VALUE)args);
805844
}
806845
else {
807846
return rb_any_to_s(obj);
@@ -4600,6 +4639,7 @@ void
46004639
Init_Object(void)
46014640
{
46024641
id_dig = rb_intern_const("dig");
4642+
id_instance_variables_to_inspect = rb_intern_const("instance_variables_to_inspect");
46034643
InitVM(Object);
46044644
}
46054645

spec/ruby/core/kernel/inspect_spec.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,34 @@ class << obj
2828
end
2929
obj.inspect.should be_kind_of(String)
3030
end
31+
32+
ruby_version_is "3.5" do
33+
it "calls #instance_variables_to_inspect private method to know which variables to display" do
34+
obj = Object.new
35+
obj.instance_eval do
36+
@host = "localhost"
37+
@user = "root"
38+
@password = "hunter2"
39+
end
40+
obj.singleton_class.class_eval do
41+
private def instance_variables_to_inspect = %i[@host @user @does_not_exist]
42+
end
43+
44+
inspected = obj.inspect.sub(/^#<Object:0x[0-9a-f]+/, '#<Object:0x00')
45+
inspected.should == '#<Object:0x00 @host="localhost", @user="root">'
46+
47+
obj = Object.new
48+
obj.instance_eval do
49+
@host = "localhost"
50+
@user = "root"
51+
@password = "hunter2"
52+
end
53+
obj.singleton_class.class_eval do
54+
private def instance_variables_to_inspect = []
55+
end
56+
57+
inspected = obj.inspect.sub(/^#<Object:0x[0-9a-f]+/, '#<Object:0x00')
58+
inspected.should == "#<Object:0x00>"
59+
end
60+
end
3161
end

test/ruby/test_object.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,19 @@ def initialize
950950
assert_match(/\bInspect\u{3042}:.* @\u{3044}=42\b/, x.inspect)
951951
x.instance_variable_set("@\u{3046}".encode(Encoding::EUC_JP), 6)
952952
assert_match(/@\u{3046}=6\b/, x.inspect)
953+
954+
x = Object.new
955+
x.singleton_class.class_eval do
956+
private def instance_variables_to_inspect = [:@host, :@user]
957+
end
958+
959+
x.instance_variable_set(:@host, "localhost")
960+
x.instance_variable_set(:@user, "root")
961+
x.instance_variable_set(:@password, "hunter2")
962+
s = x.inspect
963+
assert_include(s, "@host=\"localhost\"")
964+
assert_include(s, "@user=\"root\"")
965+
assert_not_include(s, "@password=")
953966
end
954967

955968
def test_singleton_methods

0 commit comments

Comments
 (0)