From 0378e2f4a8319440dd65c82b16f189161472d237 Mon Sep 17 00:00:00 2001 From: Ufuk Kayserilioglu Date: Tue, 27 Sep 2022 01:19:22 +0300 Subject: Add Class#attached_object Implements [Feature #12084] Returns the object for which the receiver is the singleton class, or raises TypeError if the receiver is not a singleton class. --- NEWS.md | 16 ++++++++++++++ class.c | 27 ++++++++++++++++++++++++ include/ruby/internal/intern/class.h | 12 +++++++++++ object.c | 1 + spec/ruby/core/class/attached_object_spec.rb | 31 ++++++++++++++++++++++++++++ test/ruby/test_class.rb | 25 ++++++++++++++++++++++ 6 files changed, 112 insertions(+) create mode 100644 spec/ruby/core/class/attached_object_spec.rb diff --git a/NEWS.md b/NEWS.md index 3d618c5abb..9205b6e669 100644 --- a/NEWS.md +++ b/NEWS.md @@ -115,6 +115,21 @@ Note: We're only listing outstanding class updates. STDIN.read # => Blocking operation timed out! (IO::TimeoutError) ``` +* Class + * `Class#attached_object`, which returns the object for which + the receiver is the singleton class. Raises `TypeError` if the + receiver is not a singleton class. + [[Feature #12084]] + + ```ruby + class Foo; end + + Foo.singleton_class.attached_object #=> Foo + Foo.new.singleton_class.attached_object #=> # + Foo.attached_object #=> TypeError: `Foo' is not a singleton class + nil.singleton_class.attached_object #=> TypeError: `NilClass' is not a singleton class + ``` + * Data * New core class to represent simple immutable value object. The class is similar to `Struct` and partially shares an implementation, but has more @@ -323,6 +338,7 @@ The following deprecated APIs are removed. ## Miscellaneous changes [Feature #12005]: https://bugs.ruby-lang.org/issues/12005 +[Feature #12084]: https://bugs.ruby-lang.org/issues/12084 [Feature #12655]: https://bugs.ruby-lang.org/issues/12655 [Feature #12737]: https://bugs.ruby-lang.org/issues/12737 [Feature #13110]: https://bugs.ruby-lang.org/issues/13110 diff --git a/class.c b/class.c index 7a32951e60..b0d23153de 100644 --- a/class.c +++ b/class.c @@ -1589,6 +1589,33 @@ rb_class_subclasses(VALUE klass) return class_descendants(klass, true); } +/* + * call-seq: + * attached_object -> object + * + * Returns the object for which the receiver is the singleton class. + * + * Raises an TypeError if the class is not a singleton class. + * + * class Foo; end + * + * Foo.singleton_class.attached_object #=> Foo + * Foo.attached_object #=> TypeError: `Foo' is not a singleton class + * Foo.new.singleton_class.attached_object #=> # + * TrueClass.attached_object #=> TypeError: `TrueClass' is not a singleton class + * NilClass.attached_object #=> TypeError: `NilClass' is not a singleton class + */ + +VALUE +rb_class_attached_object(VALUE klass) +{ + if (!FL_TEST(klass, FL_SINGLETON)) { + rb_raise(rb_eTypeError, "`%"PRIsVALUE"' is not a singleton class", klass); + } + + return rb_attr_get(klass, id_attached); +} + static void ins_methods_push(st_data_t name, st_data_t ary) { diff --git a/include/ruby/internal/intern/class.h b/include/ruby/internal/intern/class.h index 2181ab93c7..0fb2d001bc 100644 --- a/include/ruby/internal/intern/class.h +++ b/include/ruby/internal/intern/class.h @@ -200,6 +200,18 @@ VALUE rb_class_descendants(VALUE klass); */ VALUE rb_class_subclasses(VALUE klass); + +/** + * Returns the attached object for a singleton class. + * If the given class is not a singleton class, raises a TypeError. + * + * @param[in] klass A class. + * @return The object which has the singleton class `klass`. + * + * @internal + */ +VALUE rb_class_attached_object(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 0d99c6db47..fd07944d14 100644 --- a/object.c +++ b/object.c @@ -4464,6 +4464,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, "subclasses", rb_class_subclasses, 0); /* in class.c */ + rb_define_method(rb_cClass, "attached_object", rb_class_attached_object, 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/attached_object_spec.rb b/spec/ruby/core/class/attached_object_spec.rb new file mode 100644 index 0000000000..115d5fa563 --- /dev/null +++ b/spec/ruby/core/class/attached_object_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../spec_helper' + +ruby_version_is '3.2' do + describe "Class#attached_object" do + it "returns the object that is attached to a singleton class" do + a = Class.new + + a_obj = a.new + a_obj.singleton_class.attached_object.should == a_obj + end + + it "returns the class object that is attached to a class's singleton class" do + a = Class.new + singleton_class = (class << a; self; end) + + singleton_class.attached_object.should == a + end + + it "raises TypeError if the class is not a singleton class" do + a = Class.new + + -> { a.attached_object }.should raise_error(TypeError) + end + + it "raises TypeError for special singleton classes" do + -> { nil.singleton_class.attached_object }.should raise_error(TypeError) + -> { true.singleton_class.attached_object }.should raise_error(TypeError) + -> { false.singleton_class.attached_object }.should raise_error(TypeError) + end + end +end diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb index 07c34ce9d5..8d9fde144e 100644 --- a/test/ruby/test_class.rb +++ b/test/ruby/test_class.rb @@ -759,6 +759,31 @@ class TestClass < Test::Unit::TestCase end end + def test_attached_object + c = Class.new + sc = c.singleton_class + obj = c.new + + assert_equal(obj, obj.singleton_class.attached_object) + assert_equal(c, sc.attached_object) + + assert_raise_with_message(TypeError, /is not a singleton class/) do + c.attached_object + end + + assert_raise_with_message(TypeError, /`NilClass' is not a singleton class/) do + nil.singleton_class.attached_object + end + + assert_raise_with_message(TypeError, /`FalseClass' is not a singleton class/) do + false.singleton_class.attached_object + end + + assert_raise_with_message(TypeError, /`TrueClass' is not a singleton class/) do + true.singleton_class.attached_object + end + end + def test_subclass_gc c = Class.new 10_000.times do -- cgit v1.2.3