[前][次][番号順一覧][スレッド一覧]

ruby-changes:69877

From: Jean <ko1@a...>
Date: Tue, 23 Nov 2021 19:49:47 +0900 (JST)
Subject: [ruby-changes:69877] c0c2b31a35 (master): Add Class#subclasses

https://git.ruby-lang.org/ruby.git/commit/?id=c0c2b31a35

From c0c2b31a35e19a47b499b57807bc0a0f9325f6d3 Mon Sep 17 00:00:00 2001
From: Jean Boussier <jean.boussier@g...>
Date: Thu, 28 Oct 2021 14:07:11 +0200
Subject: Add Class#subclasses

Implements [Feature #18273]

Returns an array containing the receiver's direct subclasses without
singleton classes.
---
 NEWS.md                                 | 16 ++++++++
 class.c                                 | 73 +++++++++++++++++++++++++--------
 gems/bundled_gems                       |  2 +-
 include/ruby/internal/intern/class.h    | 15 ++++++-
 object.c                                |  1 +
 spec/ruby/core/class/subclasses_spec.rb | 38 +++++++++++++++++
 test/ruby/test_class.rb                 | 38 +++++++++++++++++
 7 files changed, 163 insertions(+), 20 deletions(-)
 create mode 100644 spec/ruby/core/class/subclasses_spec.rb

diff --git a/NEWS.md b/NEWS.md
index 3e8035714bb..df58f5a4577 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -121,6 +121,21 @@ Outstanding ones only. https://github.com/ruby/ruby/blob/trunk/NEWS.md#L121
       C.descendants    #=> []
       ```
 
+    * Class#subclasses, which returns an array of classes
+      directly inheriting from the receiver, not
+      including singleton classes.
+      [[Feature #18273]]
+
+      ```ruby
+      class A; end
+      class B < A; end
+      class C < B; end
+      class D < A; end
+      A.subclasses    #=> [D, B]
+      B.subclasses    #=> [C]
+      C.subclasses    #=> []
+      ```
+
 * Enumerable
 
     * Enumerable#compact is added. [[Feature #17312]]
@@ -457,6 +472,7 @@ See [the repository](https://github.com/ruby/error_highlight) in detail. https://github.com/ruby/ruby/blob/trunk/NEWS.md#L472
 [Feature #18172]: https://bugs.ruby-lang.org/issues/18172
 [Feature #18229]: https://bugs.ruby-lang.org/issues/18229
 [Feature #18290]: https://bugs.ruby-lang.org/issues/18290
+[Feature #18273]: https://bugs.ruby-lang.org/issues/18273
 [GH-1509]: https://github.com/ruby/ruby/pull/1509
 [GH-4815]: https://github.com/ruby/ruby/pull/4815
 
diff --git a/class.c b/class.c
index 52750aad79d..6ad64a6efec 100644
--- a/class.c
+++ b/class.c
@@ -1377,6 +1377,7 @@ struct subclass_traverse_data https://github.com/ruby/ruby/blob/trunk/class.c#L1377
     VALUE buffer;
     long count;
     long maxcount;
+    bool immediate_only;
 };
 
 static void
@@ -1390,8 +1391,38 @@ class_descendants_recursive(VALUE klass, VALUE v) https://github.com/ruby/ruby/blob/trunk/class.c#L1391
             rb_ary_push(data->buffer, klass);
         }
         data->count++;
+        if (!data->immediate_only) {
+            rb_class_foreach_subclass(klass, class_descendants_recursive, v);
+        }
+    }
+    else {
+        rb_class_foreach_subclass(klass, class_descendants_recursive, v);
+    }
+}
+
+static VALUE
+class_descendants(VALUE klass, bool immediate_only)
+{
+    struct subclass_traverse_data data = { Qfalse, 0, -1, immediate_only };
+
+    // estimate the count of subclasses
+    rb_class_foreach_subclass(klass, class_descendants_recursive, (VALUE) &data);
+
+    // the following allocation may cause GC which may change the number of subclasses
+    data.buffer = rb_ary_new_capa(data.count);
+    data.maxcount = data.count;
+    data.count = 0;
+
+    size_t gc_count = rb_gc_count();
+
+    // enumerate subclasses
+    rb_class_foreach_subclass(klass, class_descendants_recursive, (VALUE) &data);
+
+    if (gc_count != rb_gc_count()) {
+        rb_bug("GC must not occur during the subclass iteration of Class#descendants");
     }
-    rb_class_foreach_subclass(klass, class_descendants_recursive, v);
+
+    return data.buffer;
 }
 
 /*
@@ -1415,26 +1446,32 @@ class_descendants_recursive(VALUE klass, VALUE v) https://github.com/ruby/ruby/blob/trunk/class.c#L1446
 VALUE
 rb_class_descendants(VALUE klass)
 {
-    struct subclass_traverse_data data = { Qfalse, 0, -1 };
-
-    // estimate the count of subclasses
-    rb_class_foreach_subclass(klass, class_descendants_recursive, (VALUE) &data);
-
-    // the following allocation may cause GC which may change the number of subclasses
-    data.buffer = rb_ary_new_capa(data.count);
-    data.maxcount = data.count;
-    data.count = 0;
-
-    size_t gc_count = rb_gc_count();
+    return class_descendants(klass, false);
+}
 
-    // enumerate subclasses
-    rb_class_foreach_subclass(klass, class_descendants_recursive, (VALUE) &data);
 
-    if (gc_count != rb_gc_count()) {
-	rb_bug("GC must not occur during the subclass iteration of Class#descendants");
-    }
+/*
+ *  call-seq:
+ *     subclasses -> array
+ *
+ *  Returns an array of classes where the receiver is the
+ *  direct superclass of the class, excluding singleton classes.
+ *  The order of the returned array is not defined.
+ *
+ *     class A; end
+ *     class B < A; end
+ *     class C < B; end
+ *     class D < A; end
+ *
+ *     A.subclasses        #=> [D, B]
+ *     B.subclasses        #=> [C]
+ *     C.subclasses        #=> []
+ */
 
-    return data.buffer;
+VALUE
+rb_class_subclasses(VALUE klass)
+{
+    return class_descendants(klass, true);
 }
 
 static void
diff --git a/gems/bundled_gems b/gems/bundled_gems
index a947b14ca06..53138037d8b 100644
--- a/gems/bundled_gems
+++ b/gems/bundled_gems
@@ -13,4 +13,4 @@ matrix 0.4.2 https://github.com/ruby/matrix https://github.com/ruby/ruby/blob/trunk/gems/bundled_gems#L13
 prime 0.1.2 https://github.com/ruby/prime
 rbs 1.7.1 https://github.com/ruby/rbs
 typeprof 0.20.4 https://github.com/ruby/typeprof
-debug 1.3.4 https://github.com/ruby/debug
+debug 1.3.4 https://github.com/ruby/debug 1e1bc8262fcbd33199114b7573151c7754a3e505
diff --git a/include/ruby/internal/intern/class.h b/include/ruby/internal/intern/class.h
index 835e85c26d2..2181ab93c74 100644
--- a/include/ruby/internal/intern/class.h
+++ b/include/ruby/internal/intern/class.h
@@ -158,7 +158,7 @@ VALUE rb_mod_included_modules(VALUE mod); https://github.com/ruby/ruby/blob/trunk/include/ruby/internal/intern/class.h#L158
 VALUE rb_mod_include_p(VALUE child, VALUE parent);
 
 /**
- * Queries the  module's ancestors.  This  routine gathers classes  and modules
+ * Queries the  module's ancestors.  This routine gathers classes  and modules
  * that  the  passed  module  either  inherits,  includes,  or  prepends,  then
  * recursively applies  that routine again  and again to the  collected entries
  * until the list doesn't grow up.
@@ -187,6 +187,19 @@ VALUE rb_mod_ancestors(VALUE mod); https://github.com/ruby/ruby/blob/trunk/include/ruby/internal/intern/class.h#L187
  */
 VALUE rb_class_descendants(VALUE klass);
 
+/**
+ * Queries the class's direct descendants. This  routine gathers classes that are
+ * direct subclasses of the given class,
+ * returning an array of classes that have the given class as a superclass.
+ * The returned array does not include singleton classes.
+ *
+ * @param[in]  klass A class.
+ * @return     An array of classes where `klass` is the `superclass`.
+ *
+ * @internal
+ */
+VALUE rb_class_subclasses(VALUE klass);
+
 /**
  * Generates an array of symbols, which are the list of method names defined in
  * the passed class.
diff --git a/object.c b/object.c
index 0f67b276678..705ba7ff1c4 100644
--- a/object.c
+++ b/object.c
@@ -4660,6 +4660,7 @@ InitVM_Object(void) https://github.com/ruby/ruby/blob/trunk/object.c#L4660
     rb_define_method(rb_cClass, "initialize", rb_class_initialize, -1);
     rb_define_method(rb_cClass, "superclass", rb_class_superclass, 0);
     rb_define_method(rb_cClass, "descendants", rb_class_descendants, 0); /* in class.c */
+    rb_define_method(rb_cClass, "subclasses", rb_class_subclasses, 0); /* in class.c */
     rb_define_alloc_func(rb_cClass, rb_class_s_alloc);
     rb_undef_method(rb_cClass, "extend_object");
     rb_undef_method(rb_cClass, "append_features");
diff --git a/spec/ruby/core/class/subclasses_spec.rb b/spec/ruby/core/class/subclasses_spec.rb
new file mode 100644
index 00000000000..ddbcfb02c06
--- /dev/null
+++ b/spec/ruby/core/class/subclasses_spec.rb
@@ -0,0 +1,38 @@ https://github.com/ruby/ruby/blob/trunk/spec/ruby/core/class/subclasses_spec.rb#L1
+require_relative '../../spec_helper'
+require_relative '../module/fixtures/classes'
+
+ruby_version_is '3.1' do
+  describe "Class#subclasses" do
+    it "returns a list of classes directly inheriting from self" do
+      assert_subclasses(ModuleSpecs::Parent, [ModuleSpecs::Child, ModuleSpecs::Child2])
+    end
+
+    it "does not return included modules" do
+      parent = Class.new
+      child = Class.new(parent)
+      mod = Module.new
+      parent.include(mod)
+
+      assert_subclasses(parent, [child])
+    end
+
+    it "does not return singleton classes" do
+      a = Class.new
+
+      a_obj = a.new
+      def a_obj.force_singleton_class
+        42
+      end
+
+      a.subclasses.should_not include(a_obj.singleton_class)
+    end
+
+    it "has 1 entry per module or class" do
+      ModuleSpecs::Parent.subclasses.should == ModuleSpecs::Parent.subclasses.uniq
+    end
+
+    def assert_subclasses(mod,subclasses)
+      mod.subclasses.sort_by(&:inspect).should == subclasses.sort_by(&:inspect)
+    end
+  end
+end
diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb
index eb448e952a8..0ceb36bb1d3 100644
--- a/test/ruby/test_class.rb
+++ b/test/ruby/test_class.rb
@@ -770,6 +770,44 @@ class TestClass < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_class.rb#L770
     end
   end
 
+  def test_subclasses
+    c = Class.new
+    sc = Class.new(c)
+    ssc = Class.new(sc)
+    [c, sc, ssc].each do |k|
+      k.include Module.new
+      k.new.define_singleton_method(:force_singleton_class){}
+    end
+    assert_equal([sc], c.subclasses)
+    assert_equal([ssc], sc.subclasses)
+    assert_equal([], ssc.subclasses)
+
+    object_subclasses = Object.subclasses
+    assert_include(object_subclasses, c)
+    assert_not_include(object_subclasses, sc)
+    assert_not_include(object_subclasses, ssc)
+    object_subclasses.each (... truncated)

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]