From a9bf397bd4a9319ebfc7978a2263d9ef9c35a772 Mon Sep 17 00:00:00 2001 From: Shugo Maeda Date: Mon, 28 Apr 2014 15:34:37 +0900 Subject: [PATCH] Add Module#descendents. --- class.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ include/ruby/intern.h | 1 + object.c | 1 + test/ruby/test_module.rb | 24 ++++++++++++++++++++++++ 4 files changed, 71 insertions(+) diff --git a/class.c b/class.c index 8ebc711..672a6b7 100644 --- a/class.c +++ b/class.c @@ -1065,6 +1065,51 @@ rb_mod_ancestors(VALUE mod) return ary; } +static void +add_descendents(VALUE klass, VALUE hash) +{ + rb_subclass_entry_t *e; + + if (CLASS_OF(klass) && + BUILTIN_TYPE(klass) != T_ICLASS) { + rb_hash_aset(hash, klass, Qnil); + } + for (e = RCLASS_EXT(klass)->subclasses; e; e = e->next) { + add_descendents(e->klass, hash); + } +} + +/* + * call-seq: + * mod.descendents -> array + * + * Returns a list of modules including mod or a subclass of + * mod (including mod itself). + * + * module A + * end + * + * module B + * include A + * end + * + * module C + * include A + * end + * + * A.descendents #=> [A, C, B] + */ + +VALUE +rb_mod_descendents(VALUE mod) +{ + VALUE hash = rb_hash_new(); + + rb_funcall(hash, rb_intern("compare_by_identity"), 0); + add_descendents(mod, hash); + return rb_funcall(hash, rb_intern("keys"), 0); +} + #define VISI(x) ((x)&NOEX_MASK) #define VISI_CHECK(x,f) (VISI(x) == (f)) diff --git a/include/ruby/intern.h b/include/ruby/intern.h index f36f192..ebaca8e 100644 --- a/include/ruby/intern.h +++ b/include/ruby/intern.h @@ -199,6 +199,7 @@ VALUE rb_include_class_new(VALUE, VALUE); VALUE rb_mod_included_modules(VALUE); VALUE rb_mod_include_p(VALUE, VALUE); VALUE rb_mod_ancestors(VALUE); +VALUE rb_mod_descendents(VALUE); VALUE rb_class_instance_methods(int, VALUE*, VALUE); VALUE rb_class_public_instance_methods(int, VALUE*, VALUE); VALUE rb_class_protected_instance_methods(int, VALUE*, VALUE); diff --git a/object.c b/object.c index e5e0da4..37b8897 100644 --- a/object.c +++ b/object.c @@ -3377,6 +3377,7 @@ Init_Object(void) rb_define_method(rb_cModule, "include?", rb_mod_include_p, 1); /* in class.c */ rb_define_method(rb_cModule, "name", rb_mod_name, 0); /* in variable.c */ rb_define_method(rb_cModule, "ancestors", rb_mod_ancestors, 0); /* in class.c */ + rb_define_method(rb_cModule, "descendents", rb_mod_descendents, 0); /* in class.c */ rb_define_private_method(rb_cModule, "attr", rb_mod_attr, -1); rb_define_private_method(rb_cModule, "attr_reader", rb_mod_attr_reader, -1); diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index b5e128b..5a738d2 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -208,6 +208,30 @@ class TestModule < Test::Unit::TestCase assert_equal([String, Comparable, Object, Kernel, BasicObject], String.ancestors - mixins) end + private def assert_same_set(expected, actual) + assert_equal(expected.sort_by(&:object_id), + actual.sort_by(&:object_id)) + end + + def test_descendents + assert_same_set([Mixin, User], Mixin.descendents) + assert_same_set([User], User.descendents) + + m1 = Module.new + m2 = Module.new { include m1 } + m3 = Module.new { include m2 } + assert_same_set([m1, m2, m3], m1.descendents) + assert_same_set([m2, m3], m2.descendents) + assert_same_set([m3], m3.descendents) + + c1 = Class.new + c2 = Class.new(c1) + c3 = Class.new(c2) + assert_same_set([c1, c2, c3], c1.descendents) + assert_same_set([c2, c3], c2.descendents) + assert_same_set([c3], c3.descendents) + end + CLASS_EVAL = 2 @@class_eval = 'b' -- 1.7.9.6