Skip to content

Commit e26d7f7

Browse files
committed
[GR-19220] method_added trigger fixes (#2875)
PullRequest: truffleruby/3659
2 parents 925cadc + f4d4489 commit e26d7f7

File tree

3 files changed

+82
-11
lines changed

3 files changed

+82
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Bug fixes:
2121
* Fix `Array#pack` and accept `Numeric` values when `Float` is expected (#2815, @andrykonchin).
2222
* Fix `\P{}` matching in regular expressions (#2798, @andrykonchin).
2323
* Fix constants lookup when `BasicObject#instance_eval` method is called with a String (#2810, @andrykonchin).
24+
* Don't trigger the `method_added` event when changing a method's visibility or calling `module_function` (@paracycle, @nirvdrum).
2425

2526
Compatibility:
2627

spec/ruby/core/module/method_added_spec.rb

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
require_relative 'fixtures/classes'
33

44
describe "Module#method_added" do
5+
before :each do
6+
ScratchPad.record []
7+
end
8+
59
it "is a private instance method" do
610
Module.should have_private_instance_method(:method_added)
711
end
@@ -13,8 +17,6 @@
1317
end
1418

1519
it "is called when a new instance method is defined in self" do
16-
ScratchPad.record []
17-
1820
Module.new do
1921
def self.method_added(name)
2022
ScratchPad << name
@@ -32,8 +34,6 @@ def test() end
3234

3335
it "is not called when a singleton method is added" do
3436
# obj.singleton_method_added is called instead
35-
ScratchPad.record []
36-
3737
klass = Class.new
3838
def klass.method_added(name)
3939
ScratchPad << name
@@ -60,8 +60,71 @@ def self.method_added(name)
6060
m.should_not have_method(:method_to_undef)
6161
end
6262

63+
it "is not called when a method changes visibility" do
64+
Module.new do
65+
def public_method
66+
end
67+
68+
def private_method
69+
end
70+
71+
def self.method_added(name)
72+
ScratchPad << name
73+
end
74+
75+
public :public_method
76+
private :public_method
77+
78+
private :private_method
79+
public :private_method
80+
end
81+
82+
ScratchPad.recorded.should == []
83+
end
84+
85+
it "is called when using #private in a subclass" do
86+
parent = Class.new do
87+
def foo
88+
end
89+
end
90+
91+
Class.new(parent) do
92+
def self.method_added(name)
93+
ScratchPad << name
94+
end
95+
96+
# Create an instance as that might initialize some method lookup caches, which is interesting to test
97+
self.new.foo
98+
99+
private :foo
100+
public :foo
101+
end
102+
103+
ScratchPad.recorded.should == [:foo]
104+
end
105+
106+
it "is not called when a method is copied via module_function, rather #singleton_method_added is called" do
107+
Module.new do
108+
def mod_function
109+
end
110+
111+
def self.method_added(name)
112+
ScratchPad << [:method_added, name]
113+
end
114+
115+
def self.singleton_method_added(name)
116+
ScratchPad << [:singleton_method_added, name]
117+
end
118+
119+
ScratchPad.record []
120+
121+
module_function :mod_function
122+
end
123+
124+
ScratchPad.recorded.should == [[:singleton_method_added, :mod_function]]
125+
end
126+
63127
it "is called with a precise caller location with the line of the 'def'" do
64-
ScratchPad.record []
65128
line = nil
66129

67130
Module.new do

src/main/java/org/truffleruby/core/module/ModuleFields.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -533,12 +533,19 @@ public void addMethod(RubyContext context, Node currentNode, InternalMethod meth
533533
}
534534

535535
if (context.getCoreLibrary().isLoaded() && !method.isUndefined()) {
536-
final RubySymbol methodSymbol = context.getLanguageSlow().getSymbol(method.getName());
537-
if (RubyGuards.isSingletonClass(rubyModule)) {
538-
RubyDynamicObject receiver = ((RubyClass) rubyModule).attached;
539-
RubyContext.send(currentNode, receiver, "singleton_method_added", methodSymbol);
540-
} else {
541-
RubyContext.send(currentNode, rubyModule, "method_added", methodSymbol);
536+
/* method_added/singleton_method_added should only be called if there wasn't already the same method
537+
* definition for the given name on this rubyModule. Otherwise, it's just a change of visibility or so, not
538+
* "a new method", and that should not call method_added/singleton_method_added. */
539+
if (previousMethodEntry == null || previousMethodEntry.getMethod() == null ||
540+
previousMethodEntry.getMethod().getSharedMethodInfo() != method.getSharedMethodInfo()) {
541+
542+
final RubySymbol methodSymbol = context.getLanguageSlow().getSymbol(method.getName());
543+
if (RubyGuards.isSingletonClass(rubyModule)) {
544+
RubyDynamicObject receiver = ((RubyClass) rubyModule).attached;
545+
RubyContext.send(currentNode, receiver, "singleton_method_added", methodSymbol);
546+
} else {
547+
RubyContext.send(currentNode, rubyModule, "method_added", methodSymbol);
548+
}
542549
}
543550
}
544551
}

0 commit comments

Comments
 (0)