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) ```
This commit is contained in:
parent
53a4e10146
commit
8d05047d72
Notes:
git
2022-01-14 19:30:36 +09:00
1
NEWS.md
1
NEWS.md
@ -29,6 +29,7 @@ Note: We're only listing outstanding class updates.
|
|||||||
* Module
|
* Module
|
||||||
* Module.used_refinements has been added. [[Feature #14332]]
|
* Module.used_refinements has been added. [[Feature #14332]]
|
||||||
* Module#refinements has been added. [[Feature #12737]]
|
* Module#refinements has been added. [[Feature #12737]]
|
||||||
|
* Module#const_added has been added. [[Feature #17881]]
|
||||||
|
|
||||||
* Proc
|
* Proc
|
||||||
* Proc#dup returns an instance of subclass. [[Bug #17545]]
|
* Proc#dup returns an instance of subclass. [[Bug #17545]]
|
||||||
|
@ -7,6 +7,7 @@ firstline, predefined = __LINE__+1, %[\
|
|||||||
inspect
|
inspect
|
||||||
intern
|
intern
|
||||||
object_id
|
object_id
|
||||||
|
const_added
|
||||||
const_missing
|
const_missing
|
||||||
method_missing MethodMissing
|
method_missing MethodMissing
|
||||||
method_added
|
method_added
|
||||||
|
23
object.c
23
object.c
@ -1005,6 +1005,28 @@ rb_class_search_ancestor(VALUE cl, VALUE c)
|
|||||||
*/
|
*/
|
||||||
#define rb_obj_singleton_method_undefined rb_obj_dummy1
|
#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
|
* 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, "extended", rb_obj_mod_extended, 1);
|
||||||
rb_define_private_method(rb_cModule, "prepended", rb_obj_mod_prepended, 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, "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_removed", rb_obj_mod_method_removed, 1);
|
||||||
rb_define_private_method(rb_cModule, "method_undefined", rb_obj_mod_method_undefined, 1);
|
rb_define_private_method(rb_cModule, "method_undefined", rb_obj_mod_method_undefined, 1);
|
||||||
|
|
||||||
|
125
spec/ruby/core/module/const_added_spec.rb
Normal file
125
spec/ruby/core/module/const_added_spec.rb
Normal file
@ -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
|
@ -1675,6 +1675,45 @@ class TestModule < Test::Unit::TestCase
|
|||||||
assert_match(/::X\u{df}:/, c.new.to_s)
|
assert_match(/::X\u{df}:/, c.new.to_s)
|
||||||
end
|
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
|
def test_method_added
|
||||||
memo = []
|
memo = []
|
||||||
mod = Module.new do
|
mod = Module.new do
|
||||||
|
@ -108,6 +108,10 @@ class TestSetTraceFunc < Test::Unit::TestCase
|
|||||||
events.shift)
|
events.shift)
|
||||||
assert_equal(["line", 4, __method__, self.class],
|
assert_equal(["line", 4, __method__, self.class],
|
||||||
events.shift)
|
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],
|
assert_equal(["c-call", 4, :inherited, Class],
|
||||||
events.shift)
|
events.shift)
|
||||||
assert_equal(["c-return", 4, :inherited, Class],
|
assert_equal(["c-return", 4, :inherited, Class],
|
||||||
@ -345,6 +349,8 @@ class TestSetTraceFunc < Test::Unit::TestCase
|
|||||||
|
|
||||||
[["c-return", 2, :add_trace_func, Thread],
|
[["c-return", 2, :add_trace_func, Thread],
|
||||||
["line", 3, __method__, self.class],
|
["line", 3, __method__, self.class],
|
||||||
|
["c-call", 3, :const_added, Module],
|
||||||
|
["c-return", 3, :const_added, Module],
|
||||||
["c-call", 3, :inherited, Class],
|
["c-call", 3, :inherited, Class],
|
||||||
["c-return", 3, :inherited, Class],
|
["c-return", 3, :inherited, Class],
|
||||||
["class", 3, nil, nil],
|
["class", 3, nil, nil],
|
||||||
@ -487,6 +493,8 @@ class TestSetTraceFunc < Test::Unit::TestCase
|
|||||||
[:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing],
|
[:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing],
|
||||||
[:c_return, 4, "xyzzy", Integer, :times, 1, :outer, 1],
|
[:c_return, 4, "xyzzy", Integer, :times, 1, :outer, 1],
|
||||||
[:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing],
|
[: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_call, 7, "xyzzy", Class, :inherited, Object, :outer, :nothing],
|
||||||
[:c_return, 7, "xyzzy", Class, :inherited, Object, :outer, nil],
|
[:c_return, 7, "xyzzy", Class, :inherited, Object, :outer, nil],
|
||||||
[:class, 7, "xyzzy", nil, nil, xyzzy.class, nil, :nothing],
|
[:class, 7, "xyzzy", nil, nil, xyzzy.class, nil, :nothing],
|
||||||
@ -620,6 +628,8 @@ CODE
|
|||||||
[:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing],
|
[:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing],
|
||||||
[:c_call, 7, "xyzzy", Class, :inherited, Object, :outer, :nothing],
|
[:c_call, 7, "xyzzy", Class, :inherited, Object, :outer, :nothing],
|
||||||
[:c_return, 7, "xyzzy", Class, :inherited, Object, :outer, nil],
|
[: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],
|
[:class, 7, "xyzzy", nil, nil, xyzzy.class, nil, :nothing],
|
||||||
[:line, 8, "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],
|
[:line, 9, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
|
||||||
|
10
variable.c
10
variable.c
@ -3102,6 +3102,15 @@ set_namespace_path(VALUE named_namespace, VALUE namespace_path)
|
|||||||
RB_VM_LOCK_LEAVE();
|
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
|
void
|
||||||
rb_const_set(VALUE klass, ID id, VALUE val)
|
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 *
|
static struct autoload_data_i *
|
||||||
|
Loading…
x
Reference in New Issue
Block a user