summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS.md1
-rw-r--r--defs/id.def1
-rw-r--r--object.c23
-rw-r--r--spec/ruby/core/module/const_added_spec.rb125
-rw-r--r--test/ruby/test_module.rb39
-rw-r--r--test/ruby/test_settracefunc.rb10
-rw-r--r--variable.c10
7 files changed, 209 insertions, 0 deletions
diff --git a/NEWS.md b/NEWS.md
index 849188a8f1..6de6d97e8b 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -29,6 +29,7 @@ Note: We're only listing outstanding class updates.
* 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 8df6cf12e2..097e34e405 100644
--- a/defs/id.def
+++ b/defs/id.def
@@ -7,6 +7,7 @@ firstline, predefined = __LINE__+1, %[\
inspect
intern
object_id
+ const_added
const_missing
method_missing MethodMissing
method_added
diff --git a/object.c b/object.c
index 9243df5587..ef8a855dfb 100644
--- a/object.c
+++ b/object.c
@@ -1005,6 +1005,28 @@ rb_class_search_ancestor(VALUE cl, VALUE c)
*/
#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)
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 0000000000..ff2ee0987f
--- /dev/null
+++ b/spec/ruby/core/module/const_added_spec.rb
@@ -0,0 +1,125 @@
+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 9368940050..0a6959c5e6 100644
--- a/test/ruby/test_module.rb
+++ b/test/ruby/test_module.rb
@@ -1675,6 +1675,45 @@ class TestModule < Test::Unit::TestCase
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 e973e3a384..0524c35873 100644
--- a/test/ruby/test_settracefunc.rb
+++ b/test/ruby/test_settracefunc.rb
@@ -108,6 +108,10 @@ class TestSetTraceFunc < Test::Unit::TestCase
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
[["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
[: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
[:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing],
[:c_call, 7, "xyzzy", Class, :inherited, Object, :outer, :nothing],
[:c_return, 7, "xyzzy", Class, :inherited, Object, :outer, nil],
+ [:c_call, 7, "xyzzy", Class, :const_added, Object, :outer, :nothing],
+ [:c_return, 7, "xyzzy", Class, :const_added, Object, :outer, nil],
[:class, 7, "xyzzy", nil, nil, xyzzy.class, nil, :nothing],
[:line, 8, "xyzzy", nil, nil, xyzzy.class, nil, :nothing],
[:line, 9, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
diff --git a/variable.c b/variable.c
index eaad6eb497..ebfbf017c1 100644
--- a/variable.c
+++ b/variable.c
@@ -3102,6 +3102,15 @@ set_namespace_path(VALUE named_namespace, VALUE namespace_path)
RB_VM_LOCK_LEAVE();
}
+static void
+const_added(VALUE klass, ID const_name)
+{
+ if (GET_VM()->running) {
+ VALUE name = ID2SYM(const_name);
+ rb_funcallv(klass, idConst_added, 1, &name);
+ }
+}
+
void
rb_const_set(VALUE klass, ID id, VALUE val)
{
@@ -3166,6 +3175,7 @@ rb_const_set(VALUE klass, ID id, VALUE val)
}
}
}
+ const_added(klass, id);
}
static struct autoload_data_i *