mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	Add Class#subclasses
Implements [Feature #18273] Returns an array containing the receiver's direct subclasses without singleton classes.
This commit is contained in:
		
							parent
							
								
									a88b19d3d0
								
							
						
					
					
						commit
						c0c2b31a35
					
				
				
				Notes:
				
					git
				
				2021-11-23 19:49:51 +09:00 
				
			
			
			
		
		
					 7 changed files with 163 additions and 20 deletions
				
			
		
							
								
								
									
										16
									
								
								NEWS.md
									
										
									
									
									
								
							
							
						
						
									
										16
									
								
								NEWS.md
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -121,6 +121,21 @@ Outstanding ones only.
 | 
			
		|||
      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.
 | 
			
		|||
[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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										73
									
								
								class.c
									
										
									
									
									
								
							
							
						
						
									
										73
									
								
								class.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1377,6 +1377,7 @@ struct subclass_traverse_data
 | 
			
		|||
    VALUE buffer;
 | 
			
		||||
    long count;
 | 
			
		||||
    long maxcount;
 | 
			
		||||
    bool immediate_only;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
| 
						 | 
				
			
			@ -1390,8 +1391,38 @@ class_descendants_recursive(VALUE klass, VALUE v)
 | 
			
		|||
            rb_ary_push(data->buffer, klass);
 | 
			
		||||
        }
 | 
			
		||||
        data->count++;
 | 
			
		||||
        if (!data->immediate_only) {
 | 
			
		||||
            rb_class_foreach_subclass(klass, class_descendants_recursive, v);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    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");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return data.buffer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
| 
						 | 
				
			
			@ -1415,26 +1446,32 @@ class_descendants_recursive(VALUE klass, VALUE v)
 | 
			
		|||
VALUE
 | 
			
		||||
rb_class_descendants(VALUE klass)
 | 
			
		||||
{
 | 
			
		||||
    struct subclass_traverse_data data = { Qfalse, 0, -1 };
 | 
			
		||||
    return class_descendants(klass, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    // 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;
 | 
			
		||||
/*
 | 
			
		||||
 *  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        #=> []
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
    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");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return data.buffer;
 | 
			
		||||
VALUE
 | 
			
		||||
rb_class_subclasses(VALUE klass)
 | 
			
		||||
{
 | 
			
		||||
    return class_descendants(klass, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,4 +13,4 @@ matrix 0.4.2 https://github.com/ruby/matrix
 | 
			
		|||
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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -158,7 +158,7 @@ VALUE rb_mod_included_modules(VALUE mod);
 | 
			
		|||
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);
 | 
			
		|||
 */
 | 
			
		||||
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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										1
									
								
								object.c
									
										
									
									
									
								
							
							
						
						
									
										1
									
								
								object.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -4660,6 +4660,7 @@ InitVM_Object(void)
 | 
			
		|||
    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");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										38
									
								
								spec/ruby/core/class/subclasses_spec.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								spec/ruby/core/class/subclasses_spec.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
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
 | 
			
		||||
| 
						 | 
				
			
			@ -770,6 +770,44 @@ class TestClass < Test::Unit::TestCase
 | 
			
		|||
    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 do |subclass|
 | 
			
		||||
      assert_equal Object, subclass.superclass, "Expected #{subclass}.superclass to be Object"
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_subclass_gc
 | 
			
		||||
    c = Class.new
 | 
			
		||||
    100000.times do
 | 
			
		||||
      cc = Class.new(c)
 | 
			
		||||
      100.times { Class.new(cc) }
 | 
			
		||||
    end
 | 
			
		||||
    assert(c.subclasses.size <= 100000)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_subclass_gc_stress
 | 
			
		||||
    10000.times do
 | 
			
		||||
      c = Class.new
 | 
			
		||||
      100.times { Class.new(c) }
 | 
			
		||||
      assert(c.subclasses.size <= 100)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_classext_memory_leak
 | 
			
		||||
    assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true)
 | 
			
		||||
code = proc { Class.new }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue