Skip to content

Commit 4dab7ea

Browse files
committed
[GR-18163] Fix #define_method with module_function visibility
PullRequest: truffleruby/3918
2 parents 3f0a2a3 + 5d665b6 commit 4dab7ea

File tree

3 files changed

+121
-25
lines changed

3 files changed

+121
-25
lines changed

CHANGELOG.md

+1
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

+115-21
Original file line numberDiff line numberDiff line change
@@ -155,15 +155,62 @@ 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
161208
it "makes any subsequently defined methods module functions with the normal semantics" do
162-
m = Module.new {
209+
m = Module.new do
163210
module_function
164211
def test1() end
165212
def test2() end
166-
}
213+
end
167214

168215
m.respond_to?(:test1).should == true
169216
m.respond_to?(:test2).should == true
@@ -187,13 +234,13 @@ def test2() end
187234

188235
it "stops creating module functions if the body encounters another toggle " \
189236
"like public/protected/private without arguments" do
190-
m = Module.new {
237+
m = Module.new do
191238
module_function
192239
def test1() end
193240
def test2() end
194241
public
195242
def test3() end
196-
}
243+
end
197244

198245
m.respond_to?(:test1).should == true
199246
m.respond_to?(:test2).should == true
@@ -202,84 +249,131 @@ def test3() end
202249

203250
it "does not stop creating module functions if the body encounters " \
204251
"public/protected/private WITH arguments" do
205-
m = Module.new {
252+
m = Module.new do
206253
def foo() end
207254
module_function
208255
def test1() end
209256
def test2() end
210257
public :foo
211258
def test3() end
212-
}
259+
end
213260

214261
m.respond_to?(:test1).should == true
215262
m.respond_to?(:test2).should == true
216263
m.respond_to?(:test3).should == true
217264
end
218265

219266
it "does not affect module_evaled method definitions also if outside the eval itself" do
220-
m = Module.new {
267+
m = Module.new do
221268
module_function
222269
module_eval { def test1() end }
223270
module_eval " def test2() end "
224-
}
271+
end
225272

226273
m.respond_to?(:test1).should == false
227274
m.respond_to?(:test2).should == false
228275
end
229276

230277
it "has no effect if inside a module_eval if the definitions are outside of it" do
231-
m = Module.new {
278+
m = Module.new do
232279
module_eval { module_function }
233280
def test1() end
234281
def test2() end
235-
}
282+
end
236283

237284
m.respond_to?(:test1).should == false
238285
m.respond_to?(:test2).should == false
239286
end
240287

241288
it "functions normally if both toggle and definitions inside a module_eval" do
242-
m = Module.new {
243-
module_eval {
289+
m = Module.new do
290+
module_eval do
244291
module_function
245292
def test1() end
246293
def test2() end
247-
}
248-
}
294+
end
295+
end
249296

250297
m.respond_to?(:test1).should == true
251298
m.respond_to?(:test2).should == true
252299
end
253300

254-
it "affects evaled method definitions also even when outside the eval itself" do
255-
m = Module.new {
301+
it "affects eval'ed method definitions also even when outside the eval itself" do
302+
m = Module.new do
256303
module_function
257304
eval "def test1() end"
258-
}
305+
end
259306

260307
m.respond_to?(:test1).should == true
261308
end
262309

263310
it "doesn't affect definitions when inside an eval even if the definitions are outside of it" do
264-
m = Module.new {
311+
m = Module.new do
265312
eval "module_function"
266313
def test1() end
267-
}
314+
end
268315

269316
m.respond_to?(:test1).should == false
270317
end
271318

272319
it "functions normally if both toggle and definitions inside a eval" do
273-
m = Module.new {
320+
m = Module.new do
274321
eval <<-CODE
275322
module_function
276323
277324
def test1() end
278325
def test2() end
279326
CODE
280-
}
327+
end
281328

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

+5-4
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)