summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUfuk Kayserilioglu <ufuk@paralaus.com>2022-09-27 01:19:22 +0300
committerJean Boussier <jean.boussier@gmail.com>2022-10-20 17:30:17 +0200
commit0378e2f4a8319440dd65c82b16f189161472d237 (patch)
tree6c2c6dd91c624fd4ae6ad4be5dd3a6d04528df37
parent192bc725290ca4b271bff2bae6123d84c25f7173 (diff)
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.
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/6450
-rw-r--r--NEWS.md16
-rw-r--r--class.c27
-rw-r--r--include/ruby/internal/intern/class.h12
-rw-r--r--object.c1
-rw-r--r--spec/ruby/core/class/attached_object_spec.rb31
-rw-r--r--test/ruby/test_class.rb25
6 files changed, 112 insertions, 0 deletions
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:0x000000010491a370>
+ 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 #=> #<Foo:0x000000010491a370>
+ * 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