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

ruby-changes:70864

From: Jean <ko1@a...>
Date: Fri, 14 Jan 2022 19:30:29 +0900 (JST)
Subject: [ruby-changes:70864] 8d05047d72 (master): Add a Module#const_added callback

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

From 8d05047d72d0a4b97f57b23bddbca639375bbd03 Mon Sep 17 00:00:00 2001
From: Jean Boussier <jean.boussier@g...>
Date: Sat, 22 May 2021 12:04:01 +0200
Subject: Add a Module#const_added callback

[Feature #17881]

Works similarly to `method_added` but for constants.

```ruby
Foo::BAR = 42 # call Foo.const_added(:FOO)
class Foo::Baz; end # call Foo.const_added(:Baz)
Foo.autoload(:Something, "path") # call Foo.const_added(:Something)
```
---
 NEWS.md                                   |   1 +
 defs/id.def                               |   1 +
 object.c                                  |  23 ++++++
 spec/ruby/core/module/const_added_spec.rb | 125 ++++++++++++++++++++++++++++++
 test/ruby/test_module.rb                  |  39 ++++++++++
 test/ruby/test_settracefunc.rb            |  10 +++
 variable.c                                |  10 +++
 7 files changed, 209 insertions(+)
 create mode 100644 spec/ruby/core/module/const_added_spec.rb

diff --git a/NEWS.md b/NEWS.md
index 849188a8f11..6de6d97e8bc 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -29,6 +29,7 @@ Note: We're only listing outstanding class updates. https://github.com/ruby/ruby/blob/trunk/NEWS.md#L29
 * Module
     * Module.used_refinements has been added. [[Feature #14332]]
     * Module#refinements has been added. [[Feature #12737]]
+    * Module#const_added has been added. [[Feature #17881]]
 
 * Proc
     * Proc#dup returns an instance of subclass. [[Bug #17545]]
diff --git a/defs/id.def b/defs/id.def
index 8df6cf12e2a..097e34e4056 100644
--- a/defs/id.def
+++ b/defs/id.def
@@ -7,6 +7,7 @@ firstline, predefined = __LINE__+1, %[\ https://github.com/ruby/ruby/blob/trunk/defs/id.def#L7
   inspect
   intern
   object_id
+  const_added
   const_missing
   method_missing                                        MethodMissing
   method_added
diff --git a/object.c b/object.c
index 9243df55873..ef8a855dfbc 100644
--- a/object.c
+++ b/object.c
@@ -1005,6 +1005,28 @@ rb_class_search_ancestor(VALUE cl, VALUE c) https://github.com/ruby/ruby/blob/trunk/object.c#L1005
  */
 #define rb_obj_singleton_method_undefined rb_obj_dummy1
 
+/* Document-method: const_added
+ *
+ * call-seq:
+ *   const_added(const_name)
+ *
+ * Invoked as a callback whenever a constant is assigned on the receiver
+ *
+ *   module Chatty
+ *     def self.const_added(const_name)
+ *       super
+ *       puts "Added #{const_name.inspect}"
+ *     end
+ *     FOO = 1
+ *   end
+ *
+ * <em>produces:</em>
+ *
+ *   Added :FOO
+ *
+ */
+#define rb_obj_mod_const_added rb_obj_dummy1
+
 /*
  * Document-method: extended
  *
@@ -4419,6 +4441,7 @@ InitVM_Object(void) https://github.com/ruby/ruby/blob/trunk/object.c#L4441
     rb_define_private_method(rb_cModule, "extended", rb_obj_mod_extended, 1);
     rb_define_private_method(rb_cModule, "prepended", rb_obj_mod_prepended, 1);
     rb_define_private_method(rb_cModule, "method_added", rb_obj_mod_method_added, 1);
+    rb_define_private_method(rb_cModule, "const_added", rb_obj_mod_const_added, 1);
     rb_define_private_method(rb_cModule, "method_removed", rb_obj_mod_method_removed, 1);
     rb_define_private_method(rb_cModule, "method_undefined", rb_obj_mod_method_undefined, 1);
 
diff --git a/spec/ruby/core/module/const_added_spec.rb b/spec/ruby/core/module/const_added_spec.rb
new file mode 100644
index 00000000000..ff2ee0987fb
--- /dev/null
+++ b/spec/ruby/core/module/const_added_spec.rb
@@ -0,0 +1,125 @@ https://github.com/ruby/ruby/blob/trunk/spec/ruby/core/module/const_added_spec.rb#L1
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#const_added" do
+  ruby_version_is "3.1" do
+    it "is a private instance method" do
+      Module.should have_private_instance_method(:const_added)
+    end
+
+    it "returns nil in the default implementation" do
+      Module.new do
+        const_added(:TEST).should == nil
+      end
+    end
+
+    it "is called when a new constant is assigned on self" do
+      ScratchPad.record []
+
+      mod = Module.new do
+        def self.const_added(name)
+          ScratchPad << name
+        end
+      end
+
+      mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
+        TEST = 1
+      RUBY
+
+      ScratchPad.recorded.should == [:TEST]
+    end
+
+    it "is called when a new constant is assigned on self throught const_set" do
+      ScratchPad.record []
+
+      mod = Module.new do
+        def self.const_added(name)
+          ScratchPad << name
+        end
+      end
+
+      mod.const_set(:TEST, 1)
+
+      ScratchPad.recorded.should == [:TEST]
+    end
+
+    it "is called when a new module is defined under self" do
+      ScratchPad.record []
+
+      mod = Module.new do
+        def self.const_added(name)
+          ScratchPad << name
+        end
+      end
+
+      mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
+        module SubModule
+        end
+
+        module SubModule
+        end
+      RUBY
+
+      ScratchPad.recorded.should == [:SubModule]
+    end
+
+    it "is called when a new class is defined under self" do
+      ScratchPad.record []
+
+      mod = Module.new do
+        def self.const_added(name)
+          ScratchPad << name
+        end
+      end
+
+      mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
+        class SubClass
+        end
+
+        class SubClass
+        end
+      RUBY
+
+      ScratchPad.recorded.should == [:SubClass]
+    end
+
+    it "is called when an autoload is defined" do
+      ScratchPad.record []
+
+      mod = Module.new do
+        def self.const_added(name)
+          ScratchPad << name
+        end
+      end
+
+      mod.autoload :Autoload, "foo"
+      ScratchPad.recorded.should == [:Autoload]
+    end
+
+    it "is called with a precise caller location with the line of definition" do
+      ScratchPad.record []
+
+      mod = Module.new do
+        def self.const_added(name)
+          location = caller_locations(1, 1)[0]
+          ScratchPad << location.lineno
+        end
+      end
+
+      line = __LINE__
+      mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
+        TEST = 1
+
+        module SubModule
+        end
+
+        class SubClass
+        end
+      RUBY
+
+      mod.const_set(:CONST_SET, 1)
+
+      ScratchPad.recorded.should == [line + 2, line + 4, line + 7, line + 11]
+    end
+  end
+end
diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb
index 9368940050e..0a6959c5e64 100644
--- a/test/ruby/test_module.rb
+++ b/test/ruby/test_module.rb
@@ -1675,6 +1675,45 @@ class TestModule < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_module.rb#L1675
     assert_match(/::X\u{df}:/, c.new.to_s)
   end
 
+
+  def test_const_added
+    eval(<<~RUBY)
+      module TestConstAdded
+        @memo = []
+        class << self
+          attr_accessor :memo
+
+          def const_added(sym)
+            memo << sym
+          end
+        end
+        CONST = 1
+        module SubModule
+        end
+
+        class SubClass
+        end
+      end
+      TestConstAdded::OUTSIDE_CONST = 2
+      module TestConstAdded::OutsideSubModule; end
+      class TestConstAdded::OutsideSubClass; end
+    RUBY
+    TestConstAdded.const_set(:CONST_SET, 3)
+    assert_equal [
+      :CONST,
+      :SubModule,
+      :SubClass,
+      :OUTSIDE_CONST,
+      :OutsideSubModule,
+      :OutsideSubClass,
+      :CONST_SET,
+    ], TestConstAdded.memo
+  ensure
+    if self.class.const_defined? :TestConstAdded
+      self.class.send(:remove_const, :TestConstAdded)
+    end
+  end
+
   def test_method_added
     memo = []
     mod = Module.new do
diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb
index e973e3a384b..0524c35873c 100644
--- a/test/ruby/test_settracefunc.rb
+++ b/test/ruby/test_settracefunc.rb
@@ -108,6 +108,10 @@ class TestSetTraceFunc < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_settracefunc.rb#L108
                  events.shift)
     assert_equal(["line", 4, __method__, self.class],
                  events.shift)
+    assert_equal(["c-call", 4, :const_added, Module],
+                 events.shift)
+    assert_equal(["c-return", 4, :const_added, Module],
+                 events.shift)
     assert_equal(["c-call", 4, :inherited, Class],
                  events.shift)
     assert_equal(["c-return", 4, :inherited, Class],
@@ -345,6 +349,8 @@ class TestSetTraceFunc < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_settracefunc.rb#L349
 
     [["c-return", 2, :add_trace_func, Thread],
      ["line", 3, __method__, self.class],
+     ["c-call", 3, :const_added, Module],
+     ["c-return", 3, :const_added, Module],
      ["c-call", 3, :inherited, Class],
      ["c-return", 3, :inherited, Class],
      ["class", 3, nil, nil],
@@ -487,6 +493,8 @@ class TestSetTraceFunc < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_settracefunc.rb#L493
      [:line,     5, 'xyzzy', self.class,  method,           self,        :inner, :nothing],
      [:c_return, 4, "xyzzy", Integer,     :times,           1,           :outer, 1],
      [:line,     7, 'xyzzy', self.class,  method,           self,        :outer, :nothing],
+     [:c_call,   7, "xyzzy", Module,      :const_added,     TestSetTraceFunc, :outer, :nothing],
+     [:c_return, 7, "xyzzy", Module,      :const_added,     TestSetTraceFunc, :outer, nil],
      [:c_call,   7, "xyzzy", Class,       :inherited,       Object,      :outer, :nothing],
      [:c_return, 7, "xyzzy", Class,       :inherited,       Object,      :outer, nil],
      [:class,    7, "xyzzy", nil,         nil,              xyzzy.class, nil,    :nothing],
@@ -620,6 +628,8 @@ CODE https://github.com/ruby/ruby/blob/trunk/test/ruby/test_settracefunc.rb#L628
      [:line,     7, 'xyzzy', self.class,  method,           self,        :outer, :nothing],
      [:c_call,   7, "xyzzy", Class,       :inherited,       Object,      :outer, :nothing],
      [:c_return,  (... truncated)

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

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