Skip to content

Commit 3c5f2de

Browse files
committed
Fix Module#define_method and apply module_function visibility
1 parent b6c229a commit 3c5f2de

File tree

3 files changed

+100
-4
lines changed

3 files changed

+100
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Bug fixes:
99
* Fix class lookup after an object's class has been replaced by `IO#reopen` (@itarato, @eregon).
1010
* Fix `Marshal.load` and raise `ArgumentError` when dump is broken and is too short (#3108, @andrykonchin).
1111
* Fix `super` method lookup for unbounded attached methods (#3131, @itarato).
12+
* Fix `Module#define_method(name, Method)` to respect `module_function` visibility (#3181, @andrykonchin).
1213

1314
Compatibility:
1415

spec/ruby/core/module/module_function_spec.rb

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,53 @@ def foo
155155

156156
m.foo.should == ["m", "super_m"]
157157
end
158+
159+
context "methods created with define_method" do
160+
context "passed a block" do
161+
it "creates duplicates of the given instance methods" do
162+
m = Module.new do
163+
define_method :test1 do; end
164+
module_function :test1
165+
end
166+
167+
m.respond_to?(:test1).should == true
168+
end
169+
end
170+
171+
context "passed a method" do
172+
it "creates duplicates of the given instance methods" do
173+
module_with_method = Module.new do
174+
def test1; end
175+
end
176+
177+
c = Class.new do
178+
extend module_with_method
179+
end
180+
181+
m = Module.new do
182+
define_method :test2, c.method(:test1)
183+
module_function :test2
184+
end
185+
186+
m.respond_to?(:test2).should == true
187+
end
188+
end
189+
190+
context "passed an unbound method" do
191+
it "creates duplicates of the given instance methods" do
192+
module_with_method = Module.new do
193+
def test1; end
194+
end
195+
196+
m = Module.new do
197+
define_method :test2, module_with_method.instance_method(:test1)
198+
module_function :test2
199+
end
200+
201+
m.respond_to?(:test2).should == true
202+
end
203+
end
204+
end
158205
end
159206

160207
describe "Module#module_function as a toggle (no arguments) in a Module body" do
@@ -282,4 +329,51 @@ def test2() end
282329
m.respond_to?(:test1).should == true
283330
m.respond_to?(:test2).should == true
284331
end
332+
333+
context "methods are defined with define_method" do
334+
context "passed a block" do
335+
it "makes any subsequently defined methods module functions with the normal semantics" do
336+
m = Module.new do
337+
module_function
338+
define_method :test1 do; end
339+
end
340+
341+
m.respond_to?(:test1).should == true
342+
end
343+
end
344+
345+
context "passed a method" do
346+
it "makes any subsequently defined methods module functions with the normal semantics" do
347+
module_with_method = Module.new do
348+
def test1; end
349+
end
350+
351+
c = Class.new do
352+
extend module_with_method
353+
end
354+
355+
m = Module.new do
356+
module_function
357+
define_method :test2, c.method(:test1)
358+
end
359+
360+
m.respond_to?(:test2).should == true
361+
end
362+
end
363+
364+
context "passed an unbound method" do
365+
it "makes any subsequently defined methods module functions with the normal semantics" do
366+
module_with_method = Module.new do
367+
def test1; end
368+
end
369+
370+
m = Module.new do
371+
module_function
372+
define_method :test2, module_with_method.instance_method(:test1)
373+
end
374+
375+
m.respond_to?(:test2).should == true
376+
end
377+
end
378+
end
285379
end

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,7 +1240,8 @@ protected RubySymbol defineMethodWithMethod(
12401240
final String name = nameToJavaStringNode.execute(this, RubyArguments.getArgument(rubyArgs, 0));
12411241
final Object method = RubyArguments.getArgument(rubyArgs, 1);
12421242

1243-
return addMethod(module, name, (RubyMethod) method);
1243+
needCallerFrame(callerFrame, target);
1244+
return addMethod(module, name, (RubyMethod) method, callerFrame.materialize());
12441245
}
12451246

12461247
@Specialization(guards = { "isMethodParameterProvided(rubyArgs)", "isRubyProc(getArgument(rubyArgs, 1))" })
@@ -1294,7 +1295,8 @@ protected RubySymbol defineMethodWithoutMethodAndBlock(
12941295
}
12951296

12961297
@TruffleBoundary
1297-
private RubySymbol addMethod(RubyModule module, String name, RubyMethod method) {
1298+
private RubySymbol addMethod(RubyModule module, String name, RubyMethod method,
1299+
MaterializedFrame callerFrame) {
12981300
final InternalMethod internalMethod = method.method;
12991301

13001302
if (!ModuleOperations.canBindMethodTo(internalMethod, module)) {
@@ -1310,8 +1312,7 @@ private RubySymbol addMethod(RubyModule module, String name, RubyMethod method)
13101312
}
13111313
}
13121314

1313-
module.fields.addMethod(getContext(), this, internalMethod.withName(name));
1314-
return getSymbol(name);
1315+
return addInternalMethod(module, name, internalMethod, callerFrame);
13151316
}
13161317

13171318
@TruffleBoundary

0 commit comments

Comments
 (0)