force_bind 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +24 -20
- data/Rakefile +12 -9
- data/ext/extconf.rb +3 -4
- data/ext/force_bind.c +54 -15
- data/ext/method.h +103 -0
- data/spec/force_bind_spec.rb +94 -0
- metadata +31 -9
- data/test/test_force_bind.rb +0 -25
data/README
CHANGED
@@ -3,32 +3,36 @@
|
|
3
3
|
Adds UnboundMethod#force_bind to bind an unbound method to class (or any object of any type).
|
4
4
|
It basically bypasses the argument type checking that #bind has.
|
5
5
|
|
6
|
+
== Requirements
|
7
|
+
|
8
|
+
* Ruby 1.9.1 or greater (does not work in Ruby 1.8.x)
|
9
|
+
|
6
10
|
== Example
|
7
11
|
|
8
12
|
Bind an instance method to its class (making it act like a class method):
|
9
13
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
14
|
+
class Foo
|
15
|
+
def bar
|
16
|
+
puts "I'm inside #{self}"
|
14
17
|
end
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
end
|
19
|
+
|
20
|
+
Foo.new.bar
|
21
|
+
#=> "I'm inside #<Foo:0x123456>"
|
22
|
+
|
23
|
+
meth = Foo.instance_method(:bar).force_bind(Foo)
|
24
|
+
meth.call
|
25
|
+
#=> "I'm inside Foo"
|
22
26
|
|
23
27
|
You can also use this to rebind the instance method of any object to any
|
24
28
|
other object:
|
25
29
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
30
|
+
class A
|
31
|
+
def foo; self end
|
32
|
+
end
|
33
|
+
|
34
|
+
class B; end
|
35
|
+
|
36
|
+
meth = A.instance_method(:foo).force_bind(B.new)
|
37
|
+
meth.call
|
38
|
+
#=> #<B:0x123456>
|
data/Rakefile
CHANGED
@@ -4,15 +4,18 @@ require 'rake/gempackagetask'
|
|
4
4
|
WINDOWS = (PLATFORM =~ /win32|cygwin/ ? true : false) rescue false
|
5
5
|
SUDO = WINDOWS ? '' : 'sudo'
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
pkg.need_tar = true
|
7
|
+
desc "Builds the gem"
|
8
|
+
task :gem => :build do
|
9
|
+
load 'force_bind.gemspec'
|
10
|
+
Gem::Builder.new(SPEC).build
|
12
11
|
end
|
13
12
|
|
14
|
-
desc "
|
15
|
-
task :install => :
|
16
|
-
sh "#{SUDO} gem install
|
17
|
-
sh "rm -rf pkg/#{SPEC.name}-#{SPEC.version}" unless ENV['KEEP_FILES']
|
13
|
+
desc "Installs the gem"
|
14
|
+
task :install => :gem do
|
15
|
+
sh "#{SUDO} gem install #{SPEC.name}-#{SPEC.version}.gem --no-rdoc --no-ri"
|
18
16
|
end
|
17
|
+
|
18
|
+
desc 'Build the extension'
|
19
|
+
task :build do
|
20
|
+
sh "cd ext && ruby extconf.rb && make"
|
21
|
+
end
|
data/ext/extconf.rb
CHANGED
data/ext/force_bind.c
CHANGED
@@ -1,29 +1,68 @@
|
|
1
1
|
#include "ruby.h"
|
2
2
|
|
3
|
+
#ifdef RUBY192_OR_GREATER
|
4
|
+
|
5
|
+
#include "method.h"
|
6
|
+
|
7
|
+
struct METHOD {
|
8
|
+
VALUE recv;
|
9
|
+
VALUE rclass;
|
10
|
+
ID id;
|
11
|
+
rb_method_entry_t me;
|
12
|
+
};
|
13
|
+
|
14
|
+
/*
|
15
|
+
* Similar to +UnboundMethod#bind+, but forces the bind regardless of the type.
|
16
|
+
* @return [Method]
|
17
|
+
*/
|
18
|
+
VALUE
|
19
|
+
umethod_force_bind(VALUE method, VALUE recv)
|
20
|
+
{
|
21
|
+
struct METHOD *data, *bound;
|
22
|
+
const rb_data_type_t *type;
|
23
|
+
|
24
|
+
type = RTYPEDDATA_TYPE(method);
|
25
|
+
TypedData_Get_Struct(method, struct METHOD, type, data);
|
26
|
+
method = TypedData_Make_Struct(rb_cMethod, struct METHOD, type, bound);
|
27
|
+
*bound = *data;
|
28
|
+
if (bound->me.def) bound->me.def->alias_count++;
|
29
|
+
bound->recv = recv;
|
30
|
+
bound->rclass = CLASS_OF(recv);
|
31
|
+
|
32
|
+
return method;
|
33
|
+
}
|
34
|
+
|
35
|
+
#else
|
36
|
+
|
3
37
|
struct METHOD {
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
38
|
+
VALUE oclass;
|
39
|
+
VALUE rclass;
|
40
|
+
VALUE recv;
|
41
|
+
ID id, oid;
|
42
|
+
struct RNode *body;
|
9
43
|
};
|
10
44
|
|
11
|
-
|
45
|
+
VALUE
|
12
46
|
umethod_force_bind(VALUE method, VALUE recv)
|
13
47
|
{
|
14
|
-
|
48
|
+
struct METHOD *data, *bound;
|
15
49
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
50
|
+
Data_Get_Struct(method, struct METHOD, data);
|
51
|
+
method = Data_Make_Struct(rb_cMethod, struct METHOD, free, -1, bound);
|
52
|
+
*bound = *data;
|
53
|
+
bound->recv = recv;
|
54
|
+
bound->rclass = TYPE(recv) == T_CLASS ? RCLASS(recv) : CLASS_OF(recv);
|
21
55
|
|
22
|
-
|
56
|
+
return method;
|
23
57
|
}
|
24
58
|
|
59
|
+
#endif /* RUBY192_OR_GREATER */
|
60
|
+
|
25
61
|
void
|
26
62
|
Init_force_bind()
|
27
63
|
{
|
28
|
-
|
29
|
-
|
64
|
+
rb_define_method(rb_cUnboundMethod, "force_bind", umethod_force_bind, 1);
|
65
|
+
#if 0
|
66
|
+
rb_cUnboundMethod = rb_define_class("UnboundMethod", rb_cObject); /* for docs */
|
67
|
+
#endif
|
68
|
+
}
|
data/ext/method.h
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
/**********************************************************************
|
2
|
+
|
3
|
+
method.h -
|
4
|
+
|
5
|
+
$Author: ko1 $
|
6
|
+
created at: Wed Jul 15 20:02:33 2009
|
7
|
+
|
8
|
+
Copyright (C) 2009 Koichi Sasada
|
9
|
+
|
10
|
+
**********************************************************************/
|
11
|
+
#ifndef METHOD_H
|
12
|
+
#define METHOD_H
|
13
|
+
|
14
|
+
typedef enum {
|
15
|
+
NOEX_PUBLIC = 0x00,
|
16
|
+
NOEX_NOSUPER = 0x01,
|
17
|
+
NOEX_PRIVATE = 0x02,
|
18
|
+
NOEX_PROTECTED = 0x04,
|
19
|
+
NOEX_MASK = 0x06,
|
20
|
+
NOEX_BASIC = 0x08,
|
21
|
+
NOEX_UNDEF = NOEX_NOSUPER,
|
22
|
+
NOEX_MODFUNC = 0x12,
|
23
|
+
NOEX_SUPER = 0x20,
|
24
|
+
NOEX_VCALL = 0x40,
|
25
|
+
NOEX_RESPONDS = 0x80
|
26
|
+
} rb_method_flag_t;
|
27
|
+
|
28
|
+
#define NOEX_SAFE(n) ((int)((n) >> 8) & 0x0F)
|
29
|
+
#define NOEX_WITH(n, s) ((s << 8) | (n) | (ruby_running ? 0 : NOEX_BASIC))
|
30
|
+
#define NOEX_WITH_SAFE(n) NOEX_WITH(n, rb_safe_level())
|
31
|
+
|
32
|
+
/* method data type */
|
33
|
+
|
34
|
+
typedef enum {
|
35
|
+
VM_METHOD_TYPE_ISEQ,
|
36
|
+
VM_METHOD_TYPE_CFUNC,
|
37
|
+
VM_METHOD_TYPE_ATTRSET,
|
38
|
+
VM_METHOD_TYPE_IVAR,
|
39
|
+
VM_METHOD_TYPE_BMETHOD,
|
40
|
+
VM_METHOD_TYPE_ZSUPER,
|
41
|
+
VM_METHOD_TYPE_UNDEF,
|
42
|
+
VM_METHOD_TYPE_NOTIMPLEMENTED,
|
43
|
+
VM_METHOD_TYPE_OPTIMIZED, /* Kernel#send, Proc#call, etc */
|
44
|
+
VM_METHOD_TYPE_MISSING /* wrapper for method_missing(id) */
|
45
|
+
} rb_method_type_t;
|
46
|
+
|
47
|
+
typedef struct rb_method_cfunc_struct {
|
48
|
+
VALUE (*func)(ANYARGS);
|
49
|
+
int argc;
|
50
|
+
} rb_method_cfunc_t;
|
51
|
+
|
52
|
+
typedef struct rb_method_attr_struct {
|
53
|
+
ID id;
|
54
|
+
VALUE location;
|
55
|
+
} rb_method_attr_t;
|
56
|
+
|
57
|
+
typedef struct rb_iseq_struct rb_iseq_t;
|
58
|
+
|
59
|
+
typedef struct rb_method_definition_struct {
|
60
|
+
rb_method_type_t type; /* method type */
|
61
|
+
ID original_id;
|
62
|
+
union {
|
63
|
+
rb_iseq_t *iseq; /* should be mark */
|
64
|
+
rb_method_cfunc_t cfunc;
|
65
|
+
rb_method_attr_t attr;
|
66
|
+
VALUE proc; /* should be mark */
|
67
|
+
enum method_optimized_type {
|
68
|
+
OPTIMIZED_METHOD_TYPE_SEND,
|
69
|
+
OPTIMIZED_METHOD_TYPE_CALL
|
70
|
+
} optimize_type;
|
71
|
+
} body;
|
72
|
+
int alias_count;
|
73
|
+
} rb_method_definition_t;
|
74
|
+
|
75
|
+
typedef struct rb_method_entry_struct {
|
76
|
+
rb_method_flag_t flag;
|
77
|
+
char mark;
|
78
|
+
rb_method_definition_t *def;
|
79
|
+
ID called_id;
|
80
|
+
VALUE klass; /* should be mark */
|
81
|
+
} rb_method_entry_t;
|
82
|
+
|
83
|
+
struct unlinked_method_entry_list_entry {
|
84
|
+
struct unlinked_method_entry_list_entry *next;
|
85
|
+
rb_method_entry_t *me;
|
86
|
+
};
|
87
|
+
|
88
|
+
#define UNDEFINED_METHOD_ENTRY_P(me) (!(me) || !(me)->def || (me)->def->type == VM_METHOD_TYPE_UNDEF)
|
89
|
+
|
90
|
+
void rb_add_method_cfunc(VALUE klass, ID mid, VALUE (*func)(ANYARGS), int argc, rb_method_flag_t noex);
|
91
|
+
rb_method_entry_t *rb_add_method(VALUE klass, ID mid, rb_method_type_t type, void *option, rb_method_flag_t noex);
|
92
|
+
rb_method_entry_t *rb_method_entry(VALUE klass, ID id);
|
93
|
+
|
94
|
+
rb_method_entry_t *rb_method_entry_get_without_cache(VALUE klass, ID id);
|
95
|
+
rb_method_entry_t *rb_method_entry_set(VALUE klass, ID mid, const rb_method_entry_t *, rb_method_flag_t noex);
|
96
|
+
|
97
|
+
int rb_method_entry_arity(const rb_method_entry_t *me);
|
98
|
+
|
99
|
+
void rb_mark_method_entry(const rb_method_entry_t *me);
|
100
|
+
void rb_free_method_entry(rb_method_entry_t *me);
|
101
|
+
void rb_sweep_method_entry(void *vm);
|
102
|
+
|
103
|
+
#endif /* METHOD_H */
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require_relative '../ext/force_bind'
|
3
|
+
|
4
|
+
describe 'UnboundMethod#force_bind' do
|
5
|
+
class Mock
|
6
|
+
def the_method
|
7
|
+
self
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'binds an instance method on a class' do
|
12
|
+
Mock.instance_method(:the_method).force_bind(Mock).call.should be Mock
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'binds an instance method on an instance of the same class' do
|
16
|
+
Mock.instance_method(:the_method).force_bind(mock = Mock.new).call.should be mock
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'binds an instance method on an instance of another class' do
|
20
|
+
Mock.instance_method(:the_method).force_bind(object = Object.new).call.should be object
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'environment' do
|
24
|
+
class C
|
25
|
+
def initialize(n) @n = n end
|
26
|
+
def n; @n end
|
27
|
+
end
|
28
|
+
class D
|
29
|
+
def initialize(n) @n = n end
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'refers dynamically to instance variables' do
|
33
|
+
C.instance_method(:n).force_bind(D.new(4)).call.should == 4
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'refers dynamically to methods' do
|
37
|
+
C.send(:define_method, :each) { |&b| (1..@n).each(&b) }
|
38
|
+
Enumerable.instance_method(:select).force_bind(C.new(5)).call(&:even?).should == [2,4]
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'can transplant methods on instances' do
|
42
|
+
(d = D.new(4)).define_singleton_method(:attr_n, &C.instance_method(:n).force_bind(d))
|
43
|
+
d.attr_n.should == 4
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'can transplant methods on classes with a specific instance' do
|
47
|
+
D.send(:define_method, :attr_n, &C.instance_method(:n).force_bind(d = D.new(4)))
|
48
|
+
d.attr_n.should == 4
|
49
|
+
D.new(:whatever).attr_n.should == 4
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'can transplant methods from a class to another by rebinding' do
|
53
|
+
class Source
|
54
|
+
def steal_me
|
55
|
+
[self, @a]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
class Target
|
59
|
+
def initialize(a) @a = a end
|
60
|
+
end
|
61
|
+
|
62
|
+
steal = lambda do |source_method, target_class, method_name|
|
63
|
+
target_class.class_exec do
|
64
|
+
define_method(method_name) do |*a,&b|
|
65
|
+
source_method.force_bind(self).call(*a,&b)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
target = Target.new(2)
|
71
|
+
steal.call(Source.instance_method(:steal_me), Target, :stolen)
|
72
|
+
target.stolen.should == [target, 2]
|
73
|
+
(t = Target.new(3)).stolen.should == [t, 3]
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'can not (yet) bind methods using super on other instances' do
|
77
|
+
class Parent
|
78
|
+
def meth
|
79
|
+
:meth
|
80
|
+
end
|
81
|
+
end
|
82
|
+
class Child < Parent
|
83
|
+
def meth
|
84
|
+
super
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
lambda {
|
89
|
+
Child.instance_method(:meth).force_bind(C.new(1)).call
|
90
|
+
}.should raise_error(NotImplementedError,
|
91
|
+
'super from singleton method that is defined to multiple classes is not supported; this will be fixed in 1.9.3 or later')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: force_bind
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 1
|
9
|
+
version: 0.1.1
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Loren Segal
|
@@ -9,10 +14,22 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date:
|
17
|
+
date: 2011-01-10 00:00:00 -05:00
|
13
18
|
default_executable:
|
14
|
-
dependencies:
|
15
|
-
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :development
|
32
|
+
version_requirements: *id001
|
16
33
|
description:
|
17
34
|
email: lsegal@soen.ca
|
18
35
|
executables: []
|
@@ -25,8 +42,9 @@ files:
|
|
25
42
|
- Rakefile
|
26
43
|
- README
|
27
44
|
- ext/force_bind.c
|
45
|
+
- ext/method.h
|
28
46
|
- ext/extconf.rb
|
29
|
-
-
|
47
|
+
- spec/force_bind_spec.rb
|
30
48
|
has_rdoc: false
|
31
49
|
homepage: http://github.com/lsegal/force_bind
|
32
50
|
licenses: []
|
@@ -37,23 +55,27 @@ rdoc_options: []
|
|
37
55
|
require_paths:
|
38
56
|
- lib
|
39
57
|
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
40
59
|
requirements:
|
41
60
|
- - ">="
|
42
61
|
- !ruby/object:Gem::Version
|
62
|
+
segments:
|
63
|
+
- 0
|
43
64
|
version: "0"
|
44
|
-
version:
|
45
65
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
46
67
|
requirements:
|
47
68
|
- - ">="
|
48
69
|
- !ruby/object:Gem::Version
|
70
|
+
segments:
|
71
|
+
- 0
|
49
72
|
version: "0"
|
50
|
-
version:
|
51
73
|
requirements: []
|
52
74
|
|
53
75
|
rubyforge_project:
|
54
|
-
rubygems_version: 1.3.
|
76
|
+
rubygems_version: 1.3.7
|
55
77
|
signing_key:
|
56
78
|
specification_version: 3
|
57
|
-
summary: Adds UnboundMethod#force_bind to bind an unbound method to class (or any object of any type)
|
79
|
+
summary: Adds UnboundMethod#force_bind to bind an unbound method to a class (or any object of any type)
|
58
80
|
test_files: []
|
59
81
|
|
data/test/test_force_bind.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
require 'test/unit'
|
2
|
-
require File.dirname(__FILE__) + '/../force_bind'
|
3
|
-
|
4
|
-
class Mock
|
5
|
-
def the_method; self end
|
6
|
-
end
|
7
|
-
|
8
|
-
class TestForceBind < Test::Unit::TestCase
|
9
|
-
def test_force_bind_class
|
10
|
-
proc = Mock.instance_method(:the_method).force_bind(Mock)
|
11
|
-
assert_equal Mock, proc.call
|
12
|
-
end
|
13
|
-
|
14
|
-
def test_force_bind_object
|
15
|
-
mock = Mock.new
|
16
|
-
proc = Mock.instance_method(:the_method).force_bind(mock)
|
17
|
-
assert_equal mock, proc.call
|
18
|
-
end
|
19
|
-
|
20
|
-
def test_force_bind_object_from_other_class
|
21
|
-
arr = Array.new
|
22
|
-
proc = Mock.instance_method(:the_method).force_bind(arr)
|
23
|
-
assert_equal arr, proc.call
|
24
|
-
end
|
25
|
-
end
|