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
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
|
||||
|
||||
|
75
class.c
75
class.c
@ -1377,6 +1377,7 @@ struct subclass_traverse_data
|
||||
VALUE buffer;
|
||||
long count;
|
||||
long maxcount;
|
||||
bool immediate_only;
|
||||
};
|
||||
|
||||
static void
|
||||
@ -1390,9 +1391,39 @@ 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);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
@ -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 };
|
||||
|
||||
// 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 class_descendants(klass, false);
|
||||
}
|
||||
|
||||
return data.buffer;
|
||||
|
||||
/*
|
||||
* 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 #=> []
|
||||
*/
|
||||
|
||||
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
|
||||
|
@ -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…
x
Reference in New Issue
Block a user