Skip to content

Commit 8fc7e5e

Browse files
committed
[GR-17126] Use the class where the method was defined to check if an UnboundMethod can be used for define_method.
PullRequest: truffleruby/951
2 parents b7a173d + 90d2f50 commit 8fc7e5e

File tree

7 files changed

+57
-21
lines changed

7 files changed

+57
-21
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Bug fixes:
1010
* Fixed `Symbol#match` returning `MatchData` (#1706).
1111
* Allow `Time#strftime` to be called with binary format strings.
1212
* Do not modify the argument passed to `IO#write` when the encoding does not match (#1714).
13+
* Use the class where the method was defined to check if an `UnboundMethod` can be used for `#define_method` (#1710).
1314

1415
Compatibility:
1516

spec/ruby/core/module/define_method_spec.rb

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ def foo
281281

282282
lambda {
283283
Class.new { define_method :bar, m }
284-
}.should raise_error(TypeError)
284+
}.should raise_error(TypeError, /can't bind singleton method to a different class/)
285285
end
286286

287287
it "raises a TypeError when a Method from one class is defined on an unrelated class" do
@@ -396,20 +396,42 @@ class DefineMethodSpecClass
396396
klass.new.should respond_to(:bar)
397397
end
398398

399+
400+
it "allows an UnboundMethod of a Kernel method retrieved from Object to defined on a BasicObject subclass" do
401+
klass = Class.new(BasicObject) do
402+
define_method :instance_of?, ::Object.instance_method(:instance_of?)
403+
end
404+
klass.new.instance_of?(klass).should == true
405+
end
406+
399407
it "raises a TypeError when an UnboundMethod from a child class is defined on a parent class" do
400408
lambda {
401409
ParentClass = Class.new { define_method(:foo) { :bar } }
402410
ChildClass = Class.new(ParentClass) { define_method(:foo) { :baz } }
403411
ParentClass.send :define_method, :foo, ChildClass.instance_method(:foo)
404-
}.should raise_error(TypeError)
412+
}.should raise_error(TypeError, /bind argument must be a subclass of ChildClass/)
405413
end
406414

407415
it "raises a TypeError when an UnboundMethod from one class is defined on an unrelated class" do
408416
lambda {
409417
DestinationClass = Class.new {
410418
define_method :bar, ModuleSpecs::InstanceMeth.instance_method(:foo)
411419
}
412-
}.should raise_error(TypeError)
420+
}.should raise_error(TypeError, /bind argument must be a subclass of ModuleSpecs::InstanceMeth/)
421+
end
422+
423+
it "raises a TypeError when an UnboundMethod from a singleton class is defined on another class" do
424+
c = Class.new do
425+
class << self
426+
def foo
427+
end
428+
end
429+
end
430+
m = c.method(:foo).unbind
431+
432+
lambda {
433+
Class.new { define_method :bar, m }
434+
}.should raise_error(TypeError, /can't bind singleton method to a different class/)
413435
end
414436
end
415437

spec/ruby/core/unboundmethod/bind_spec.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,21 @@
2121
UnboundMethodSpecs::Mod.instance_method(:from_mod).bind(Object.new).should be_kind_of(Method)
2222
end
2323

24-
it "returns Method returned for obj is equal to one directly returned by obj.method" do
24+
it "the returned Method is equal to the one directly returned by obj.method" do
2525
obj = UnboundMethodSpecs::Methods.new
2626
@normal_um.bind(obj).should == obj.method(:foo)
2727
end
2828

29+
it "returns Method for any object kind_of? the Module the method is defined in" do
30+
@parent_um.bind(UnboundMethodSpecs::Child1.new).should be_kind_of(Method)
31+
@child1_um.bind(UnboundMethodSpecs::Parent.new).should be_kind_of(Method)
32+
@child2_um.bind(UnboundMethodSpecs::Child1.new).should be_kind_of(Method)
33+
end
34+
35+
it "allows binding a Kernel method retrieved from Object on BasicObject" do
36+
Object.instance_method(:instance_of?).bind(BasicObject.new).call(BasicObject).should == true
37+
end
38+
2939
it "returns a callable method" do
3040
obj = UnboundMethodSpecs::Methods.new
3141
@normal_um.bind(obj).call.should == obj.foo

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ public RubyConstant removeConstant(RubyContext context, Node currentNode, String
363363

364364
@TruffleBoundary
365365
public void addMethod(RubyContext context, Node currentNode, InternalMethod method) {
366-
assert ModuleOperations.canBindMethodTo(method.getDeclaringModule(), rubyModuleObject) ||
366+
assert ModuleOperations.canBindMethodTo(method, rubyModuleObject) ||
367367
ModuleOperations.assignableTo(context.getCoreLibrary().getObjectClass(), method.getDeclaringModule()) ||
368368
// TODO (pitr-ch 24-Jul-2016): find out why undefined methods sometimes do not match above assertion
369369
// e.g. "block in _routes route_set.rb:525" in rails/actionpack/lib/action_dispatch/routing/

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

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,14 +1049,18 @@ public DynamicObject defineMethodMethod(DynamicObject module, String name, Dynam
10491049
@TruffleBoundary
10501050
@Specialization(guards = "isRubyUnboundMethod(method)")
10511051
public DynamicObject defineMethod(DynamicObject module, String name, DynamicObject method, NotProvided block) {
1052-
final DynamicObject origin = Layouts.UNBOUND_METHOD.getOrigin(method);
1053-
if (!ModuleOperations.canBindMethodTo(origin, module)) {
1054-
throw new RaiseException(getContext(), coreExceptions().typeError("bind argument must be a subclass of " + Layouts.MODULE.getFields(origin).getName(), this));
1052+
final InternalMethod internalMethod = Layouts.UNBOUND_METHOD.getMethod(method);
1053+
if (!ModuleOperations.canBindMethodTo(internalMethod, module)) {
1054+
final DynamicObject declaringModule = internalMethod.getDeclaringModule();
1055+
if (RubyGuards.isSingletonClass(declaringModule)) {
1056+
throw new RaiseException(getContext(), coreExceptions().typeError(
1057+
"can't bind singleton method to a different class", this));
1058+
} else {
1059+
throw new RaiseException(getContext(), coreExceptions().typeError("bind argument must be a subclass of " + Layouts.MODULE.getFields(declaringModule).getName(), this));
1060+
}
10551061
}
10561062

1057-
// TODO CS 5-Apr-15 TypeError if the method came from a singleton
1058-
1059-
return addMethod(module, name, Layouts.UNBOUND_METHOD.getMethod(method));
1063+
return addMethod(module, name, internalMethod);
10601064
}
10611065

10621066
@TruffleBoundary

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,17 +60,17 @@ public static boolean inAncestorsOf(DynamicObject module, DynamicObject ancestor
6060
}
6161

6262
@TruffleBoundary
63-
public static boolean canBindMethodTo(DynamicObject origin, DynamicObject module) {
64-
assert RubyGuards.isRubyModule(origin);
63+
public static boolean canBindMethodTo(InternalMethod method, DynamicObject module) {
6564
assert RubyGuards.isRubyModule(module);
65+
final DynamicObject origin = method.getDeclaringModule();
6666

67-
if (!(RubyGuards.isRubyClass(origin))) {
67+
if (!RubyGuards.isRubyClass(origin)) { // Module (not Class) methods can always be bound
6868
return true;
6969
} else if (Layouts.MODULE.getFields(module).isRefinement()) {
7070
DynamicObject refinedClass = Layouts.MODULE.getFields(module).getRefinedClass();
71-
return ((RubyGuards.isRubyClass(refinedClass)) && ModuleOperations.assignableTo(refinedClass, origin));
71+
return RubyGuards.isRubyClass(refinedClass) && ModuleOperations.assignableTo(refinedClass, origin);
7272
} else {
73-
return ((RubyGuards.isRubyClass(module)) && ModuleOperations.assignableTo(module, origin));
73+
return RubyGuards.isRubyClass(module) && ModuleOperations.assignableTo(module, origin);
7474
}
7575
}
7676

src/main/java/org/truffleruby/language/methods/CanBindMethodToModuleNode.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,17 @@ public static CanBindMethodToModuleNode create() {
3232
protected boolean canBindMethodToCached(InternalMethod method, DynamicObject module,
3333
@Cached("method.getDeclaringModule()") DynamicObject declaringModule,
3434
@Cached("module") DynamicObject cachedModule,
35-
@Cached("canBindMethodTo(declaringModule, cachedModule)") boolean canBindMethodTo) {
35+
@Cached("canBindMethodTo(method, cachedModule)") boolean canBindMethodTo) {
3636
return canBindMethodTo;
3737
}
3838

3939
@Specialization(guards = "isRubyModule(module)")
4040
protected boolean canBindMethodToUncached(InternalMethod method, DynamicObject module) {
41-
final DynamicObject declaringModule = method.getDeclaringModule();
42-
return canBindMethodTo(declaringModule, module);
41+
return canBindMethodTo(method, module);
4342
}
4443

45-
protected boolean canBindMethodTo(DynamicObject declaringModule, DynamicObject module) {
46-
return ModuleOperations.canBindMethodTo(declaringModule, module);
44+
protected boolean canBindMethodTo(InternalMethod method, DynamicObject module) {
45+
return ModuleOperations.canBindMethodTo(method, module);
4746
}
4847

4948
protected int getCacheLimit() {

0 commit comments

Comments
 (0)